summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-05-11 14:39:40 +0900
committersyuilo <Syuilotan@yahoo.co.jp>2021-05-11 14:39:40 +0900
commitf96c60c1a0374aabeecbe82c15d7e3373efed8eb (patch)
tree59b136dff0287f74166b385d6de811d83b1f455a /src
parentMerge branch 'develop' (diff)
parent12.81.0 (diff)
downloadmisskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.gz
misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.tar.bz2
misskey-f96c60c1a0374aabeecbe82c15d7e3373efed8eb.zip
Merge branch 'develop'
Diffstat (limited to 'src')
-rw-r--r--src/client/components/global/ad.vue77
-rw-r--r--src/client/components/media-image.vue2
-rw-r--r--src/client/components/ui/modal.vue6
-rw-r--r--src/client/instance.ts9
-rw-r--r--src/client/pages/instance/ads.vue6
-rw-r--r--src/client/pages/instance/index.vue2
-rw-r--r--src/client/pages/instance/instance.vue6
-rw-r--r--src/client/pages/instance/logs.vue20
-rw-r--r--src/client/pages/instance/queue.chart.vue6
-rw-r--r--src/client/store.ts4
-rw-r--r--src/client/ui/deck/widgets-column.vue5
-rw-r--r--src/client/widgets/federation.vue4
-rw-r--r--src/models/entities/ad.ts6
-rw-r--r--src/queue/index.ts31
-rw-r--r--src/queue/initialize.ts6
-rw-r--r--src/queue/processors/db/delete-drive-files.ts3
-rw-r--r--src/queue/processors/db/export-blocking.ts5
-rw-r--r--src/queue/processors/db/export-following.ts5
-rw-r--r--src/queue/processors/db/export-mute.ts5
-rw-r--r--src/queue/processors/db/export-notes.ts9
-rw-r--r--src/queue/processors/db/export-user-lists.ts5
-rw-r--r--src/queue/processors/db/import-following.ts3
-rw-r--r--src/queue/processors/db/import-user-lists.ts3
-rw-r--r--src/queue/processors/db/index.ts7
-rw-r--r--src/queue/processors/deliver.ts3
-rw-r--r--src/queue/processors/inbox.ts4
-rw-r--r--src/queue/processors/object-storage/clean-remote-files.ts2
-rw-r--r--src/queue/processors/object-storage/delete-file.ts3
-rw-r--r--src/queue/processors/object-storage/index.ts5
-rw-r--r--src/queue/queues.ts9
-rw-r--r--src/queue/types.ts39
-rw-r--r--src/remote/activitypub/kernel/like.ts9
-rw-r--r--src/server/api/endpoints/admin/ad/create.ts4
-rw-r--r--src/server/api/endpoints/admin/ad/update.ts4
-rw-r--r--src/server/api/endpoints/admin/get-index-stats.ts26
-rw-r--r--src/server/api/endpoints/meta.ts3
-rw-r--r--src/services/note/reaction/create.ts12
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;