From ce32cd576be1496eba4dc2eec804e1059d99457d Mon Sep 17 00:00:00 2001 From: Johann150 Date: Thu, 23 Sep 2021 15:32:16 +0200 Subject: fix inboxQueue import (#7829) --- src/server/api/endpoints/admin/queue/inbox-delayed.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/server') diff --git a/src/server/api/endpoints/admin/queue/inbox-delayed.ts b/src/server/api/endpoints/admin/queue/inbox-delayed.ts index 59e5c834ed..1925906c28 100644 --- a/src/server/api/endpoints/admin/queue/inbox-delayed.ts +++ b/src/server/api/endpoints/admin/queue/inbox-delayed.ts @@ -1,6 +1,6 @@ import { URL } from 'url'; import define from '../../../define'; -import { inboxQueue } from '@/queue/index'; +import { inboxQueue } from '@/queue/queues'; export const meta = { tags: ['admin'], -- cgit v1.2.3-freya From 414f1d11582510f77ea3a927053fe68fa160278b Mon Sep 17 00:00:00 2001 From: Johann150 Date: Wed, 29 Sep 2021 18:44:22 +0200 Subject: fix: truncate image descriptions (#7699) * move truncate function to separate file to reuse it * truncate image descriptions * show image description limit in UI * correctly treat null Co-authored-by: nullobsi * make truncate Unicode-aware The strings that truncate returns should now be valid Unicode. PostgreSQL also counts Unicode Code Points instead of bytes so this should be correct. * move truncate to internal, validate in API Truncating could also be done in src/services/drive/add-file.ts or src/services/drive/upload-from-url.ts but those would also affect local images. But local images should result in a hard error if the image comment is too long. * avoid overwriting Co-authored-by: nullobsi --- src/client/components/media-caption.vue | 30 +++++++++++++++++++--- src/misc/hard-limits.ts | 6 +++++ src/misc/truncate.ts | 11 ++++++++ src/remote/activitypub/models/image.ts | 4 ++- src/remote/activitypub/models/person.ts | 11 +------- src/server/api/endpoints/drive/files/update.ts | 3 ++- .../api/endpoints/drive/files/upload-from-url.ts | 3 ++- 7 files changed, 51 insertions(+), 17 deletions(-) create mode 100644 src/misc/truncate.ts (limited to 'src/server') diff --git a/src/client/components/media-caption.vue b/src/client/components/media-caption.vue index 690927d4c5..73eba23025 100644 --- a/src/client/components/media-caption.vue +++ b/src/client/components/media-caption.vue @@ -3,10 +3,13 @@
-
+
+ + {{ remainingLength }} +
- {{ $ts.ok }} + {{ $ts.ok }} {{ $ts.cancel }}
@@ -26,10 +29,12 @@ + + diff --git a/src/client/router.ts b/src/client/router.ts index 573f285c79..56dc948669 100644 --- a/src/client/router.ts +++ b/src/client/router.ts @@ -23,6 +23,7 @@ const defaultRoutes = [ { path: '/@:acct/room', props: true, component: page('room/room') }, { path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, { path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) }, + { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) }, { path: '/announcements', component: page('announcements') }, { path: '/about', component: page('about') }, { path: '/about-misskey', component: page('about-misskey') }, diff --git a/src/db/postgre.ts b/src/db/postgre.ts index c963242488..8948f22cdc 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -72,6 +72,7 @@ import { ChannelNotePining } from '@/models/entities/channel-note-pining'; import { RegistryItem } from '@/models/entities/registry-item'; import { Ad } from '@/models/entities/ad'; import { PasswordResetRequest } from '@/models/entities/password-reset-request'; +import { UserPending } from '@/models/entities/user-pending'; const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); @@ -173,6 +174,7 @@ export const entities = [ RegistryItem, Ad, PasswordResetRequest, + UserPending, ...charts as any ]; diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts index 6428aacdf1..9a1a87c155 100644 --- a/src/models/entities/meta.ts +++ b/src/models/entities/meta.ts @@ -148,6 +148,11 @@ export class Meta { @JoinColumn() public proxyAccount: User | null; + @Column('boolean', { + default: false, + }) + public emailRequiredForSignup: boolean; + @Column('boolean', { default: false, }) diff --git a/src/models/entities/user-pending.ts b/src/models/entities/user-pending.ts new file mode 100644 index 0000000000..40482af333 --- /dev/null +++ b/src/models/entities/user-pending.ts @@ -0,0 +1,32 @@ +import { PrimaryColumn, Entity, Index, Column } from 'typeorm'; +import { id } from '../id'; + +@Entity() +export class UserPending { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone') + public createdAt: Date; + + @Index({ unique: true }) + @Column('varchar', { + length: 128, + }) + public code: string; + + @Column('varchar', { + length: 128, + }) + public username: string; + + @Column('varchar', { + length: 128, + }) + public email: string; + + @Column('varchar', { + length: 128, + }) + public password: string; +} diff --git a/src/models/index.ts b/src/models/index.ts index 9f8bd104e9..059a3d7b87 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -62,6 +62,7 @@ import { ChannelNotePining } from './entities/channel-note-pining'; import { RegistryItem } from './entities/registry-item'; import { Ad } from './entities/ad'; import { PasswordResetRequest } from './entities/password-reset-request'; +import { UserPending } from './entities/user-pending'; export const Announcements = getRepository(Announcement); export const AnnouncementReads = getRepository(AnnouncementRead); @@ -76,6 +77,7 @@ export const PollVotes = getRepository(PollVote); export const Users = getCustomRepository(UserRepository); export const UserProfiles = getRepository(UserProfile); export const UserKeypairs = getRepository(UserKeypair); +export const UserPendings = getRepository(UserPending); export const AttestationChallenges = getRepository(AttestationChallenge); export const UserSecurityKeys = getRepository(UserSecurityKey); export const UserPublickeys = getRepository(UserPublickey); diff --git a/src/server/api/common/signup.ts b/src/server/api/common/signup.ts index eb3aa09c8c..2ba0d8e479 100644 --- a/src/server/api/common/signup.ts +++ b/src/server/api/common/signup.ts @@ -11,20 +11,30 @@ import { UserKeypair } from '@/models/entities/user-keypair'; import { usersChart } from '@/services/chart/index'; import { UsedUsername } from '@/models/entities/used-username'; -export async function signup(username: User['username'], password: UserProfile['password'], host: string | null = null) { +export async function signup(opts: { + username: User['username']; + password?: string | null; + passwordHash?: UserProfile['password'] | null; + host?: string | null; +}) { + const { username, password, passwordHash, host } = opts; + let hash = passwordHash; + // Validate username if (!Users.validateLocalUsername.ok(username)) { throw new Error('INVALID_USERNAME'); } - // Validate password - if (!Users.validatePassword.ok(password)) { - throw new Error('INVALID_PASSWORD'); - } + if (password != null && passwordHash == null) { + // Validate password + if (!Users.validatePassword.ok(password)) { + throw new Error('INVALID_PASSWORD'); + } - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); + // Generate hash of password + const salt = await bcrypt.genSalt(8); + hash = await bcrypt.hash(password, salt); + } // Generate secret const secret = generateUserToken(); diff --git a/src/server/api/endpoints/admin/accounts/create.ts b/src/server/api/endpoints/admin/accounts/create.ts index 9691b9c7e3..fa15e84f77 100644 --- a/src/server/api/endpoints/admin/accounts/create.ts +++ b/src/server/api/endpoints/admin/accounts/create.ts @@ -35,7 +35,10 @@ export default define(meta, async (ps, _me) => { })) === 0; if (!noUsers && !me?.isAdmin) throw new Error('access denied'); - const { account, secret } = await signup(ps.username, ps.password); + const { account, secret } = await signup({ + username: ps.username, + password: ps.password, + }); const res = await Users.pack(account, account, { detail: true, diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 46f30fef7d..55447098dc 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -93,6 +93,10 @@ export const meta = { validator: $.optional.bool, }, + emailRequiredForSignup: { + validator: $.optional.bool, + }, + enableHcaptcha: { validator: $.optional.bool, }, @@ -374,6 +378,10 @@ export default define(meta, async (ps, me) => { set.proxyRemoteFiles = ps.proxyRemoteFiles; } + if (ps.emailRequiredForSignup !== undefined) { + set.emailRequiredForSignup = ps.emailRequiredForSignup; + } + if (ps.enableHcaptcha !== undefined) { set.enableHcaptcha = ps.enableHcaptcha; } diff --git a/src/server/api/endpoints/email-address/available.ts b/src/server/api/endpoints/email-address/available.ts new file mode 100644 index 0000000000..65fe6f9178 --- /dev/null +++ b/src/server/api/endpoints/email-address/available.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + tags: ['users'], + + requireCredential: false as const, + + params: { + emailAddress: { + validator: $.str + } + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + available: { + type: 'boolean' as const, + optional: false as const, nullable: false as const, + } + } + } +}; + +export default define(meta, async (ps) => { + const exist = await UserProfiles.count({ + emailVerified: true, + email: ps.emailAddress, + }); + + return { + available: exist === 0 + }; +}); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 3f422dff07..ce21556243 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -104,6 +104,10 @@ export const meta = { type: 'boolean' as const, optional: false as const, nullable: false as const }, + emailRequiredForSignup: { + type: 'boolean' as const, + optional: false as const, nullable: false as const + }, enableHcaptcha: { type: 'boolean' as const, optional: false as const, nullable: false as const @@ -488,6 +492,7 @@ export default define(meta, async (ps, me) => { disableGlobalTimeline: instance.disableGlobalTimeline, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, @@ -537,6 +542,7 @@ export default define(meta, async (ps, me) => { registration: !instance.disableRegistration, localTimeLine: !instance.disableLocalTimeline, globalTimeLine: !instance.disableGlobalTimeline, + emailRequiredForSignup: instance.emailRequiredForSignup, elasticsearch: config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, diff --git a/src/server/api/index.ts b/src/server/api/index.ts index db35fdf9e0..82579075eb 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -12,6 +12,7 @@ import endpoints from './endpoints'; import handler from './api-handler'; import signup from './private/signup'; import signin from './private/signin'; +import signupPending from './private/signup-pending'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; @@ -65,6 +66,7 @@ for (const endpoint of endpoints) { router.post('/signup', signup); router.post('/signin', signin); +router.post('/signup-pending', signupPending); router.use(discord.routes()); router.use(github.routes()); diff --git a/src/server/api/private/signup-pending.ts b/src/server/api/private/signup-pending.ts new file mode 100644 index 0000000000..c0638a1cda --- /dev/null +++ b/src/server/api/private/signup-pending.ts @@ -0,0 +1,35 @@ +import * as Koa from 'koa'; +import { Users, UserPendings, UserProfiles } from '@/models/index'; +import { signup } from '../common/signup'; +import signin from '../common/signin'; + +export default async (ctx: Koa.Context) => { + const body = ctx.request.body; + + const code = body['code']; + + try { + const pendingUser = await UserPendings.findOneOrFail({ code }); + + const { account, secret } = await signup({ + username: pendingUser.username, + passwordHash: pendingUser.password, + }); + + UserPendings.delete({ + id: pendingUser.id, + }); + + const profile = await UserProfiles.findOneOrFail(account.id); + + await UserProfiles.update({ userId: profile.userId }, { + email: pendingUser.email, + emailVerified: true, + emailVerifyCode: null, + }); + + signin(ctx, account); + } catch (e) { + ctx.throw(400, e); + } +}; diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index ef61767f65..93caaea935 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,8 +1,13 @@ import * as Koa from 'koa'; +import rndstr from 'rndstr'; +import * as bcrypt from 'bcryptjs'; import { fetchMeta } from '@/misc/fetch-meta'; import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha'; -import { Users, RegistrationTickets } from '@/models/index'; +import { Users, RegistrationTickets, UserPendings } from '@/models/index'; import { signup } from '../common/signup'; +import config from '@/config'; +import { sendEmail } from '@/services/send-email'; +import { genId } from '@/misc/gen-id'; export default async (ctx: Koa.Context) => { const body = ctx.request.body; @@ -29,8 +34,16 @@ export default async (ctx: Koa.Context) => { const password = body['password']; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; + const emailAddress = body['emailAddress']; - if (instance && instance.disableRegistration) { + if (instance.emailRequiredForSignup) { + if (emailAddress == null || typeof emailAddress != 'string') { + ctx.status = 400; + return; + } + } + + if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode != 'string') { ctx.status = 400; return; @@ -48,18 +61,45 @@ export default async (ctx: Koa.Context) => { RegistrationTickets.delete(ticket.id); } - try { - const { account, secret } = await signup(username, password, host); + if (instance.emailRequiredForSignup) { + const code = rndstr('a-z0-9', 16); + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(password, salt); - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true + await UserPendings.insert({ + id: genId(), + createdAt: new Date(), + code, + email: emailAddress, + username: username, + password: hash, }); - (res as any).token = secret; + const link = `${config.url}/signup-complete/${code}`; + + sendEmail(emailAddress, 'Signup', + `To complete signup, please click this link:
${link}`, + `To complete signup, please click this link: ${link}`); - ctx.body = res; - } catch (e) { - ctx.throw(400, e); + ctx.status = 204; + } else { + try { + const { account, secret } = await signup({ + username, password, host + }); + + const res = await Users.pack(account, account, { + detail: true, + includeSecrets: true + }); + + (res as any).token = secret; + + ctx.body = res; + } catch (e) { + ctx.throw(400, e); + } } }; diff --git a/src/server/nodeinfo.ts b/src/server/nodeinfo.ts index dec2615086..6a864fcc52 100644 --- a/src/server/nodeinfo.ts +++ b/src/server/nodeinfo.ts @@ -68,6 +68,7 @@ const nodeinfo2 = async () => { disableRegistration: meta.disableRegistration, disableLocalTimeline: meta.disableLocalTimeline, disableGlobalTimeline: meta.disableGlobalTimeline, + emailRequiredForSignup: meta.emailRequiredForSignup, enableHcaptcha: meta.enableHcaptcha, enableRecaptcha: meta.enableRecaptcha, maxNoteTextLength: meta.maxNoteTextLength, -- cgit v1.2.3-freya From 5bf69476f625f3c4764cfb242d7d6a21c808f8b8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 8 Oct 2021 14:05:07 +0900 Subject: enhance(api): ap系のエンドポイントをログイン必須化+レートリミット追加 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 他のサーバーにリクエストを送信するという性質上、攻撃の踏み台にされることがあるため --- CHANGELOG.md | 1 + src/server/api/endpoints/ap/get.ts | 8 +++++++- src/server/api/endpoints/ap/show.ts | 8 +++++++- 3 files changed, 15 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index bd526fd694..f65de79116 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように - クライアント: MFM関数構文のサジェストを実装 - ActivityPub: HTML -> MFMの変換を強化 +- API: ap系のエンドポイントをログイン必須化+レートリミット追加 ### Bugfixes - Fix createDeleteAccountJob diff --git a/src/server/api/endpoints/ap/get.ts b/src/server/api/endpoints/ap/get.ts index 2cffce1f16..2f97a24774 100644 --- a/src/server/api/endpoints/ap/get.ts +++ b/src/server/api/endpoints/ap/get.ts @@ -2,11 +2,17 @@ import $ from 'cafy'; import define from '../../define'; import Resolver from '@/remote/activitypub/resolver'; import { ApiError } from '../../error'; +import ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 30 + }, params: { uri: { diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index aa0dae070c..32685d44bd 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -11,11 +11,17 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; +import ms from 'ms'; export const meta = { tags: ['federation'], - requireCredential: false as const, + requireCredential: true as const, + + limit: { + duration: ms('1hour'), + max: 30 + }, params: { uri: { -- cgit v1.2.3-freya From a38e4b0b144eed8b42e6d6c894a00ad3feca40aa Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 8 Oct 2021 21:24:05 +0900 Subject: server: コマンドラインオプション廃止 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve #7863 Resolve #6337 --- CHANGELOG.md | 2 ++ package.json | 1 - src/argv.ts | 23 ----------------------- src/boot/index.ts | 8 ++++---- src/boot/master.ts | 8 ++++---- src/db/postgre.ts | 4 ++-- src/env.ts | 20 ++++++++++++++++++++ src/queue/index.ts | 4 ++-- src/server/index.ts | 4 ++-- src/services/logger.ts | 8 ++++---- yarn.lock | 5 ----- 11 files changed, 40 insertions(+), 47 deletions(-) delete mode 100644 src/argv.ts create mode 100644 src/env.ts (limited to 'src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index f65de79116..215555df72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ - クライアント: MFM関数構文のサジェストを実装 - ActivityPub: HTML -> MFMの変換を強化 - API: ap系のエンドポイントをログイン必須化+レートリミット追加 +- Misskeyのコマンドラインオプションを廃止 + - 代わりに環境変数で設定することができます ### Bugfixes - Fix createDeleteAccountJob diff --git a/package.json b/package.json index d12a0fc9ba..308e6242c8 100644 --- a/package.json +++ b/package.json @@ -121,7 +121,6 @@ "chalk": "4.1.2", "chart.js": "2.9.4", "cli-highlight": "2.1.11", - "commander": "4.1.1", "compare-versions": "3.6.0", "concurrently": "6.3.0", "content-disposition": "0.5.3", diff --git a/src/argv.ts b/src/argv.ts deleted file mode 100644 index 106ecf2675..0000000000 --- a/src/argv.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Command } from 'commander'; -import config from '@/config/index'; - -const program = new Command(); - -program.version(config.version); -program.option('--no-daemons', 'Disable daemon processes (for debbuging)'); -program.option('--disable-clustering', 'Disable clustering'); -program.option('--only-server', 'Run server only (without job queue processing)'); -program.option('--only-queue', 'Pocessing job queue only (without server)'); -program.option('--quiet', 'Suppress all logs'); -program.option('--verbose', 'Enable all logs'); -program.option('--with-log-time', 'Include timestamp for each logs'); -program.option('--slow', 'Delay all requests (for debbuging)'); -program.option('--color', 'This option is a dummy for some external program\'s (e.g. forever) issue.'); -program.parse(process.argv); - -if (process.env.MK_ONLY_QUEUE) program.onlyQueue = true; -if (process.env.NODE_ENV === 'test') program.disableClustering = true; -//if (process.env.NODE_ENV === 'test') program.quiet = true; -if (process.env.NODE_ENV === 'test') program.noDaemons = true; - -export { program }; diff --git a/src/boot/index.ts b/src/boot/index.ts index 20c53a366c..cb4c8536db 100644 --- a/src/boot/index.ts +++ b/src/boot/index.ts @@ -3,7 +3,7 @@ import * as chalk from 'chalk'; import Xev from 'xev'; import Logger from '@/services/logger'; -import { program } from '../argv'; +import { envOption } from '../env'; // for typeorm import 'reflect-metadata'; @@ -20,7 +20,7 @@ const ev = new Xev(); export default async function() { process.title = `Misskey (${cluster.isMaster ? 'master' : 'worker'})`; - if (cluster.isMaster || program.disableClustering) { + if (cluster.isMaster || envOption.disableClustering) { await masterMain(); if (cluster.isMaster) { @@ -28,7 +28,7 @@ export default async function() { } } - if (cluster.isWorker || program.disableClustering) { + if (cluster.isWorker || envOption.disableClustering) { await workerMain(); } @@ -60,7 +60,7 @@ cluster.on('exit', worker => { }); // Display detail of unhandled promise rejection -if (!program.quiet) { +if (!envOption.quiet) { process.on('unhandledRejection', console.dir); } diff --git a/src/boot/master.ts b/src/boot/master.ts index d9cc7c16be..071b37b76d 100644 --- a/src/boot/master.ts +++ b/src/boot/master.ts @@ -11,7 +11,7 @@ import Logger from '@/services/logger'; import loadConfig from '@/config/load'; import { Config } from '@/config/types'; import { lessThan } from '@/prelude/array'; -import { program } from '../argv'; +import { envOption } from '../env'; import { showMachineInfo } from '@/misc/show-machine-info'; import { initDb } from '../db/postgre'; @@ -25,7 +25,7 @@ const logger = new Logger('core', 'cyan'); const bootLogger = logger.createSubLogger('boot', 'magenta', false); function greet() { - if (!program.quiet) { + if (!envOption.quiet) { //#region Misskey logo const v = `v${meta.version}`; console.log(' _____ _ _ '); @@ -73,13 +73,13 @@ export async function masterMain() { bootLogger.succ('Misskey initialized'); - if (!program.disableClustering) { + if (!envOption.disableClustering) { await spawnWorkers(config.clusterLimit); } bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); - if (!program.noDaemons) { + if (!envOption.noDaemons) { require('../daemons/server-stats').default(); require('../daemons/queue-stats').default(); require('../daemons/janitor').default(); diff --git a/src/db/postgre.ts b/src/db/postgre.ts index 8948f22cdc..0b635ea18e 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -63,7 +63,7 @@ import { Antenna } from '@/models/entities/antenna'; import { AntennaNote } from '@/models/entities/antenna-note'; import { PromoNote } from '@/models/entities/promo-note'; import { PromoRead } from '@/models/entities/promo-read'; -import { program } from '../argv'; +import { envOption } from '../env'; import { Relay } from '@/models/entities/relay'; import { MutedNote } from '@/models/entities/muted-note'; import { Channel } from '@/models/entities/channel'; @@ -84,7 +84,7 @@ class MyCustomLogger implements Logger { } public logQuery(query: string, parameters?: any[]) { - if (program.verbose) { + if (envOption.verbose) { sqlLogger.info(this.highlight(query)); } } diff --git a/src/env.ts b/src/env.ts new file mode 100644 index 0000000000..1b678edc44 --- /dev/null +++ b/src/env.ts @@ -0,0 +1,20 @@ +const envOption = { + onlyQueue: false, + onlyServer: false, + noDaemons: false, + disableClustering: false, + verbose: false, + withLogTime: false, + quiet: false, + slow: false, +}; + +for (const key of Object.keys(envOption) as (keyof typeof envOption)[]) { + if (process.env['MK_' + key.replace(/[A-Z]/g, letter => `_${letter}`).toUpperCase()]) envOption[key] = true; +} + +if (process.env.NODE_ENV === 'test') envOption.disableClustering = true; +if (process.env.NODE_ENV === 'test') envOption.quiet = true; +if (process.env.NODE_ENV === 'test') envOption.noDaemons = true; + +export { envOption }; diff --git a/src/queue/index.ts b/src/queue/index.ts index c787253ded..1e1d5da5a2 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -1,7 +1,7 @@ import * as httpSignature from 'http-signature'; import config from '@/config/index'; -import { program } from '../argv'; +import { envOption } from '../env'; import processDeliver from './processors/deliver'; import processInbox from './processors/inbox'; @@ -200,7 +200,7 @@ export function createCleanRemoteFilesJob() { } export default function() { - if (!program.onlyServer) { + if (!envOption.onlyServer) { deliverQueue.process(config.deliverJobConcurrency || 128, processDeliver); inboxQueue.process(config.inboxJobConcurrency || 16, processInbox); processDb(dbQueue); diff --git a/src/server/index.ts b/src/server/index.ts index fb4e48c1c6..c891596140 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -20,7 +20,7 @@ import config from '@/config/index'; import apiServer from './api/index'; import { sum } from '@/prelude/array'; import Logger from '@/services/logger'; -import { program } from '../argv'; +import { envOption } from '../env'; import { UserProfiles, Users } from '@/models/index'; import { networkChart } from '@/services/chart/index'; import { genAvatar } from '@/misc/gen-avatar'; @@ -40,7 +40,7 @@ if (!['production', 'test'].includes(process.env.NODE_ENV || '')) { })); // Delay - if (program.slow) { + if (envOption.slow) { app.use(slow({ delay: 3000 })); diff --git a/src/services/logger.ts b/src/services/logger.ts index 229be891e1..8e783e67f6 100644 --- a/src/services/logger.ts +++ b/src/services/logger.ts @@ -2,7 +2,7 @@ import * as cluster from 'cluster'; import * as os from 'os'; import * as chalk from 'chalk'; import * as dateformat from 'dateformat'; -import { program } from '../argv'; +import { envOption } from '../env'; import { getRepository } from 'typeorm'; import { Log } from '@/models/entities/log'; import { genId } from '@/misc/gen-id'; @@ -52,7 +52,7 @@ export default class Logger { } private log(level: Level, message: string, data?: Record | null, important = false, subDomains: Domain[] = [], store = true): void { - if (program.quiet) return; + if (envOption.quiet) return; if (!this.store) store = false; if (level === 'debug') store = false; @@ -80,7 +80,7 @@ export default class Logger { null; let log = `${l} ${worker}\t[${domains.join(' ')}]\t${m}`; - if (program.withLogTime) log = chalk.gray(time) + ' ' + log; + if (envOption.withLogTime) log = chalk.gray(time) + ' ' + log; console.log(important ? chalk.bold(log) : log); @@ -132,7 +132,7 @@ export default class Logger { } public debug(message: string, data?: Record | null, important = false): void { // デバッグ用に使う(開発者に必要だが利用者に不要な情報) - if (process.env.NODE_ENV != 'production' || program.verbose) { + if (process.env.NODE_ENV != 'production' || envOption.verbose) { this.log('debug', message, data, important); } } diff --git a/yarn.lock b/yarn.lock index e999c20552..b5ef779125 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3093,11 +3093,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" - integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== - commander@^2.12.1, commander@^2.19.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" -- cgit v1.2.3-freya From 8b1999dc5b68220a3e142812fadbc42f563a18b1 Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 8 Oct 2021 21:24:53 +0900 Subject: fix(api): (0 , ms_1.default) is not a function --- src/server/api/endpoints/ap/get.ts | 2 +- src/server/api/endpoints/ap/show.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src/server') diff --git a/src/server/api/endpoints/ap/get.ts b/src/server/api/endpoints/ap/get.ts index 2f97a24774..78919f43b0 100644 --- a/src/server/api/endpoints/ap/get.ts +++ b/src/server/api/endpoints/ap/get.ts @@ -2,7 +2,7 @@ import $ from 'cafy'; import define from '../../define'; import Resolver from '@/remote/activitypub/resolver'; import { ApiError } from '../../error'; -import ms from 'ms'; +import * as ms from 'ms'; export const meta = { tags: ['federation'], diff --git a/src/server/api/endpoints/ap/show.ts b/src/server/api/endpoints/ap/show.ts index 32685d44bd..2280d93724 100644 --- a/src/server/api/endpoints/ap/show.ts +++ b/src/server/api/endpoints/ap/show.ts @@ -11,7 +11,7 @@ import { Note } from '@/models/entities/note'; import { User } from '@/models/entities/user'; import { fetchMeta } from '@/misc/fetch-meta'; import { isActor, isPost, getApId } from '@/remote/activitypub/type'; -import ms from 'ms'; +import * as ms from 'ms'; export const meta = { tags: ['federation'], -- cgit v1.2.3-freya From ec05c073217a0c754032ac92de9d91fe03808dd0 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 9 Oct 2021 12:44:19 +0900 Subject: feat: 未読の通知のみ表示する機能 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 ++ locales/ja-JP.yml | 1 + src/client/components/notifications.vue | 11 +++++++++++ src/client/pages/notifications.vue | 22 ++++++++++++++++------ src/server/api/endpoints/i/notifications.ts | 9 +++++++++ 5 files changed, 39 insertions(+), 6 deletions(-) (limited to 'src/server') diff --git a/CHANGELOG.md b/CHANGELOG.md index 215555df72..6550db9803 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,9 @@ - アカウント登録にメールアドレスの設定を必須にするオプション - クライアント: アニメーションを減らす設定をメニューのアニメーションにも適用するように - クライアント: MFM関数構文のサジェストを実装 +- クライアント: 未読の通知のみ表示する機能 - ActivityPub: HTML -> MFMの変換を強化 +- API: i/notifications に unreadOnly オプションを追加 - API: ap系のエンドポイントをログイン必須化+レートリミット追加 - Misskeyのコマンドラインオプションを廃止 - 代わりに環境変数で設定することができます diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 0be87cf2c5..eb72c7ca17 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -792,6 +792,7 @@ unresolved: "未解決" itsOn: "オンになっています" itsOff: "オフになっています" emailRequiredForSignup: "アカウント登録にメールアドレスを必須にする" +unread: "未読" _signup: almostThere: "ほとんど完了です" diff --git a/src/client/components/notifications.vue b/src/client/components/notifications.vue index e91f18a693..8be1e191b9 100644 --- a/src/client/components/notifications.vue +++ b/src/client/components/notifications.vue @@ -48,6 +48,11 @@ export default defineComponent({ required: false, default: null, }, + unreadOnly: { + type: Boolean, + required: false, + default: false, + }, }, data() { @@ -58,6 +63,7 @@ export default defineComponent({ limit: 10, params: () => ({ includeTypes: this.allIncludeTypes || undefined, + unreadOnly: this.unreadOnly, }) }, }; @@ -76,6 +82,11 @@ export default defineComponent({ }, deep: true }, + unreadOnly: { + handler() { + this.reload(); + }, + }, // TODO: vue/vuexのバグか仕様かは不明なものの、プロフィール更新するなどして $i が更新されると、 // mutingNotificationTypes に変化が無くてもこのハンドラーが呼び出され無駄なリロードが発生するのを直す '$i.mutingNotificationTypes': { diff --git a/src/client/pages/notifications.vue b/src/client/pages/notifications.vue index 6cbcc9b8e5..9728b87bf1 100644 --- a/src/client/pages/notifications.vue +++ b/src/client/pages/notifications.vue @@ -2,13 +2,13 @@
- +