diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-11 14:39:40 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-05-11 14:39:40 +0900 |
| commit | f96c60c1a0374aabeecbe82c15d7e3373efed8eb (patch) | |
| tree | 59b136dff0287f74166b385d6de811d83b1f455a /src | |
| parent | Merge branch 'develop' (diff) | |
| parent | 12.81.0 (diff) | |
| download | misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.gz misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.bz2 misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.zip | |
Merge branch 'develop'
Diffstat (limited to 'src')
37 files changed, 258 insertions, 100 deletions
diff --git a/src/client/components/global/ad.vue b/src/client/components/global/ad.vue index f88a1d2026..8397b2229e 100644 --- a/src/client/components/global/ad.vue +++ b/src/client/components/global/ad.vue @@ -9,8 +9,9 @@ <div class="menu" v-else> <div class="body"> <div>Ads by {{ host }}</div> - <!--<MkButton>{{ $ts.stopThisAd }}</MkButton>--> - <button class="_textButton" @click="toggleMenu">{{ $ts.close }}</button> + <!--<MkButton class="button" primary>{{ $ts._ad.like }}</MkButton>--> + <MkButton v-if="ad.ratio !== 0" class="button" @click="reduceFrequency">{{ $ts._ad.reduceFrequencyOfThisAd }}</MkButton> + <button class="_textButton" @click="toggleMenu">{{ $ts._ad.back }}</button> </div> </div> </div> @@ -19,9 +20,11 @@ <script lang="ts"> import { defineComponent, ref } from 'vue'; -import { instance } from '@client/instance'; +import { Instance, instance } from '@client/instance'; import { host } from '@client/config'; import MkButton from '@client/components/ui/button.vue'; +import { defaultStore } from '@client/store'; +import * as os from '@client/os'; export default defineComponent({ components: { @@ -45,35 +48,65 @@ export default defineComponent({ showMenu.value = !showMenu.value; }; - let ad = null; + const choseAd = (): Instance['ads'][number] | null => { + if (props.specify) { + return props.specify as Instance['ads'][number]; + } + + const allAds = instance.ads.map(ad => defaultStore.state.mutedAds.includes(ad.id) ? { + ...ad, + ratio: 0 + } : ad); - if (props.specify) { - ad = props.specify; - } else { - let ads = instance.ads.filter(ad => props.prefer.includes(ad.place)); + let ads = allAds.filter(ad => props.prefer.includes(ad.place)); if (ads.length === 0) { - ads = instance.ads.filter(ad => ad.place === 'square'); + ads = allAds.filter(ad => ad.place === 'square'); } - const high = ads.filter(ad => ad.priority === 'high'); - const middle = ads.filter(ad => ad.priority === 'middle'); - const low = ads.filter(ad => ad.priority === 'low'); + const lowPriorityAds = ads.filter(ad => ad.ratio === 0); + ads = ads.filter(ad => ad.ratio !== 0); - if (high.length > 0) { - ad = high[Math.floor(Math.random() * high.length)]; - } else if (middle.length > 0) { - ad = middle[Math.floor(Math.random() * middle.length)]; - } else if (low.length > 0) { - ad = low[Math.floor(Math.random() * low.length)]; + if (ads.length === 0) { + if (lowPriorityAds.length !== 0) { + return lowPriorityAds[Math.floor(Math.random() * lowPriorityAds.length)]; + } else { + return null; + } } - } + + const totalFactor = ads.reduce((a, b) => a + b.ratio, 0); + const r = Math.random() * totalFactor; + + let stackedFactor = 0; + for (const ad of ads) { + if (r >= stackedFactor && r <= stackedFactor + ad.ratio) { + return ad; + } else { + stackedFactor += ad.ratio; + } + } + + return null; + }; + + const chosen = ref(choseAd()); + + const reduceFrequency = () => { + if (chosen.value == null) return; + if (defaultStore.state.mutedAds.includes(chosen.value.id)) return; + defaultStore.push('mutedAds', chosen.value.id); + os.success(); + chosen.value = choseAd(); + showMenu.value = false; + }; return { - ad, + ad: chosen, showMenu, toggleMenu, host, + reduceFrequency, }; } }); @@ -157,6 +190,10 @@ export default defineComponent({ margin: 0 auto; max-width: 400px; border: solid 1px var(--divider); + + > .button { + margin: 8px auto; + } } } } diff --git a/src/client/components/media-image.vue b/src/client/components/media-image.vue index 0573b2592d..267e4debd2 100644 --- a/src/client/components/media-image.vue +++ b/src/client/components/media-image.vue @@ -9,7 +9,6 @@ </div> </div> <div class="gqnyydlz" :style="{ background: color }" v-else> - <i class="fas fa-eye-slash" @click="hide = true"></i> <a :href="image.url" :title="image.name" @@ -18,6 +17,7 @@ <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name" :cover="false"/> <div class="gif" v-if="image.type === 'image/gif'">GIF</div> </a> + <i class="fas fa-eye-slash" @click="hide = true"></i> </div> </template> diff --git a/src/client/components/ui/modal.vue b/src/client/components/ui/modal.vue index 3b11213426..2a4eec4034 100644 --- a/src/client/components/ui/modal.vue +++ b/src/client/components/ui/modal.vue @@ -226,12 +226,12 @@ export default defineComponent({ .mk-modal { > .bg { - z-index: 10000; + z-index: 20000; } > .content:not(.popup) { position: fixed; - z-index: 10000; + z-index: 20000; top: 0; bottom: 0; left: 0; @@ -263,7 +263,7 @@ export default defineComponent({ > .content.popup { position: absolute; - z-index: 10000; + z-index: 20000; &.fixed { position: fixed; diff --git a/src/client/instance.ts b/src/client/instance.ts index bd6b1bd571..024ff1acbd 100644 --- a/src/client/instance.ts +++ b/src/client/instance.ts @@ -3,10 +3,17 @@ import { api } from './os'; // TODO: 他のタブと永続化されたstateを同期 -type Instance = { +export type Instance = { emojis: { category: string; }[]; + ads: { + id: string; + ratio: number; + place: string; + url: string; + imageUrl: string; + }[]; }; const data = localStorage.getItem('instance'); diff --git a/src/client/pages/instance/ads.vue b/src/client/pages/instance/ads.vue index 20747d6f9c..6b536793b7 100644 --- a/src/client/pages/instance/ads.vue +++ b/src/client/pages/instance/ads.vue @@ -15,12 +15,17 @@ <MkRadio v-model="ad.place" value="horizontal">horizontal</MkRadio> <MkRadio v-model="ad.place" value="horizontal-big">horizontal-big</MkRadio> </div> + <!-- <div style="margin: 32px 0;"> {{ $ts.priority }} <MkRadio v-model="ad.priority" value="high">{{ $ts.high }}</MkRadio> <MkRadio v-model="ad.priority" value="middle">{{ $ts.middle }}</MkRadio> <MkRadio v-model="ad.priority" value="low">{{ $ts.low }}</MkRadio> </div> + --> + <MkInput v-model:value="ad.ratio" type="number"> + <span>{{ $ts.ratio }}</span> + </MkInput> <MkInput v-model:value="ad.expiresAt" type="date"> <span>{{ $ts.expiration }}</span> </MkInput> @@ -82,6 +87,7 @@ export default defineComponent({ memo: '', place: 'square', priority: 'middle', + ratio: 1, url: '', imageUrl: null, expiresAt: null, diff --git a/src/client/pages/instance/index.vue b/src/client/pages/instance/index.vue index 974c4345bb..acd46518f5 100644 --- a/src/client/pages/instance/index.vue +++ b/src/client/pages/instance/index.vue @@ -43,6 +43,7 @@ <FormGroup> <template #label>{{ $ts.info }}</template> <FormLink :active="page === 'database'" replace to="/instance/database"><template #icon><i class="fas fa-database"></i></template>{{ $ts.database }}</FormLink> + <FormLink :active="page === 'logs'" replace to="/instance/logs"><template #icon><i class="fas fa-stream"></i></template>{{ $ts.logs }}</FormLink> </FormGroup> </FormBase> </div> @@ -105,6 +106,7 @@ export default defineComponent({ case 'announcements': return defineAsyncComponent(() => import('./announcements.vue')); case 'ads': return defineAsyncComponent(() => import('./ads.vue')); case 'database': return defineAsyncComponent(() => import('./database.vue')); + case 'logs': return defineAsyncComponent(() => import('./logs.vue')); case 'abuses': return defineAsyncComponent(() => import('./abuses.vue')); case 'settings': return defineAsyncComponent(() => import('./settings.vue')); case 'files-settings': return defineAsyncComponent(() => import('./files-settings.vue')); diff --git a/src/client/pages/instance/instance.vue b/src/client/pages/instance/instance.vue index f52e5d866b..75a24bcb80 100644 --- a/src/client/pages/instance/instance.vue +++ b/src/client/pages/instance/instance.vue @@ -123,7 +123,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import XModalWindow from '@client/components/ui/modal-window.vue'; import MkUsersDialog from '@client/components/users-dialog.vue'; @@ -280,7 +280,7 @@ export default defineComponent({ } Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - this.chartInstance = new Chart(this.canvas, { + this.chartInstance = markRaw(new Chart(this.canvas, { type: 'line', data: { labels: new Array(chartLimit).fill(0).map((_, i) => this.getDate(i).toLocaleString()).slice().reverse(), @@ -331,7 +331,7 @@ export default defineComponent({ mode: 'index', } } - }); + })); }, getDate(ago: number) { diff --git a/src/client/pages/instance/logs.vue b/src/client/pages/instance/logs.vue index 7b634259d3..112b0f66d0 100644 --- a/src/client/pages/instance/logs.vue +++ b/src/client/pages/instance/logs.vue @@ -5,13 +5,13 @@ <span>{{ $ts.domain }}</span> </MkInput> <MkSelect v-model:value="logLevel"> - <template #label>{{ $ts.level }}</template> - <option value="all">{{ $ts.levels.all }}</option> - <option value="info">{{ $ts.levels.info }}</option> - <option value="success">{{ $ts.levels.success }}</option> - <option value="warning">{{ $ts.levels.warning }}</option> - <option value="error">{{ $ts.levels.error }}</option> - <option value="debug">{{ $ts.levels.debug }}</option> + <template #label>Level</template> + <option value="all">All</option> + <option value="info">Info</option> + <option value="success">Success</option> + <option value="warning">Warning</option> + <option value="error">Error</option> + <option value="debug">Debug</option> </MkSelect> </div> @@ -45,6 +45,8 @@ export default defineComponent({ MkTextarea, }, + emits: ['info'], + data() { return { [symbols.PAGE_INFO]: { @@ -72,6 +74,10 @@ export default defineComponent({ this.fetchLogs(); }, + mounted() { + this.$emit('info', this[symbols.PAGE_INFO]); + }, + methods: { fetchLogs() { os.api('admin/logs', { diff --git a/src/client/pages/instance/queue.chart.vue b/src/client/pages/instance/queue.chart.vue index 446c979209..0cd983127f 100644 --- a/src/client/pages/instance/queue.chart.vue +++ b/src/client/pages/instance/queue.chart.vue @@ -27,7 +27,7 @@ </template> <script lang="ts"> -import { defineComponent } from 'vue'; +import { defineComponent, markRaw } from 'vue'; import Chart from 'chart.js'; import number from '../../filters/number'; @@ -69,7 +69,7 @@ export default defineComponent({ Chart.defaults.global.defaultFontColor = getComputedStyle(document.documentElement).getPropertyValue('--fg'); - this.chart = new Chart(this.$refs.chart, { + this.chart = markRaw(new Chart(this.$refs.chart, { type: 'line', data: { labels: [], @@ -152,7 +152,7 @@ export default defineComponent({ mode: 'index', } } - }); + })); this.connection.on('stats', this.onStats); this.connection.on('statsLog', this.onStatsLog); diff --git a/src/client/store.ts b/src/client/store.ts index 376135a99d..1a3286554d 100644 --- a/src/client/store.ts +++ b/src/client/store.ts @@ -55,6 +55,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'account', default: [] }, + mutedAds: { + where: 'account', + default: [] as string[] + }, menu: { where: 'deviceAccount', diff --git a/src/client/ui/deck/widgets-column.vue b/src/client/ui/deck/widgets-column.vue index 47d7e7e314..69aaaffa88 100644 --- a/src/client/ui/deck/widgets-column.vue +++ b/src/client/ui/deck/widgets-column.vue @@ -64,11 +64,8 @@ export default defineComponent({ <style lang="scss" scoped> .wtdtxvec { --margin: 8px; + --panelShadow: none; padding: 0 var(--margin); - - ::v-deep(._panel) { - box-shadow: none; - } } </style> diff --git a/src/client/widgets/federation.vue b/src/client/widgets/federation.vue index 3f2e1e691d..8ab7f594a2 100644 --- a/src/client/widgets/federation.vue +++ b/src/client/widgets/federation.vue @@ -114,6 +114,7 @@ export default defineComponent({ overflow: hidden; font-size: 0.9em; color: var(--fg); + padding-right: 8px; > .a { display: block; @@ -129,6 +130,9 @@ export default defineComponent({ font-size: 75%; opacity: 0.7; line-height: $bodyInfoHieght; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } } diff --git a/src/models/entities/ad.ts b/src/models/entities/ad.ts index 3279de29ea..b2fc04c4f0 100644 --- a/src/models/entities/ad.ts +++ b/src/models/entities/ad.ts @@ -23,11 +23,17 @@ export class Ad { }) public place: string; + // 今は使われていないが将来的に活用される可能性はある @Column('varchar', { length: 32, nullable: false }) public priority: string; + @Column('integer', { + default: 1, nullable: false + }) + public ratio: number; + @Column('varchar', { length: 1024, nullable: false }) diff --git a/src/queue/index.ts b/src/queue/index.ts index 201b663799..01dd020d93 100644 --- a/src/queue/index.ts +++ b/src/queue/index.ts @@ -1,7 +1,6 @@ import * as httpSignature from 'http-signature'; import config from '@/config'; -import { User } from '../models/entities/user'; import { program } from '../argv'; import processDeliver from './processors/deliver'; @@ -11,14 +10,9 @@ import procesObjectStorage from './processors/object-storage'; import { queueLogger } from './logger'; import { DriveFile } from '../models/entities/drive-file'; import { getJobInfo } from './get-job-info'; -import { IActivity } from '../remote/activitypub/type'; import { dbQueue, deliverQueue, inboxQueue, objectStorageQueue } from './queues'; - -export type InboxJobData = { - activity: IActivity, - /** HTTP-Signature */ - signature: httpSignature.IParsedSignature -}; +import { ThinUser } from './types'; +import { IActivity } from '@/remote/activitypub/type'; function renderError(e: Error): any { return { @@ -65,8 +59,9 @@ objectStorageQueue .on('error', (job: any, err: Error) => objectStorageLogger.error(`error ${err}`, { job, e: renderError(err) })) .on('stalled', (job) => objectStorageLogger.warn(`stalled id=${job.id}`)); -export function deliver(user: { id: User['id']; host: null; }, content: any, to: any) { +export function deliver(user: ThinUser, content: unknown, to: string | null) { if (content == null) return null; + if (to == null) return null; const data = { user, @@ -85,7 +80,7 @@ export function deliver(user: { id: User['id']; host: null; }, content: any, to: }); } -export function inbox(activity: any, signature: httpSignature.IParsedSignature) { +export function inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { const data = { activity: activity, signature @@ -102,7 +97,7 @@ export function inbox(activity: any, signature: httpSignature.IParsedSignature) }); } -export function createDeleteDriveFilesJob(user: { id: User['id'] }) { +export function createDeleteDriveFilesJob(user: ThinUser) { return dbQueue.add('deleteDriveFiles', { user: user }, { @@ -111,7 +106,7 @@ export function createDeleteDriveFilesJob(user: { id: User['id'] }) { }); } -export function createExportNotesJob(user: { id: User['id'] }) { +export function createExportNotesJob(user: ThinUser) { return dbQueue.add('exportNotes', { user: user }, { @@ -120,7 +115,7 @@ export function createExportNotesJob(user: { id: User['id'] }) { }); } -export function createExportFollowingJob(user: { id: User['id'] }) { +export function createExportFollowingJob(user: ThinUser) { return dbQueue.add('exportFollowing', { user: user }, { @@ -129,7 +124,7 @@ export function createExportFollowingJob(user: { id: User['id'] }) { }); } -export function createExportMuteJob(user: { id: User['id'] }) { +export function createExportMuteJob(user: ThinUser) { return dbQueue.add('exportMute', { user: user }, { @@ -138,7 +133,7 @@ export function createExportMuteJob(user: { id: User['id'] }) { }); } -export function createExportBlockingJob(user: { id: User['id'] }) { +export function createExportBlockingJob(user: ThinUser) { return dbQueue.add('exportBlocking', { user: user }, { @@ -147,7 +142,7 @@ export function createExportBlockingJob(user: { id: User['id'] }) { }); } -export function createExportUserListsJob(user: { id: User['id'] }) { +export function createExportUserListsJob(user: ThinUser) { return dbQueue.add('exportUserLists', { user: user }, { @@ -156,7 +151,7 @@ export function createExportUserListsJob(user: { id: User['id'] }) { }); } -export function createImportFollowingJob(user: { id: User['id'] }, fileId: DriveFile['id']) { +export function createImportFollowingJob(user: ThinUser, fileId: DriveFile['id']) { return dbQueue.add('importFollowing', { user: user, fileId: fileId @@ -166,7 +161,7 @@ export function createImportFollowingJob(user: { id: User['id'] }, fileId: Drive }); } -export function createImportUserListsJob(user: { id: User['id'] }, fileId: DriveFile['id']) { +export function createImportUserListsJob(user: ThinUser, fileId: DriveFile['id']) { return dbQueue.add('importUserLists', { user: user, fileId: fileId diff --git a/src/queue/initialize.ts b/src/queue/initialize.ts index 941fe4bc33..4c0e5f9d87 100644 --- a/src/queue/initialize.ts +++ b/src/queue/initialize.ts @@ -1,8 +1,8 @@ -import * as Queue from 'bull'; +import * as Bull from 'bull'; import config from '@/config'; -export function initialize(name: string, limitPerSec = -1) { - return new Queue(name, { +export function initialize<T>(name: string, limitPerSec = -1) { + return new Bull<T>(name, { redis: { port: config.redis.port, host: config.redis.host, diff --git a/src/queue/processors/db/delete-drive-files.ts b/src/queue/processors/db/delete-drive-files.ts index a2fd9050a9..874623204b 100644 --- a/src/queue/processors/db/delete-drive-files.ts +++ b/src/queue/processors/db/delete-drive-files.ts @@ -4,10 +4,11 @@ import { queueLogger } from '../../logger'; import { deleteFileSync } from '../../../services/drive/delete-file'; import { Users, DriveFiles } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('delete-drive-files'); -export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void> { +export async function deleteDriveFiles(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Deleting drive files of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/export-blocking.ts b/src/queue/processors/db/export-blocking.ts index 9bbc9b2f12..001b50a22c 100644 --- a/src/queue/processors/db/export-blocking.ts +++ b/src/queue/processors/db/export-blocking.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Blockings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-blocking'); -export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { +export async function exportBlocking(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting blocking of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-following.ts b/src/queue/processors/db/export-following.ts index 79df2298d8..c1ccb7af4c 100644 --- a/src/queue/processors/db/export-following.ts +++ b/src/queue/processors/db/export-following.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Followings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-following'); -export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { +export async function exportFollowing(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting following of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-mute.ts b/src/queue/processors/db/export-mute.ts index c10556f882..55d45cc29c 100644 --- a/src/queue/processors/db/export-mute.ts +++ b/src/queue/processors/db/export-mute.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, Mutings } from '../../../models'; import { MoreThan } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-mute'); -export async function exportMute(job: Bull.Job, done: any): Promise<void> { +export async function exportMute(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting mute of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -61,7 +62,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> { } const content = getFullApAccount(u.username, u.host); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-notes.ts b/src/queue/processors/db/export-notes.ts index f76a47aacd..2d09c0d201 100644 --- a/src/queue/processors/db/export-notes.ts +++ b/src/queue/processors/db/export-notes.ts @@ -9,10 +9,11 @@ import { Users, Notes, Polls } from '../../../models'; import { MoreThan } from 'typeorm'; import { Note } from '../../../models/entities/note'; import { Poll } from '../../../models/entities/poll'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-notes'); -export async function exportNotes(job: Bull.Job, done: any): Promise<void> { +export async function exportNotes(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting notes of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -33,7 +34,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { const stream = fs.createWriteStream(path, { flags: 'a' }); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write('[', err => { if (err) { logger.error(err); @@ -72,7 +73,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { poll = await Polls.findOneOrFail({ noteId: note.id }); } const content = JSON.stringify(serialize(note, poll)); - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(exportedNotesCount === 0 ? content : ',\n' + content, err => { if (err) { logger.error(err); @@ -92,7 +93,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> { job.progress(exportedNotesCount / total); } - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(']', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/export-user-lists.ts b/src/queue/processors/db/export-user-lists.ts index b6c527fc78..3f793e064f 100644 --- a/src/queue/processors/db/export-user-lists.ts +++ b/src/queue/processors/db/export-user-lists.ts @@ -8,10 +8,11 @@ import dateFormat = require('dateformat'); import { getFullApAccount } from '@/misc/convert-host'; import { Users, UserLists, UserListJoinings } from '../../../models'; import { In } from 'typeorm'; +import { DbUserJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('export-user-lists'); -export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { +export async function exportUserLists(job: Bull.Job<DbUserJobData>, done: any): Promise<void> { logger.info(`Exporting user lists of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); @@ -45,7 +46,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> { for (const u of users) { const acct = getFullApAccount(u.username, u.host); const content = `${list.name},${acct}`; - await new Promise((res, rej) => { + await new Promise<void>((res, rej) => { stream.write(content + '\n', err => { if (err) { logger.error(err); diff --git a/src/queue/processors/db/import-following.ts b/src/queue/processors/db/import-following.ts index 554337849e..55c0aaa9f9 100644 --- a/src/queue/processors/db/import-following.ts +++ b/src/queue/processors/db/import-following.ts @@ -7,10 +7,11 @@ import { resolveUser } from '../../../remote/resolve-user'; import { downloadTextFile } from '@/misc/download-text-file'; import { isSelfHost, toPuny } from '@/misc/convert-host'; import { Users, DriveFiles } from '../../../models'; +import { DbUserImportJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('import-following'); -export async function importFollowing(job: Bull.Job, done: any): Promise<void> { +export async function importFollowing(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { logger.info(`Importing following of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/import-user-lists.ts b/src/queue/processors/db/import-user-lists.ts index 2fe023da7d..d316b95ff0 100644 --- a/src/queue/processors/db/import-user-lists.ts +++ b/src/queue/processors/db/import-user-lists.ts @@ -8,10 +8,11 @@ import { downloadTextFile } from '@/misc/download-text-file'; import { isSelfHost, toPuny } from '@/misc/convert-host'; import { DriveFiles, Users, UserLists, UserListJoinings } from '../../../models'; import { genId } from '@/misc/gen-id'; +import { DbUserImportJobData } from '@/queue/types'; const logger = queueLogger.createSubLogger('import-user-lists'); -export async function importUserLists(job: Bull.Job, done: any): Promise<void> { +export async function importUserLists(job: Bull.Job<DbUserImportJobData>, done: any): Promise<void> { logger.info(`Importing user lists of ${job.data.user.id} ...`); const user = await Users.findOne(job.data.user.id); diff --git a/src/queue/processors/db/index.ts b/src/queue/processors/db/index.ts index 921cdf7ab1..b56b7bfa2c 100644 --- a/src/queue/processors/db/index.ts +++ b/src/queue/processors/db/index.ts @@ -1,4 +1,5 @@ import * as Bull from 'bull'; +import { DbJobData } from '@/queue/types'; import { deleteDriveFiles } from './delete-drive-files'; import { exportNotes } from './export-notes'; import { exportFollowing } from './export-following'; @@ -17,10 +18,10 @@ const jobs = { exportUserLists, importFollowing, importUserLists -} as any; +} as Record<string, Bull.ProcessCallbackFunction<DbJobData> | Bull.ProcessPromiseFunction<DbJobData>>; -export default function(dbQueue: Bull.Queue) { +export default function(dbQueue: Bull.Queue<DbJobData>) { for (const [k, v] of Object.entries(jobs)) { - dbQueue.process(k, v as any); + dbQueue.process(k, v); } } diff --git a/src/queue/processors/deliver.ts b/src/queue/processors/deliver.ts index b167154fcd..f9c53fc8f1 100644 --- a/src/queue/processors/deliver.ts +++ b/src/queue/processors/deliver.ts @@ -10,6 +10,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { toPuny } from '@/misc/convert-host'; import { Cache } from '@/misc/cache'; import { Instance } from '../../models/entities/instance'; +import { DeliverJobData } from '../types'; const logger = new Logger('deliver'); @@ -17,7 +18,7 @@ let latest: string | null = null; const suspendedHostsCache = new Cache<Instance[]>(1000 * 60 * 60); -export default async (job: Bull.Job) => { +export default async (job: Bull.Job<DeliverJobData>) => { const { host } = new URL(job.data.to); // ブロックしてたら中断 diff --git a/src/queue/processors/inbox.ts b/src/queue/processors/inbox.ts index 7c746eb25d..2ef19777f1 100644 --- a/src/queue/processors/inbox.ts +++ b/src/queue/processors/inbox.ts @@ -10,7 +10,7 @@ import { fetchMeta } from '@/misc/fetch-meta'; import { toPuny, extractDbHost } from '@/misc/convert-host'; import { getApId } from '../../remote/activitypub/type'; import { fetchInstanceMetadata } from '../../services/fetch-instance-metadata'; -import { InboxJobData } from '..'; +import { InboxJobData } from '../types'; import DbResolver from '../../remote/activitypub/db-resolver'; import { resolvePerson } from '../../remote/activitypub/models/person'; import { LdSignature } from '../../remote/activitypub/misc/ld-signature'; @@ -23,7 +23,7 @@ export default async (job: Bull.Job<InboxJobData>): Promise<string> => { const activity = job.data.activity; //#region Log - const info = Object.assign({}, activity); + const info = Object.assign({}, activity) as any; delete info['@context']; logger.debug(JSON.stringify(info, null, 2)); //#endregion diff --git a/src/queue/processors/object-storage/clean-remote-files.ts b/src/queue/processors/object-storage/clean-remote-files.ts index 7b34892e1f..a922755f4d 100644 --- a/src/queue/processors/object-storage/clean-remote-files.ts +++ b/src/queue/processors/object-storage/clean-remote-files.ts @@ -7,7 +7,7 @@ import { MoreThan, Not, IsNull } from 'typeorm'; const logger = queueLogger.createSubLogger('clean-remote-files'); -export default async function cleanRemoteFiles(job: Bull.Job, done: any): Promise<void> { +export default async function cleanRemoteFiles(job: Bull.Job<{}>, done: any): Promise<void> { logger.info(`Deleting cached remote files...`); let deletedCount = 0; diff --git a/src/queue/processors/object-storage/delete-file.ts b/src/queue/processors/object-storage/delete-file.ts index f899df7d2e..31050998af 100644 --- a/src/queue/processors/object-storage/delete-file.ts +++ b/src/queue/processors/object-storage/delete-file.ts @@ -1,7 +1,8 @@ +import { ObjectStorageFileJobData } from '@/queue/types'; import * as Bull from 'bull'; import { deleteObjectStorageFile } from '../../../services/drive/delete-file'; -export default async (job: Bull.Job) => { +export default async (job: Bull.Job<ObjectStorageFileJobData>) => { const key: string = job.data.key; await deleteObjectStorageFile(key); diff --git a/src/queue/processors/object-storage/index.ts b/src/queue/processors/object-storage/index.ts index 33ef665b38..0d9570e179 100644 --- a/src/queue/processors/object-storage/index.ts +++ b/src/queue/processors/object-storage/index.ts @@ -1,14 +1,15 @@ import * as Bull from 'bull'; +import { ObjectStorageJobData } from '@/queue/types'; import deleteFile from './delete-file'; import cleanRemoteFiles from './clean-remote-files'; const jobs = { deleteFile, cleanRemoteFiles, -} as any; +} as Record<string, Bull.ProcessCallbackFunction<ObjectStorageJobData> | Bull.ProcessPromiseFunction<ObjectStorageJobData>>; export default function(q: Bull.Queue) { for (const [k, v] of Object.entries(jobs)) { - q.process(k, 16, v as any); + q.process(k, 16, v); } } diff --git a/src/queue/queues.ts b/src/queue/queues.ts index 819bcf31d8..5e2754b83f 100644 --- a/src/queue/queues.ts +++ b/src/queue/queues.ts @@ -1,7 +1,8 @@ import config from '@/config'; import { initialize as initializeQueue } from './initialize'; +import { DeliverJobData, InboxJobData, DbJobData, ObjectStorageJobData } from './types'; -export const deliverQueue = initializeQueue('deliver', config.deliverJobPerSec || 128); -export const inboxQueue = initializeQueue('inbox', config.inboxJobPerSec || 16); -export const dbQueue = initializeQueue('db'); -export const objectStorageQueue = initializeQueue('objectStorage'); +export const deliverQueue = initializeQueue<DeliverJobData>('deliver', config.deliverJobPerSec || 128); +export const inboxQueue = initializeQueue<InboxJobData>('inbox', config.inboxJobPerSec || 16); +export const dbQueue = initializeQueue<DbJobData>('db'); +export const objectStorageQueue = initializeQueue<ObjectStorageJobData>('objectStorage'); diff --git a/src/queue/types.ts b/src/queue/types.ts new file mode 100644 index 0000000000..a782fc6b97 --- /dev/null +++ b/src/queue/types.ts @@ -0,0 +1,39 @@ +import { DriveFile } from '@/models/entities/drive-file'; +import { User } from '@/models/entities/user'; +import { IActivity } from '@/remote/activitypub/type'; +import * as httpSignature from 'http-signature'; + +export type DeliverJobData = { + /** Actor */ + user: ThinUser; + /** Activity */ + content: unknown; + /** inbox URL to deliver */ + to: string; +}; + +export type InboxJobData = { + activity: IActivity; + signature: httpSignature.IParsedSignature; +}; + +export type DbJobData = DbUserJobData | DbUserImportJobData; + +export type DbUserJobData = { + user: ThinUser; +}; + +export type DbUserImportJobData = { + user: ThinUser; + fileId: DriveFile['id']; +}; + +export type ObjectStorageJobData = ObjectStorageFileJobData | {}; + +export type ObjectStorageFileJobData = { + key: string; +}; + +export type ThinUser = { + id: User['id']; +}; diff --git a/src/remote/activitypub/kernel/like.ts b/src/remote/activitypub/kernel/like.ts index a6f02a1f8f..6ba03e4a48 100644 --- a/src/remote/activitypub/kernel/like.ts +++ b/src/remote/activitypub/kernel/like.ts @@ -11,6 +11,11 @@ export default async (actor: IRemoteUser, activity: ILike) => { await extractEmojis(activity.tag || [], actor.host).catch(() => null); - await create(actor, note, activity._misskey_reaction || activity.content || activity.name); - return `ok`; + return await create(actor, note, activity._misskey_reaction || activity.content || activity.name).catch(e => { + if (e.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') { + return 'skip: already reacted'; + } else { + throw e; + } + }).then(() => 'ok'); }; diff --git a/src/server/api/endpoints/admin/ad/create.ts b/src/server/api/endpoints/admin/ad/create.ts index 7777e95e6e..337114a3fa 100644 --- a/src/server/api/endpoints/admin/ad/create.ts +++ b/src/server/api/endpoints/admin/ad/create.ts @@ -22,6 +22,9 @@ export const meta = { priority: { validator: $.str }, + ratio: { + validator: $.num.int().min(0) + }, expiresAt: { validator: $.num.int() }, @@ -39,6 +42,7 @@ export default define(meta, async (ps) => { url: ps.url, imageUrl: ps.imageUrl, priority: ps.priority, + ratio: ps.ratio, place: ps.place, memo: ps.memo, }); diff --git a/src/server/api/endpoints/admin/ad/update.ts b/src/server/api/endpoints/admin/ad/update.ts index 694af98394..71e6054a88 100644 --- a/src/server/api/endpoints/admin/ad/update.ts +++ b/src/server/api/endpoints/admin/ad/update.ts @@ -29,6 +29,9 @@ export const meta = { priority: { validator: $.str }, + ratio: { + validator: $.num.int().min(0) + }, expiresAt: { validator: $.num.int() }, @@ -52,6 +55,7 @@ export default define(meta, async (ps, me) => { url: ps.url, place: ps.place, priority: ps.priority, + ratio: ps.ratio, memo: ps.memo, imageUrl: ps.imageUrl, expiresAt: new Date(ps.expiresAt), diff --git a/src/server/api/endpoints/admin/get-index-stats.ts b/src/server/api/endpoints/admin/get-index-stats.ts new file mode 100644 index 0000000000..f2b06d0ef2 --- /dev/null +++ b/src/server/api/endpoints/admin/get-index-stats.ts @@ -0,0 +1,26 @@ +import define from '../../define'; +import { getConnection } from 'typeorm'; + +export const meta = { + requireCredential: true as const, + requireModerator: true, + + tags: ['admin'], + + params: { + }, +}; + +export default define(meta, async () => { + const stats = await + getConnection().query(`SELECT * FROM pg_indexes;`) + .then(recs => { + const res = [] as { tablename: string; indexname: string; }[]; + for (const rec of recs) { + res.push(rec); + } + return res; + }); + + return stats; +}); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 5b7292ef16..26d9110ea9 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -509,9 +509,10 @@ export default define(meta, async (ps, me) => { maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH), emojis: await Emojis.packMany(emojis), ads: ads.map(ad => ({ + id: ad.id, url: ad.url, place: ad.place, - priority: ad.priority, + ratio: ad.ratio, imageUrl: ad.imageUrl, })), enableEmail: instance.enableEmail, diff --git a/src/services/note/reaction/create.ts b/src/services/note/reaction/create.ts index e2e7fc54ef..ce6ae08b30 100644 --- a/src/services/note/reaction/create.ts +++ b/src/services/note/reaction/create.ts @@ -13,12 +13,13 @@ import { createNotification } from '../../create-notification'; import deleteReaction from './delete'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error'; import { NoteReaction } from '../../../models/entities/note-reaction'; +import { IdentifiableError } from '@/misc/identifiable-error'; export default async (user: { id: User['id']; host: User['host']; }, note: Note, reaction?: string) => { // TODO: cache reaction = await toDbReaction(reaction, user.host); - let record: NoteReaction = { + const record: NoteReaction = { id: genId(), createdAt: new Date(), noteId: note.id, @@ -31,17 +32,18 @@ export default async (user: { id: User['id']; host: User['host']; }, note: Note, await NoteReactions.insert(record); } catch (e) { if (isDuplicateKeyValueError(e)) { - record = await NoteReactions.findOneOrFail({ + const exists = await NoteReactions.findOneOrFail({ noteId: note.id, userId: user.id, }); - if (record.reaction !== reaction) { + if (exists.reaction !== reaction) { // 別のリアクションがすでにされていたら置き換える await deleteReaction(user, note); + await NoteReactions.insert(record); } else { - // 同じリアクションがすでにされていたら何もしない - return; + // 同じリアクションがすでにされていたらエラー + throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298'); } } else { throw e; |