Merge branch 'develop' into sw-notification-action

This commit is contained in:
tamaina
2021-03-23 21:25:39 +09:00
783 changed files with 5464 additions and 3649 deletions

View File

@@ -1,8 +1,8 @@
import { Antenna } from '../models/entities/antenna';
import { Note } from '../models/entities/note';
import { AntennaNotes, Mutings, Notes } from '../models';
import { genId } from '../misc/gen-id';
import { isMutedUserRelated } from '../misc/is-muted-user-related';
import { genId } from '@/misc/gen-id';
import { isMutedUserRelated } from '@/misc/is-muted-user-related';
import { publishAntennaStream, publishMainStream } from './stream';
import { User } from '../models/entities/user';
@@ -10,7 +10,7 @@ export async function addNoteToAntenna(antenna: Antenna, note: Note, noteUser: U
// 通知しない設定になっているか、自分自身の投稿なら既読にする
const read = !antenna.notify || (antenna.userId === noteUser.id);
AntennaNotes.save({
AntennaNotes.insert({
id: genId(),
antennaId: antenna.id,
noteId: note.id,

View File

@@ -1,4 +1,4 @@
import { publishMainStream } from '../stream';
import { publishMainStream, publishUserEvent } from '../stream';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderFollow from '../../remote/activitypub/renderer/follow';
import renderUndo from '../../remote/activitypub/renderer/undo';
@@ -8,7 +8,7 @@ import renderReject from '../../remote/activitypub/renderer/reject';
import { User } from '../../models/entities/user';
import { Blockings, Users, FollowRequests, Followings } from '../../models';
import { perUserFollowingChart } from '../chart';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
export default async function(blocker: User, blockee: User) {
await Promise.all([
@@ -18,7 +18,7 @@ export default async function(blocker: User, blockee: User) {
unFollow(blockee, blocker)
]);
await Blockings.save({
await Blockings.insert({
id: genId(),
createdAt: new Date(),
blockerId: blocker.id,
@@ -55,7 +55,10 @@ async function cancelRequest(follower: User, followee: User) {
if (Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}).then(packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
});
}
// リモートにフォローリクエストをしていたらUndoFollow送信
@@ -97,7 +100,10 @@ async function unFollow(follower: User, followee: User) {
if (Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}).then(packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
});
}
// リモートにフォローをしていたらUndoFollow送信

View File

@@ -1,7 +1,7 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { User } from '../../../../models/entities/user';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Users } from '../../../../models';
import { name, schema } from '../schemas/active-users';
@@ -17,6 +17,18 @@ export default class ActiveUsersChart extends Chart<ActiveUsersLog> {
return {};
}
@autobind
protected aggregate(logs: ActiveUsersLog[]): ActiveUsersLog {
return {
local: {
users: logs.reduce((a, b) => a.concat(b.local.users), [] as ActiveUsersLog['local']['users']),
},
remote: {
users: logs.reduce((a, b) => a.concat(b.remote.users), [] as ActiveUsersLog['remote']['users']),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<ActiveUsersLog>> {
return {};
@@ -25,11 +37,11 @@ export default class ActiveUsersChart extends Chart<ActiveUsersLog> {
@autobind
public async update(user: User) {
const update: Obj = {
count: 1
users: [user.id]
};
await this.incIfUnique({
await this.inc({
[Users.isLocalUser(user) ? 'local' : 'remote']: update
}, 'users', user.id);
});
}
}

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { DriveFiles } from '../../../../models';
import { Not, IsNull } from 'typeorm';
import { DriveFile } from '../../../../models/entities/drive-file';
@@ -27,6 +27,28 @@ export default class DriveChart extends Chart<DriveLog> {
};
}
@autobind
protected aggregate(logs: DriveLog[]): DriveLog {
return {
local: {
totalCount: logs[0].local.totalCount,
totalSize: logs[0].local.totalSize,
incCount: logs.reduce((a, b) => a + b.local.incCount, 0),
incSize: logs.reduce((a, b) => a + b.local.incSize, 0),
decCount: logs.reduce((a, b) => a + b.local.decCount, 0),
decSize: logs.reduce((a, b) => a + b.local.decSize, 0),
},
remote: {
totalCount: logs[0].remote.totalCount,
totalSize: logs[0].remote.totalSize,
incCount: logs.reduce((a, b) => a + b.remote.incCount, 0),
incSize: logs.reduce((a, b) => a + b.remote.incSize, 0),
decCount: logs.reduce((a, b) => a + b.remote.decCount, 0),
decSize: logs.reduce((a, b) => a + b.remote.decSize, 0),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<DriveLog>> {
const [localCount, remoteCount, localSize, remoteSize] = await Promise.all([

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Instances } from '../../../../models';
import { name, schema } from '../schemas/federation';
@@ -20,6 +20,17 @@ export default class FederationChart extends Chart<FederationLog> {
};
}
@autobind
protected aggregate(logs: FederationLog[]): FederationLog {
return {
instance: {
total: logs[0].instance.total,
inc: logs.reduce((a, b) => a + b.instance.inc, 0),
dec: logs.reduce((a, b) => a + b.instance.dec, 0),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<FederationLog>> {
const [total] = await Promise.all([

View File

@@ -1,7 +1,7 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { User } from '../../../../models/entities/user';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Users } from '../../../../models';
import { name, schema } from '../schemas/hashtag';
@@ -17,6 +17,18 @@ export default class HashtagChart extends Chart<HashtagLog> {
return {};
}
@autobind
protected aggregate(logs: HashtagLog[]): HashtagLog {
return {
local: {
users: logs.reduce((a, b) => a.concat(b.local.users), [] as HashtagLog['local']['users']),
},
remote: {
users: logs.reduce((a, b) => a.concat(b.remote.users), [] as HashtagLog['remote']['users']),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<HashtagLog>> {
return {};
@@ -25,11 +37,11 @@ export default class HashtagChart extends Chart<HashtagLog> {
@autobind
public async update(hashtag: string, user: User) {
const update: Obj = {
count: 1
users: [user.id]
};
await this.incIfUnique({
await this.inc({
[Users.isLocalUser(user) ? 'local' : 'remote']: update
}, 'users', user.id, hashtag);
}, hashtag);
}
}

View File

@@ -1,11 +1,11 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { DriveFiles, Followings, Users, Notes } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file';
import { name, schema } from '../schemas/instance';
import { Note } from '../../../../models/entities/note';
import { toPuny } from '../../../../misc/convert-host';
import { toPuny } from '@/misc/convert-host';
type InstanceLog = SchemaType<typeof schema>;
@@ -36,6 +36,50 @@ export default class InstanceChart extends Chart<InstanceLog> {
};
}
@autobind
protected aggregate(logs: InstanceLog[]): InstanceLog {
return {
requests: {
failed: logs.reduce((a, b) => a + b.requests.failed, 0),
succeeded: logs.reduce((a, b) => a + b.requests.succeeded, 0),
received: logs.reduce((a, b) => a + b.requests.received, 0),
},
notes: {
total: logs[0].notes.total,
inc: logs.reduce((a, b) => a + b.notes.inc, 0),
dec: logs.reduce((a, b) => a + b.notes.dec, 0),
diffs: {
reply: logs.reduce((a, b) => a + b.notes.diffs.reply, 0),
renote: logs.reduce((a, b) => a + b.notes.diffs.renote, 0),
normal: logs.reduce((a, b) => a + b.notes.diffs.normal, 0),
},
},
users: {
total: logs[0].users.total,
inc: logs.reduce((a, b) => a + b.users.inc, 0),
dec: logs.reduce((a, b) => a + b.users.dec, 0),
},
following: {
total: logs[0].following.total,
inc: logs.reduce((a, b) => a + b.following.inc, 0),
dec: logs.reduce((a, b) => a + b.following.dec, 0),
},
followers: {
total: logs[0].followers.total,
inc: logs.reduce((a, b) => a + b.followers.inc, 0),
dec: logs.reduce((a, b) => a + b.followers.dec, 0),
},
drive: {
totalFiles: logs[0].drive.totalFiles,
totalUsage: logs[0].drive.totalUsage,
incFiles: logs.reduce((a, b) => a + b.drive.incFiles, 0),
incUsage: logs.reduce((a, b) => a + b.drive.incUsage, 0),
decFiles: logs.reduce((a, b) => a + b.drive.decFiles, 0),
decUsage: logs.reduce((a, b) => a + b.drive.decUsage, 0),
},
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<InstanceLog>> {
const [

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { name, schema } from '../schemas/network';
type NetworkLog = SchemaType<typeof schema>;
@@ -15,6 +15,17 @@ export default class NetworkChart extends Chart<NetworkLog> {
return {};
}
@autobind
protected aggregate(logs: NetworkLog[]): NetworkLog {
return {
incomingRequests: logs.reduce((a, b) => a + b.incomingRequests, 0),
outgoingRequests: logs.reduce((a, b) => a + b.outgoingRequests, 0),
totalTime: logs.reduce((a, b) => a + b.totalTime, 0),
incomingBytes: logs.reduce((a, b) => a + b.incomingBytes, 0),
outgoingBytes: logs.reduce((a, b) => a + b.outgoingBytes, 0),
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<NetworkLog>> {
return {};

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Notes } from '../../../../models';
import { Not, IsNull } from 'typeorm';
import { Note } from '../../../../models/entities/note';
@@ -25,6 +25,32 @@ export default class NotesChart extends Chart<NotesLog> {
};
}
@autobind
protected aggregate(logs: NotesLog[]): NotesLog {
return {
local: {
total: logs[0].local.total,
inc: logs.reduce((a, b) => a + b.local.inc, 0),
dec: logs.reduce((a, b) => a + b.local.dec, 0),
diffs: {
reply: logs.reduce((a, b) => a + b.local.diffs.reply, 0),
renote: logs.reduce((a, b) => a + b.local.diffs.renote, 0),
normal: logs.reduce((a, b) => a + b.local.diffs.normal, 0),
},
},
remote: {
total: logs[0].remote.total,
inc: logs.reduce((a, b) => a + b.remote.inc, 0),
dec: logs.reduce((a, b) => a + b.remote.dec, 0),
diffs: {
reply: logs.reduce((a, b) => a + b.remote.diffs.reply, 0),
renote: logs.reduce((a, b) => a + b.remote.diffs.renote, 0),
normal: logs.reduce((a, b) => a + b.remote.diffs.normal, 0),
},
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<NotesLog>> {
const [localCount, remoteCount] = await Promise.all([

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { DriveFiles } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file';
import { name, schema } from '../schemas/per-user-drive';
@@ -20,6 +20,18 @@ export default class PerUserDriveChart extends Chart<PerUserDriveLog> {
};
}
@autobind
protected aggregate(logs: PerUserDriveLog[]): PerUserDriveLog {
return {
totalCount: logs[0].totalCount,
totalSize: logs[0].totalSize,
incCount: logs.reduce((a, b) => a + b.incCount, 0),
incSize: logs.reduce((a, b) => a + b.incSize, 0),
decCount: logs.reduce((a, b) => a + b.decCount, 0),
decSize: logs.reduce((a, b) => a + b.decSize, 0),
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<PerUserDriveLog>> {
const [count, size] = await Promise.all([

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Followings, Users } from '../../../../models';
import { Not, IsNull } from 'typeorm';
import { User } from '../../../../models/entities/user';
@@ -35,6 +35,36 @@ export default class PerUserFollowingChart extends Chart<PerUserFollowingLog> {
};
}
@autobind
protected aggregate(logs: PerUserFollowingLog[]): PerUserFollowingLog {
return {
local: {
followings: {
total: logs[0].local.followings.total,
inc: logs.reduce((a, b) => a + b.local.followings.inc, 0),
dec: logs.reduce((a, b) => a + b.local.followings.dec, 0),
},
followers: {
total: logs[0].local.followers.total,
inc: logs.reduce((a, b) => a + b.local.followers.inc, 0),
dec: logs.reduce((a, b) => a + b.local.followers.dec, 0),
},
},
remote: {
followings: {
total: logs[0].remote.followings.total,
inc: logs.reduce((a, b) => a + b.remote.followings.inc, 0),
dec: logs.reduce((a, b) => a + b.remote.followings.dec, 0),
},
followers: {
total: logs[0].remote.followers.total,
inc: logs.reduce((a, b) => a + b.remote.followers.inc, 0),
dec: logs.reduce((a, b) => a + b.remote.followers.dec, 0),
},
},
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<PerUserFollowingLog>> {
const [

View File

@@ -1,7 +1,7 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { User } from '../../../../models/entities/user';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Notes } from '../../../../models';
import { Note } from '../../../../models/entities/note';
import { name, schema } from '../schemas/per-user-notes';
@@ -20,6 +20,20 @@ export default class PerUserNotesChart extends Chart<PerUserNotesLog> {
};
}
@autobind
protected aggregate(logs: PerUserNotesLog[]): PerUserNotesLog {
return {
total: logs[0].total,
inc: logs.reduce((a, b) => a + b.inc, 0),
dec: logs.reduce((a, b) => a + b.dec, 0),
diffs: {
reply: logs.reduce((a, b) => a + b.diffs.reply, 0),
renote: logs.reduce((a, b) => a + b.diffs.renote, 0),
normal: logs.reduce((a, b) => a + b.diffs.normal, 0),
},
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<PerUserNotesLog>> {
const [count] = await Promise.all([

View File

@@ -2,7 +2,7 @@ import autobind from 'autobind-decorator';
import Chart, { DeepPartial } from '../../core';
import { User } from '../../../../models/entities/user';
import { Note } from '../../../../models/entities/note';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Users } from '../../../../models';
import { name, schema } from '../schemas/per-user-reactions';
@@ -18,6 +18,18 @@ export default class PerUserReactionsChart extends Chart<PerUserReactionsLog> {
return {};
}
@autobind
protected aggregate(logs: PerUserReactionsLog[]): PerUserReactionsLog {
return {
local: {
count: logs.reduce((a, b) => a + b.local.count, 0),
},
remote: {
count: logs.reduce((a, b) => a + b.remote.count, 0),
},
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<PerUserReactionsLog>> {
return {};

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { name, schema } from '../schemas/test-grouped';
type TestGroupedLog = SchemaType<typeof schema>;
@@ -21,6 +21,17 @@ export default class TestGroupedChart extends Chart<TestGroupedLog> {
};
}
@autobind
protected aggregate(logs: TestGroupedLog[]): TestGroupedLog {
return {
foo: {
total: logs[0].foo.total,
inc: logs.reduce((a, b) => a + b.foo.inc, 0),
dec: logs.reduce((a, b) => a + b.foo.dec, 0),
},
};
}
@autobind
protected async fetchActual(group: string): Promise<DeepPartial<TestGroupedLog>> {
return {

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { name, schema } from '../schemas/test-unique';
type TestUniqueLog = SchemaType<typeof schema>;
@@ -15,6 +15,13 @@ export default class TestUniqueChart extends Chart<TestUniqueLog> {
return {};
}
@autobind
protected aggregate(logs: TestUniqueLog[]): TestUniqueLog {
return {
foo: logs.reduce((a, b) => a.concat(b.foo), [] as TestUniqueLog['foo']),
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<TestUniqueLog>> {
return {};
@@ -22,8 +29,8 @@ export default class TestUniqueChart extends Chart<TestUniqueLog> {
@autobind
public async uniqueIncrement(key: string) {
await this.incIfUnique({
foo: 1
}, 'foos', key);
await this.inc({
foo: [key]
});
}
}

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { name, schema } from '../schemas/test';
type TestLog = SchemaType<typeof schema>;
@@ -21,6 +21,17 @@ export default class TestChart extends Chart<TestLog> {
};
}
@autobind
protected aggregate(logs: TestLog[]): TestLog {
return {
foo: {
total: logs[0].foo.total,
inc: logs.reduce((a, b) => a + b.foo.inc, 0),
dec: logs.reduce((a, b) => a + b.foo.dec, 0),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<TestLog>> {
return {

View File

@@ -1,6 +1,6 @@
import autobind from 'autobind-decorator';
import Chart, { Obj, DeepPartial } from '../../core';
import { SchemaType } from '../../../../misc/schema';
import { SchemaType } from '@/misc/schema';
import { Users } from '../../../../models';
import { Not, IsNull } from 'typeorm';
import { User } from '../../../../models/entities/user';
@@ -25,6 +25,22 @@ export default class UsersChart extends Chart<UsersLog> {
};
}
@autobind
protected aggregate(logs: UsersLog[]): UsersLog {
return {
local: {
total: logs[0].local.total,
inc: logs.reduce((a, b) => a + b.local.inc, 0),
dec: logs.reduce((a, b) => a + b.local.dec, 0),
},
remote: {
total: logs[0].remote.total,
inc: logs.reduce((a, b) => a + b.remote.inc, 0),
dec: logs.reduce((a, b) => a + b.remote.dec, 0),
},
};
}
@autobind
protected async fetchActual(): Promise<DeepPartial<UsersLog>> {
const [localCount, remoteCount] = await Promise.all([

View File

@@ -1,11 +1,15 @@
export const logSchema = {
/**
* アクティブユーザー
* アクティブユーザー
*/
count: {
type: 'number' as const,
users: {
type: 'array' as const,
optional: false as const, nullable: false as const,
description: 'アクティブユーザー',
description: 'アクティブユーザー',
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
}
},
};

View File

@@ -1,11 +1,15 @@
export const logSchema = {
/**
* 投稿された数
* 投稿したユーザー
*/
count: {
type: 'number' as const,
users: {
type: 'array' as const,
optional: false as const, nullable: false as const,
description: '投稿された数',
description: '投稿したユーザー',
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
}
},
};

View File

@@ -3,9 +3,12 @@ export const schema = {
optional: false as const, nullable: false as const,
properties: {
foo: {
type: 'number' as const,
type: 'array' as const,
optional: false as const, nullable: false as const,
description: ''
items: {
type: 'string' as const,
optional: false as const, nullable: false as const,
}
},
}
};

View File

@@ -7,10 +7,10 @@
import * as nestedProperty from 'nested-property';
import autobind from 'autobind-decorator';
import Logger from '../logger';
import { Schema } from '../../misc/schema';
import { Schema } from '@/misc/schema';
import { EntitySchema, getRepository, Repository, LessThan, Between } from 'typeorm';
import { dateUTC, isTimeSame, isTimeBefore, subtractTime, addTime } from '../../prelude/time';
import { getChartInsertLock } from '../../misc/app-lock';
import { getChartInsertLock } from '@/misc/app-lock';
const logger = new Logger('chart', 'white', process.env.NODE_ENV !== 'test');
@@ -24,8 +24,6 @@ type ArrayValue<T> = {
[P in keyof T]: T[P] extends number ? T[P][] : ArrayValue<T[P]>;
};
type Span = 'day' | 'hour';
type Log = {
id: number;
@@ -38,22 +36,14 @@ type Log = {
* 集計日時のUnixタイムスタンプ(秒)
*/
date: number;
/**
* 集計期間
*/
span: Span;
/**
* ユニークインクリメント用
*/
unique?: Record<string, any>;
};
const camelToSnake = (str: string) => {
return str.replace(/([A-Z])/g, s => '_' + s.charAt(0).toLowerCase());
};
const removeDuplicates = (array: any[]) => Array.from(new Set(array));
/**
* 様々なチャートの管理を司るクラス
*/
@@ -62,10 +52,21 @@ export default abstract class Chart<T extends Record<string, any>> {
private static readonly columnDot = '_';
private name: string;
private queue: {
diff: DeepPartial<T>;
group: string | null;
}[] = [];
public schema: Schema;
protected repository: Repository<Log>;
protected abstract genNewLog(latest: T): DeepPartial<T>;
protected abstract async fetchActual(group: string | null): Promise<DeepPartial<T>>;
/**
* @param logs 日時が新しい方が先頭
*/
protected abstract aggregate(logs: T[]): T;
protected abstract fetchActual(group: string | null): Promise<DeepPartial<T>>;
@autobind
private static convertSchemaToFlatColumnDefinitions(schema: Schema) {
@@ -75,10 +76,15 @@ export default abstract class Chart<T extends Record<string, any>> {
const p = path ? `${path}${this.columnDot}${k}` : k;
if (v.type === 'object') {
flatColumns(v.properties, p);
} else {
} else if (v.type === 'number') {
columns[this.columnPrefix + p] = {
type: 'bigint',
};
} else if (v.type === 'array' && v.items.type === 'string') {
columns[this.columnPrefix + p] = {
type: 'varchar',
array: true,
};
}
}
};
@@ -99,11 +105,11 @@ export default abstract class Chart<T extends Record<string, any>> {
@autobind
private static convertObjectToFlattenColumns(x: Record<string, any>) {
const columns = {} as Record<string, number>;
const columns = {} as Record<string, number | unknown[]>;
const flatten = (x: Obj, path?: string) => {
for (const [k, v] of Object.entries(x)) {
const p = path ? `${path}${this.columnDot}${k}` : k;
if (typeof v === 'object') {
if (typeof v === 'object' && !Array.isArray(v)) {
flatten(v, p);
} else {
columns[this.columnPrefix + p] = v;
@@ -115,14 +121,37 @@ export default abstract class Chart<T extends Record<string, any>> {
}
@autobind
private static convertQuery(x: Record<string, any>) {
private static countUniqueFields(x: Record<string, any>) {
const exec = (x: Obj) => {
const res = {} as Record<string, any>;
for (const [k, v] of Object.entries(x)) {
if (typeof v === 'object' && !Array.isArray(v)) {
res[k] = exec(v);
} else if (Array.isArray(v)) {
res[k] = Array.from(new Set(v)).length;
} else {
res[k] = v;
}
}
return res;
};
return exec(x);
}
@autobind
private static convertQuery(diff: Record<string, number | unknown[]>) {
const query: Record<string, Function> = {};
const columns = Chart.convertObjectToFlattenColumns(x);
for (const [k, v] of Object.entries(columns)) {
if (v > 0) query[k] = () => `"${k}" + ${v}`;
if (v < 0) query[k] = () => `"${k}" - ${Math.abs(v)}`;
for (const [k, v] of Object.entries(diff)) {
if (typeof v === 'number') {
if (v > 0) query[k] = () => `"${k}" + ${v}`;
if (v < 0) query[k] = () => `"${k}" - ${Math.abs(v)}`;
} else if (Array.isArray(v)) {
// TODO: item が文字列以外の場合も対応
// TODO: item をSQLエスケープ
const items = v.map(item => `"${item}"`).join(',');
query[k] = () => `array_cat("${k}", '{${items}}'::varchar[])`;
}
}
return query;
@@ -169,28 +198,14 @@ export default abstract class Chart<T extends Record<string, any>> {
length: 128,
nullable: true
},
span: {
type: 'enum',
enum: ['hour', 'day']
},
unique: {
type: 'jsonb',
default: {}
},
...Chart.convertSchemaToFlatColumnDefinitions(schema)
},
indices: [{
columns: ['date']
}, {
columns: ['span']
}, {
columns: ['group']
}, {
columns: ['span', 'date']
}, {
columns: ['date', 'group']
}, {
columns: ['span', 'date', 'group']
}]
});
}
@@ -200,7 +215,7 @@ export default abstract class Chart<T extends Record<string, any>> {
this.schema = schema;
const entity = Chart.schemaToEntity(name, schema);
const keys = ['span', 'date'];
const keys = ['date'];
if (grouped) keys.push('group');
entity.options.uniques = [{
@@ -220,7 +235,8 @@ export default abstract class Chart<T extends Record<string, any>> {
flatColumns(v.properties, p);
} else {
if (nestedProperty.get(log, p) == null) {
nestedProperty.set(log, p, 0);
const emptyValue = v.type === 'number' ? 0 : [];
nestedProperty.set(log, p, emptyValue);
}
}
}
@@ -230,10 +246,9 @@ export default abstract class Chart<T extends Record<string, any>> {
}
@autobind
private getLatestLog(span: Span, group: string | null = null): Promise<Log | null> {
private getLatestLog(group: string | null = null): Promise<Log | null> {
return this.repository.findOne({
group: group,
span: span
}, {
order: {
date: -1
@@ -242,17 +257,13 @@ export default abstract class Chart<T extends Record<string, any>> {
}
@autobind
private async getCurrentLog(span: Span, group: string | null = null): Promise<Log> {
private async getCurrentLog(group: string | null = null): Promise<Log> {
const [y, m, d, h] = Chart.getCurrentDate();
const current =
span == 'day' ? dateUTC([y, m, d, 0]) :
span == 'hour' ? dateUTC([y, m, d, h]) :
null as never;
const current = dateUTC([y, m, d, h]);
// 現在(今日または今のHour)のログ
// 現在(=今のHour)のログ
const currentLog = await this.repository.findOne({
span: span,
date: Chart.dateToTimestamp(current),
...(group ? { group: group } : {})
});
@@ -271,7 +282,7 @@ export default abstract class Chart<T extends Record<string, any>> {
// * 昨日何もチャートを更新するような出来事がなかった場合は、
// * ログがそもそも作られずドキュメントが存在しないということがあり得るため、
// * 「昨日の」と決め打ちせずに「もっとも最近の」とします
const latest = await this.getLatestLog(span, group);
const latest = await this.getLatestLog(group);
if (latest != null) {
const obj = Chart.convertFlattenColumnsToObject(
@@ -286,17 +297,16 @@ export default abstract class Chart<T extends Record<string, any>> {
// 初期ログデータを作成
data = this.getNewLog(null);
logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): Initial commit created`);
logger.info(`${this.name + (group ? `:${group}` : '')}: Initial commit created`);
}
const date = Chart.dateToTimestamp(current);
const lockKey = `${this.name}:${date}:${group}:${span}`;
const lockKey = `${this.name}:${date}:${group}`;
const unlock = await getChartInsertLock(lockKey);
try {
// ロック内でもう1回チェックする
const currentLog = await this.repository.findOne({
span: span,
date: date,
...(group ? { group: group } : {})
});
@@ -307,12 +317,11 @@ export default abstract class Chart<T extends Record<string, any>> {
// 新規ログ挿入
log = await this.repository.save({
group: group,
span: span,
date: date,
...Chart.convertObjectToFlattenColumns(data)
});
logger.info(`${this.name + (group ? `:${group}` : '')} (${span}): New commit created`);
logger.info(`${this.name + (group ? `:${group}` : '')}: New commit created`);
return log;
} finally {
@@ -321,38 +330,62 @@ export default abstract class Chart<T extends Record<string, any>> {
}
@autobind
protected commit(query: Record<string, Function>, group: string | null = null, uniqueKey?: string, uniqueValue?: string): Promise<any> {
const update = async (log: Log) => {
// ユニークインクリメントの場合、指定のキーに指定の値が既に存在していたら弾く
if (
uniqueKey && log.unique &&
log.unique[uniqueKey] &&
log.unique[uniqueKey].includes(uniqueValue)
) return;
protected commit(diff: DeepPartial<T>, group: string | null = null): void {
this.queue.push({
diff, group,
});
}
// ユニークインクリメントの指定のキーに値を追加
if (uniqueKey && log.unique) {
if (log.unique[uniqueKey]) {
const sql = `jsonb_set("unique", '{${uniqueKey}}', ("unique"->>'${uniqueKey}')::jsonb || '["${uniqueValue}"]'::jsonb)`;
query['unique'] = () => sql;
} else {
const sql = `jsonb_set("unique", '{${uniqueKey}}', '["${uniqueValue}"]')`;
query['unique'] = () => sql;
@autobind
public async save() {
if (this.queue.length === 0) {
logger.info(`${this.name}: Write skipped`);
return;
}
// TODO: 前の時間のログがqueueにあった場合のハンドリング
// 例えば、save が20分ごとに行われるとして、前回行われたのは 01:50 だったとする。
// 次に save が行われるのは 02:10 ということになるが、もし 01:55 に新規ログが queue に追加されたとすると、
// そのログは本来は 01:00~ のログとしてDBに保存されて欲しいのに、02:00~ のログ扱いになってしまう。
// これを回避するための実装は複雑になりそうなため、一旦保留。
const update = async (log: Log) => {
const finalDiffs = {} as Record<string, number | unknown[]>;
for (const diff of this.queue.filter(q => q.group === log.group).map(q => q.diff)) {
const columns = Chart.convertObjectToFlattenColumns(diff);
for (const [k, v] of Object.entries(columns)) {
if (finalDiffs[k] == null) {
finalDiffs[k] = v;
} else {
if (typeof finalDiffs[k] === 'number') {
(finalDiffs[k] as number) += v as number;
} else {
(finalDiffs[k] as unknown[]) = (finalDiffs[k] as unknown[]).concat(v);
}
}
}
}
const query = Chart.convertQuery(finalDiffs);
// ログ更新
await this.repository.createQueryBuilder()
.update()
.set(query)
.where('id = :id', { id: log.id })
.execute();
logger.info(`${this.name + (log.group ? `:${log.group}` : '')}: Updated`);
// TODO: この一連の処理が始まった後に新たにqueueに入ったものは消さないようにする
this.queue = this.queue.filter(q => q.group !== log.group);
};
return Promise.all([
this.getCurrentLog('day', group).then(log => update(log)),
this.getCurrentLog('hour', group).then(log => update(log)),
]);
const groups = removeDuplicates(this.queue.map(log => log.group));
await Promise.all(groups.map(group => this.getCurrentLog(group).then(log => update(log))));
}
@autobind
@@ -367,39 +400,30 @@ export default abstract class Chart<T extends Record<string, any>> {
.execute();
};
return Promise.all([
this.getCurrentLog('day', group).then(log => update(log)),
this.getCurrentLog('hour', group).then(log => update(log)),
]);
return this.getCurrentLog(group).then(log => update(log));
}
@autobind
protected async inc(inc: DeepPartial<T>, group: string | null = null): Promise<void> {
await this.commit(Chart.convertQuery(inc as any), group);
await this.commit(inc, group);
}
@autobind
protected async incIfUnique(inc: DeepPartial<T>, key: string, value: string, group: string | null = null): Promise<void> {
await this.commit(Chart.convertQuery(inc as any), group, key, value);
}
@autobind
public async getChart(span: Span, amount: number, begin: Date | null, group: string | null = null): Promise<ArrayValue<T>> {
const [y, m, d, h, _m, _s, _ms] = begin ? Chart.parseDate(subtractTime(addTime(begin, 1, span), 1)) : Chart.getCurrentDate();
const [y2, m2, d2, h2] = begin ? Chart.parseDate(addTime(begin, 1, span)) : [] as never;
public async getChart(span: 'hour' | 'day', amount: number, cursor: Date | null, group: string | null = null): Promise<ArrayValue<T>> {
const [y, m, d, h, _m, _s, _ms] = cursor ? Chart.parseDate(subtractTime(addTime(cursor, 1, span), 1)) : Chart.getCurrentDate();
const [y2, m2, d2, h2] = cursor ? Chart.parseDate(addTime(cursor, 1, span)) : [] as never;
const lt = dateUTC([y, m, d, h, _m, _s, _ms]);
const gt =
span === 'day' ? subtractTime(begin ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
span === 'hour' ? subtractTime(begin ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
span === 'day' ? subtractTime(cursor ? dateUTC([y2, m2, d2, 0]) : dateUTC([y, m, d, 0]), amount - 1, 'day') :
span === 'hour' ? subtractTime(cursor ? dateUTC([y2, m2, d2, h2]) : dateUTC([y, m, d, h]), amount - 1, 'hour') :
null as never;
// ログ取得
let logs = await this.repository.find({
where: {
group: group,
span: span,
date: Between(Chart.dateToTimestamp(gt), Chart.dateToTimestamp(lt))
},
order: {
@@ -413,7 +437,6 @@ export default abstract class Chart<T extends Record<string, any>> {
// (すくなくともひとつログが無いと隙間埋めできないため)
const recentLog = await this.repository.findOne({
group: group,
span: span
}, {
order: {
date: -1
@@ -430,7 +453,6 @@ export default abstract class Chart<T extends Record<string, any>> {
// (隙間埋めできないため)
const outdatedLog = await this.repository.findOne({
group: group,
span: span,
date: LessThan(Chart.dateToTimestamp(gt))
}, {
order: {
@@ -445,23 +467,56 @@ export default abstract class Chart<T extends Record<string, any>> {
const chart: T[] = [];
// 整形
for (let i = (amount - 1); i >= 0; i--) {
const current =
span === 'day' ? subtractTime(dateUTC([y, m, d, 0]), i, 'day') :
span === 'hour' ? subtractTime(dateUTC([y, m, d, h]), i, 'hour') :
null as never;
if (span === 'hour') {
for (let i = (amount - 1); i >= 0; i--) {
const current = subtractTime(dateUTC([y, m, d, h]), i, 'hour');
const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current));
const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current));
if (log) {
const data = Chart.convertFlattenColumnsToObject(log as Record<string, any>);
chart.unshift(data);
} else {
// 隙間埋め
const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current));
const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record<string, any>) : null;
chart.unshift(this.getNewLog(data));
if (log) {
const data = Chart.convertFlattenColumnsToObject(log as Record<string, any>);
chart.unshift(Chart.countUniqueFields(data));
} else {
// 隙間埋め
const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current));
const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record<string, any>) : null;
chart.unshift(Chart.countUniqueFields(this.getNewLog(data)));
}
}
} else if (span === 'day') {
const logsForEachDays: T[][] = [];
let currentDay = -1;
let currentDayIndex = -1;
for (let i = ((amount - 1) * 24) + h; i >= 0; i--) {
const current = subtractTime(dateUTC([y, m, d, h]), i, 'hour');
const _currentDay = Chart.parseDate(current)[2];
if (currentDay != _currentDay) currentDayIndex++;
currentDay = _currentDay;
const log = logs.find(l => isTimeSame(new Date(l.date * 1000), current));
if (log) {
if (logsForEachDays[currentDayIndex]) {
logsForEachDays[currentDayIndex].unshift(Chart.convertFlattenColumnsToObject(log));
} else {
logsForEachDays[currentDayIndex] = [Chart.convertFlattenColumnsToObject(log)];
}
} else {
// 隙間埋め
const latest = logs.find(l => isTimeBefore(new Date(l.date * 1000), current));
const data = latest ? Chart.convertFlattenColumnsToObject(latest as Record<string, any>) : null;
const newLog = this.getNewLog(data);
if (logsForEachDays[currentDayIndex]) {
logsForEachDays[currentDayIndex].unshift(newLog);
} else {
logsForEachDays[currentDayIndex] = [newLog];
}
}
}
for (const logs of logsForEachDays) {
const log = this.aggregate(logs);
chart.unshift(Chart.countUniqueFields(log));
}
}
@@ -473,20 +528,19 @@ export default abstract class Chart<T extends Record<string, any>> {
* { foo: [1, 2, 3], bar: [5, 6, 7] }
* にする
*/
const dive = (x: Obj, path?: string) => {
const compact = (x: Obj, path?: string) => {
for (const [k, v] of Object.entries(x)) {
const p = path ? `${path}.${k}` : k;
if (typeof v == 'object') {
dive(v, p);
if (typeof v === 'object' && !Array.isArray(v)) {
compact(v, p);
} else {
const values = chart.map(s => nestedProperty.get(s, p))
.map(v => parseInt(v, 10)); // TypeORMのバグ()で何故か数値カラムの値が文字列型になっているので数値に戻す
const values = chart.map(s => nestedProperty.get(s, p));
nestedProperty.set(res, p, values);
}
}
};
dive(chart[0]);
compact(chart[0]);
return res;
}

View File

@@ -10,6 +10,7 @@ import PerUserReactionsChart from './charts/classes/per-user-reactions';
import HashtagChart from './charts/classes/hashtag';
import PerUserFollowingChart from './charts/classes/per-user-following';
import PerUserDriveChart from './charts/classes/per-user-drive';
import { beforeShutdown } from '@/misc/before-shutdown';
export const federationChart = new FederationChart();
export const notesChart = new NotesChart();
@@ -23,3 +24,27 @@ export const perUserReactionsChart = new PerUserReactionsChart();
export const hashtagChart = new HashtagChart();
export const perUserFollowingChart = new PerUserFollowingChart();
export const perUserDriveChart = new PerUserDriveChart();
const charts = [
federationChart,
notesChart,
usersChart,
networkChart,
activeUsersChart,
instanceChart,
perUserNotesChart,
driveChart,
perUserReactionsChart,
hashtagChart,
perUserFollowingChart,
perUserDriveChart,
];
// 20分おきにメモリ情報をDBに書き込み
setInterval(() => {
for (const chart of charts) {
chart.save();
}
}, 1000 * 60 * 20);
beforeShutdown(() => Promise.all(charts.map(chart => chart.save())));

View File

@@ -1,7 +1,7 @@
import { publishMainStream } from './stream';
import { pushNotification } from './push-notification';
import { Notifications, Mutings, UserProfiles } from '../models';
import { genId } from '../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { User } from '../models/entities/user';
import { Notification } from '../models/entities/notification';
import { sendEmailNotification } from './send-email-notification';
@@ -30,7 +30,7 @@ export async function createNotification(
...data
} as Partial<Notification>);
const packed = await Notifications.pack(notification);
const packed = await Notifications.pack(notification, {});
// Publish notification event
publishMainStream(notifieeId, 'notification', packed);

View File

@@ -1,11 +1,11 @@
import * as bcrypt from 'bcryptjs';
import { v4 as uuid } from 'uuid';
import generateNativeUserToken from '../server/api/common/generate-native-user-token';
import { genRsaKeyPair } from '../misc/gen-key-pair';
import { genRsaKeyPair } from '@/misc/gen-key-pair';
import { User } from '../models/entities/user';
import { UserProfile } from '../models/entities/user-profile';
import { getConnection } from 'typeorm';
import { genId } from '../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { UserKeypair } from '../models/entities/user-keypair';
import { UsedUsername } from '../models/entities/used-username';

View File

@@ -4,19 +4,19 @@ import { v4 as uuid } from 'uuid';
import { publishMainStream, publishDriveStream } from '../stream';
import { deleteFile } from './delete-file';
import { fetchMeta } from '../../misc/fetch-meta';
import { fetchMeta } from '@/misc/fetch-meta';
import { GenerateVideoThumbnail } from './generate-video-thumbnail';
import { driveLogger } from './logger';
import { IImage, convertSharpToJpeg, convertSharpToWebp, convertSharpToPng, convertSharpToPngOrJpeg } from './image-processor';
import { contentDisposition } from '../../misc/content-disposition';
import { getFileInfo } from '../../misc/get-file-info';
import { contentDisposition } from '@/misc/content-disposition';
import { getFileInfo } from '@/misc/get-file-info';
import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models';
import { InternalStorage } from './internal-storage';
import { DriveFile } from '../../models/entities/drive-file';
import { IRemoteUser, User } from '../../models/entities/user';
import { driveChart, perUserDriveChart, instanceChart } from '../chart';
import { genId } from '../../misc/gen-id';
import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error';
import { genId } from '@/misc/gen-id';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
import * as S3 from 'aws-sdk/clients/s3';
import { getS3 } from './s3';
import * as sharp from 'sharp';

View File

@@ -3,14 +3,14 @@ import { InternalStorage } from './internal-storage';
import { DriveFiles, Instances, Notes, Users } from '../../models';
import { driveChart, perUserDriveChart, instanceChart } from '../chart';
import { createDeleteObjectStorageFileJob } from '../../queue';
import { fetchMeta } from '../../misc/fetch-meta';
import { fetchMeta } from '@/misc/fetch-meta';
import { getS3 } from './s3';
import { v4 as uuid } from 'uuid';
import { Note } from '../../models/entities/note';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderDelete from '../../remote/activitypub/renderer/delete';
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
import config from '../../config';
import config from '@/config';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
import { Brackets } from 'typeorm';
import { deliverToRelays } from '../relay';

View File

@@ -1,6 +1,6 @@
import * as fs from 'fs';
import * as Path from 'path';
import config from '../../config';
import config from '@/config';
export class InternalStorage {
private static readonly path = Path.resolve(__dirname, '../../../files');

View File

@@ -1,6 +1,6 @@
import * as S3 from 'aws-sdk/clients/s3';
import { Meta } from '../../models/entities/meta';
import { getAgentByUrl } from '../../misc/fetch';
import { getAgentByUrl } from '@/misc/fetch';
export function getS3(meta: Meta) {
const u = meta.objectStorageEndpoint != null

View File

@@ -1,8 +1,8 @@
import create from './add-file';
import { User } from '../../models/entities/user';
import { driveLogger } from './logger';
import { createTemp } from '../../misc/create-temp';
import { downloadUrl } from '../../misc/download-url';
import { createTemp } from '@/misc/create-temp';
import { downloadUrl } from '@/misc/download-url';
import { DriveFolder } from '../../models/entities/drive-folder';
import { DriveFile } from '../../models/entities/drive-file';
import { DriveFiles } from '../../models';

View File

@@ -1,9 +1,9 @@
import { DOMWindow, JSDOM } from 'jsdom';
import fetch from 'node-fetch';
import { getJson, getHtml, getAgentByUrl } from '../misc/fetch';
import { getJson, getHtml, getAgentByUrl } from '@/misc/fetch';
import { Instance } from '../models/entities/instance';
import { Instances } from '../models';
import { getFetchInstanceMetadataLock } from '../misc/app-lock';
import { getFetchInstanceMetadataLock } from '@/misc/app-lock';
import Logger from './logger';
import { URL } from 'url';

View File

@@ -1,4 +1,4 @@
import { publishMainStream } from '../stream';
import { publishMainStream, publishUserEvent } from '../stream';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderFollow from '../../remote/activitypub/renderer/follow';
import renderAccept from '../../remote/activitypub/renderer/accept';
@@ -7,13 +7,13 @@ import { deliver } from '../../queue';
import createFollowRequest from './requests/create';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import Logger from '../logger';
import { IdentifiableError } from '../../misc/identifiable-error';
import { IdentifiableError } from '@/misc/identifiable-error';
import { User } from '../../models/entities/user';
import { Followings, Users, FollowRequests, Blockings, Instances, UserProfiles } from '../../models';
import { instanceChart, perUserFollowingChart } from '../chart';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { createNotification } from '../create-notification';
import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
const logger = new Logger('following/create');
@@ -22,7 +22,7 @@ export async function insertFollowingDoc(followee: User, follower: User) {
let alreadyFollowed = false;
await Followings.save({
await Followings.insert({
id: genId(),
createdAt: new Date(),
followerId: follower.id,
@@ -88,12 +88,15 @@ export async function insertFollowingDoc(followee: User, follower: User) {
if (Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'follow', packed));
}).then(packed => {
publishUserEvent(follower.id, 'follow', packed);
publishMainStream(follower.id, 'follow', packed);
});
}
// Publish followed event
if (Users.isLocalUser(followee)) {
Users.pack(follower, followee).then(packed => publishMainStream(followee.id, 'followed', packed)),
Users.pack(follower, followee).then(packed => publishMainStream(followee.id, 'followed', packed));
// 通知を作成
createNotification(followee.id, 'follow', {

View File

@@ -1,4 +1,4 @@
import { publishMainStream } from '../stream';
import { publishMainStream, publishUserEvent } from '../stream';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderFollow from '../../remote/activitypub/renderer/follow';
import renderUndo from '../../remote/activitypub/renderer/undo';
@@ -30,7 +30,10 @@ export default async function(follower: User, followee: User, silent = false) {
if (!silent && Users.isLocalUser(follower)) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}).then(packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
});
}
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {

View File

@@ -6,7 +6,7 @@ import { publishMainStream } from '../../stream';
import { insertFollowingDoc } from '../create';
import { User, ILocalUser } from '../../../models/entities/user';
import { FollowRequests, Users } from '../../../models';
import { IdentifiableError } from '../../../misc/identifiable-error';
import { IdentifiableError } from '@/misc/identifiable-error';
export default async function(followee: User, follower: User) {
const request = await FollowRequests.findOne({

View File

@@ -3,7 +3,7 @@ import renderFollow from '../../../remote/activitypub/renderer/follow';
import renderUndo from '../../../remote/activitypub/renderer/undo';
import { deliver } from '../../../queue';
import { publishMainStream } from '../../stream';
import { IdentifiableError } from '../../../misc/identifiable-error';
import { IdentifiableError } from '@/misc/identifiable-error';
import { User, ILocalUser } from '../../../models/entities/user';
import { Users, FollowRequests } from '../../../models';

View File

@@ -4,7 +4,7 @@ import renderFollow from '../../../remote/activitypub/renderer/follow';
import { deliver } from '../../../queue';
import { User } from '../../../models/entities/user';
import { Blockings, FollowRequests, Users } from '../../../models';
import { genId } from '../../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { createNotification } from '../../create-notification';
export default async function(follower: User, followee: User, requestId?: string) {

View File

@@ -2,7 +2,7 @@ import { renderActivity } from '../../../remote/activitypub/renderer';
import renderFollow from '../../../remote/activitypub/renderer/follow';
import renderReject from '../../../remote/activitypub/renderer/reject';
import { deliver } from '../../../queue';
import { publishMainStream } from '../../stream';
import { publishMainStream, publishUserEvent } from '../../stream';
import { User, ILocalUser } from '../../../models/entities/user';
import { Users, FollowRequests, Followings } from '../../../models';
import { decrementFollowing } from '../delete';
@@ -39,5 +39,8 @@ export default async function(followee: User, follower: User) {
Users.pack(followee, follower, {
detail: true
}).then(packed => publishMainStream(follower.id, 'unfollow', packed));
}).then(packed => {
publishUserEvent(follower.id, 'unfollow', packed);
publishMainStream(follower.id, 'unfollow', packed);
});
}

View File

@@ -1,13 +1,13 @@
import config from '../../config';
import config from '@/config';
import renderAdd from '../../remote/activitypub/renderer/add';
import renderRemove from '../../remote/activitypub/renderer/remove';
import { renderActivity } from '../../remote/activitypub/renderer';
import { IdentifiableError } from '../../misc/identifiable-error';
import { IdentifiableError } from '@/misc/identifiable-error';
import { User } from '../../models/entities/user';
import { Note } from '../../models/entities/note';
import { Notes, UserNotePinings, Users } from '../../models';
import { UserNotePining } from '../../models/entities/user-note-pining';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { deliverToFollowers } from '../../remote/activitypub/deliver-manager';
import { deliverToRelays } from '../relay';
@@ -37,7 +37,7 @@ export async function addPinned(user: User, noteId: Note['id']) {
throw new IdentifiableError('23f0cf4e-59a3-4276-a91d-61a5891c1514', 'That note has already been pinned.');
}
await UserNotePinings.save({
await UserNotePinings.insert({
id: genId(),
createdAt: new Date(),
userId: user.id,

View File

@@ -1,9 +1,9 @@
import { ILocalUser } from '../models/entities/user';
import { ModerationLogs } from '../models';
import { genId } from '../misc/gen-id';
import { genId } from '@/misc/gen-id';
export async function insertModerationLog(moderator: ILocalUser, type: string, info?: Record<string, any>) {
await ModerationLogs.save({
await ModerationLogs.insert({
id: genId(),
createdAt: new Date(),
userId: moderator.id,

View File

@@ -1,17 +1,27 @@
import { createSystemUser } from './create-system-user';
import { ILocalUser } from '../models/entities/user';
import { Users } from '../models';
import { Cache } from '@/misc/cache';
const ACTOR_USERNAME = 'instance.actor' as const;
const cache = new Cache<ILocalUser>(Infinity);
export async function getInstanceActor(): Promise<ILocalUser> {
const cached = cache.get(null);
if (cached) return cached;
const user = await Users.findOne({
host: null,
username: ACTOR_USERNAME
});
}) as ILocalUser | undefined;
if (user) return user as ILocalUser;
const created = await createSystemUser(ACTOR_USERNAME);
return created as ILocalUser;
if (user) {
cache.set(null, user);
return user;
} else {
const created = await createSystemUser(ACTOR_USERNAME) as ILocalUser;
cache.set(null, created);
return created;
}
}

View File

@@ -5,8 +5,8 @@ import * as dateformat from 'dateformat';
import { program } from '../argv';
import { getRepository } from 'typeorm';
import { Log } from '../models/entities/log';
import { genId } from '../misc/gen-id';
import config from '../config';
import { genId } from '@/misc/gen-id';
import config from '@/config';
const SyslogPro = require('syslog-pro');

View File

@@ -2,7 +2,7 @@ import { User } from '../../models/entities/user';
import { UserGroup } from '../../models/entities/user-group';
import { DriveFile } from '../../models/entities/drive-file';
import { MessagingMessages, UserGroupJoinings, Mutings, Users } from '../../models';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { MessagingMessage } from '../../models/entities/messaging-message';
import { publishMessagingStream, publishMessagingIndexStream, publishMainStream, publishGroupMessagingStream } from '../stream';
import { pushNotification } from '../push-notification';
@@ -14,7 +14,7 @@ import { renderActivity } from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
export async function createMessage(user: User, recipientUser: User | undefined, recipientGroup: UserGroup | undefined, text: string | undefined, file: DriveFile | null, uri?: string) {
const message = await MessagingMessages.save({
const message = {
id: genId(),
createdAt: new Date(),
fileId: file ? file.id : null,
@@ -25,7 +25,9 @@ export async function createMessage(user: User, recipientUser: User | undefined,
isRead: false,
reads: [] as any[],
uri
} as MessagingMessage);
} as MessagingMessage;
await MessagingMessages.insert(message);
const messageObj = await MessagingMessages.pack(message);

View File

@@ -1,4 +1,4 @@
import config from '../../config';
import config from '@/config';
import { MessagingMessages, Users } from '../../models';
import { MessagingMessage } from '../../models/entities/messaging-message';
import { publishGroupMessagingStream, publishMessagingStream } from '../stream';

View File

@@ -7,32 +7,33 @@ import renderAnnounce from '../../remote/activitypub/renderer/announce';
import { renderActivity } from '../../remote/activitypub/renderer';
import { parse } from '../../mfm/parse';
import { resolveUser } from '../../remote/resolve-user';
import config from '../../config';
import config from '@/config';
import { updateHashtags } from '../update-hashtag';
import { concat } from '../../prelude/array';
import insertNoteUnread from './unread';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags';
import extractMentions from '@/misc/extract-mentions';
import extractEmojis from '@/misc/extract-emojis';
import extractHashtags from '@/misc/extract-hashtags';
import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings } from '../../models';
import { DriveFile } from '../../models/entities/drive-file';
import { App } from '../../models/entities/app';
import { Not, getConnection, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart';
import { Poll, IPoll } from '../../models/entities/poll';
import { createNotification } from '../create-notification';
import { isDuplicateKeyValueError } from '../../misc/is-duplicate-key-value-error';
import { checkHitAntenna } from '../../misc/check-hit-antenna';
import { checkWordMute } from '../../misc/check-word-mute';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error';
import { checkHitAntenna } from '@/misc/check-hit-antenna';
import { checkWordMute } from '@/misc/check-word-mute';
import { addNoteToAntenna } from '../add-note-to-antenna';
import { countSameRenotes } from '../../misc/count-same-renotes';
import { countSameRenotes } from '@/misc/count-same-renotes';
import { deliverToRelays } from '../relay';
import { Channel } from '../../models/entities/channel';
import { normalizeForSearch } from '../../misc/normalize-for-search';
import { normalizeForSearch } from '@/misc/normalize-for-search';
import { getAntennas } from '@/misc/antenna-cache';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@@ -241,13 +242,14 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
incNotesCountOfUser(user);
// Word mute
// TODO: cache
UserProfiles.find({
enableWordMute: true
}).then(us => {
for (const u of us) {
checkWordMute(note, { id: u.userId }, u.mutedWords).then(shouldMute => {
if (shouldMute) {
MutedNotes.save({
MutedNotes.insert({
id: genId(),
userId: u.userId,
noteId: note.id,
@@ -259,21 +261,19 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
});
// Antenna
Antennas.find().then(async antennas => {
const followings = await Followings.createQueryBuilder('following')
.andWhere(`following.followeeId = :userId`, { userId: note.userId })
.getMany();
const followers = followings.map(f => f.followerId);
for (const antenna of antennas) {
checkHitAntenna(antenna, note, user, followers).then(hit => {
if (hit) {
addNoteToAntenna(antenna, note, user);
}
});
}
});
Followings.createQueryBuilder('following')
.andWhere(`following.followeeId = :userId`, { userId: note.userId })
.getMany()
.then(async followings => {
const followers = followings.map(f => f.followerId);
for (const antenna of (await getAntennas())) {
checkHitAntenna(antenna, note, user, followers).then(hit => {
if (hit) {
addNoteToAntenna(antenna, note, user);
}
});
}
});
// Channel
if (note.channelId) {
@@ -444,8 +444,13 @@ async function renderNoteOrRenoteActivity(data: Option, note: Note) {
}
function incRenoteCount(renote: Note) {
Notes.increment({ id: renote.id }, 'renoteCount', 1);
Notes.increment({ id: renote.id }, 'score', 1);
Notes.createQueryBuilder().update()
.set({
renoteCount: () => '"renoteCount" + 1',
score: () => '"score" + 1'
})
.where('id = :id', { id: renote.id })
.execute();
}
async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) {
@@ -525,7 +530,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
await Notes.insert(insert);
}
return await Notes.findOneOrFail(insert.id);
return insert;
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {
@@ -594,10 +599,13 @@ function saveReply(reply: Note, note: Note) {
}
function incNotesCountOfUser(user: User) {
Users.increment({ id: user.id }, 'notesCount', 1);
Users.update({ id: user.id }, {
updatedAt: new Date()
});
Users.createQueryBuilder().update()
.set({
updatedAt: new Date(),
notesCount: () => '"notesCount" + 1'
})
.where('id = :id', { id: user.id })
.execute();
}
async function extractMentionedUsers(user: User, tokens: ReturnType<typeof parse>): Promise<User[]> {

View File

@@ -4,14 +4,14 @@ import renderAnnounce from '../../remote/activitypub/renderer/announce';
import renderUndo from '../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../remote/activitypub/renderer';
import renderTombstone from '../../remote/activitypub/renderer/tombstone';
import config from '../../config';
import config from '@/config';
import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
import { Note, IMentionedRemoteUsers } from '../../models/entities/note';
import { Notes, Users, Instances } from '../../models';
import { notesChart, perUserNotesChart, instanceChart } from '../chart';
import { deliverToFollowers, deliverToUser } from '../../remote/activitypub/deliver-manager';
import { countSameRenotes } from '../../misc/count-same-renotes';
import { countSameRenotes } from '@/misc/count-same-renotes';
import { deliverToRelays } from '../relay';
import { Brackets, In } from 'typeorm';

View File

@@ -3,7 +3,7 @@ import { User } from '../../../models/entities/user';
import { Note } from '../../../models/entities/note';
import { PollVotes, NoteWatchings, Polls } from '../../../models';
import { Not } from 'typeorm';
import { genId } from '../../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { createNotification } from '../../create-notification';
export default async function(user: User, note: Note, choice: number) {
@@ -29,7 +29,7 @@ export default async function(user: User, note: Note, choice: number) {
}
// Create vote
await PollVotes.save({
await PollVotes.insert({
id: genId(),
createdAt: new Date(),
noteId: note.id,

View File

@@ -2,54 +2,62 @@ import { publishNoteStream } from '../../stream';
import { renderLike } from '../../../remote/activitypub/renderer/like';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
import { renderActivity } from '../../../remote/activitypub/renderer';
import { toDbReaction, decodeReaction } from '../../../misc/reaction-lib';
import { toDbReaction, decodeReaction } from '@/misc/reaction-lib';
import { User, IRemoteUser } from '../../../models/entities/user';
import { Note } from '../../../models/entities/note';
import { NoteReactions, Users, NoteWatchings, Notes, Emojis } from '../../../models';
import { Not } from 'typeorm';
import { perUserReactionsChart } from '../../chart';
import { genId } from '../../../misc/gen-id';
import { genId } from '@/misc/gen-id';
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';
export default async (user: User, note: Note, reaction?: string) => {
// TODO: cache
reaction = await toDbReaction(reaction, user.host);
const exist = await NoteReactions.findOne({
noteId: note.id,
userId: user.id,
});
if (exist) {
if (exist.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await deleteReaction(user, note);
} else {
// 同じリアクションがすでにされていたら何もしない
return;
}
}
// Create reaction
const inserted = await NoteReactions.save({
let record: NoteReaction = {
id: genId(),
createdAt: new Date(),
noteId: note.id,
userId: user.id,
reaction
});
};
// Create reaction
try {
await NoteReactions.insert(record);
} catch (e) {
if (isDuplicateKeyValueError(e)) {
record = await NoteReactions.findOneOrFail({
noteId: note.id,
userId: user.id,
});
if (record.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await deleteReaction(user, note);
} else {
// 同じリアクションがすでにされていたら何もしない
return;
}
} else {
throw e;
}
}
// Increment reactions count
const sql = `jsonb_set("reactions", '{${reaction}}', (COALESCE("reactions"->>'${reaction}', '0')::int + 1)::text::jsonb)`;
await Notes.createQueryBuilder().update()
.set({
reactions: () => sql,
score: () => '"score" + 1'
})
.where('id = :id', { id: note.id })
.execute();
Notes.increment({ id: note.id }, 'score', 1);
perUserReactionsChart.update(user, note);
// カスタム絵文字リアクションだったら絵文字情報も送る
@@ -101,7 +109,7 @@ export default async (user: User, note: Note, reaction?: string) => {
//#region 配信
if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(await renderLike(inserted, note));
const content = renderActivity(await renderLike(record, note));
const dm = new DeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await Users.findOne(note.userId);

View File

@@ -3,11 +3,11 @@ import { renderLike } from '../../../remote/activitypub/renderer/like';
import renderUndo from '../../../remote/activitypub/renderer/undo';
import { renderActivity } from '../../../remote/activitypub/renderer';
import DeliverManager from '../../../remote/activitypub/deliver-manager';
import { IdentifiableError } from '../../../misc/identifiable-error';
import { IdentifiableError } from '@/misc/identifiable-error';
import { User, IRemoteUser } from '../../../models/entities/user';
import { Note } from '../../../models/entities/note';
import { NoteReactions, Users, Notes } from '../../../models';
import { decodeReaction } from '../../../misc/reaction-lib';
import { decodeReaction } from '@/misc/reaction-lib';
export default async (user: User, note: Note) => {
// if already unreacted

View File

@@ -1,97 +1,122 @@
import { publishMainStream } from '../stream';
import { Note } from '../../models/entities/note';
import { User } from '../../models/entities/user';
import { NoteUnreads, Antennas, AntennaNotes, Users } from '../../models';
import { Not, IsNull } from 'typeorm';
import { NoteUnreads, AntennaNotes, Users, Followings, ChannelFollowings } from '../../models';
import { Not, IsNull, In } from 'typeorm';
import { Channel } from '../../models/entities/channel';
import { checkHitAntenna } from '@/misc/check-hit-antenna';
import { getAntennas } from '@/misc/antenna-cache';
import { PackedNote } from '../../models/repositories/note';
/**
* Mark a note as read
* Mark notes as read
*/
export default async function(
userId: User['id'],
noteId: Note['id']
notes: (Note | PackedNote)[],
info?: {
following: Set<User['id']>;
followingChannels: Set<Channel['id']>;
}
) {
async function careNoteUnreads() {
const exist = await NoteUnreads.findOne({
userId: userId,
noteId: noteId,
});
const following = info?.following ? info.following : new Set<string>((await Followings.find({
where: {
followerId: userId
},
select: ['followeeId']
})).map(x => x.followeeId));
const followingChannels = info?.followingChannels ? info.followingChannels : new Set<string>((await ChannelFollowings.find({
where: {
followerId: userId
},
select: ['followeeId']
})).map(x => x.followeeId));
if (!exist) return;
const myAntennas = (await getAntennas()).filter(a => a.userId === userId);
const readMentions: (Note | PackedNote)[] = [];
const readSpecifiedNotes: (Note | PackedNote)[] = [];
const readChannelNotes: (Note | PackedNote)[] = [];
const readAntennaNotes: (Note | PackedNote)[] = [];
// Remove the record
await NoteUnreads.delete({
userId: userId,
noteId: noteId,
});
if (exist.isMentioned) {
NoteUnreads.count({
userId: userId,
isMentioned: true
}).then(mentionsCount => {
if (mentionsCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllUnreadMentions');
}
});
for (const note of notes) {
if (note.mentions && note.mentions.includes(userId)) {
readMentions.push(note);
} else if (note.visibleUserIds && note.visibleUserIds.includes(userId)) {
readSpecifiedNotes.push(note);
}
if (exist.isSpecified) {
NoteUnreads.count({
userId: userId,
isSpecified: true
}).then(specifiedCount => {
if (specifiedCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
});
if (note.channelId && followingChannels.has(note.channelId)) {
readChannelNotes.push(note);
}
if (exist.noteChannelId) {
NoteUnreads.count({
userId: userId,
noteChannelId: Not(IsNull())
}).then(channelNoteCount => {
if (channelNoteCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllChannels');
if (note.user != null) { // たぶんnullになることは無いはずだけど一応
for (const antenna of myAntennas) {
if (checkHitAntenna(antenna, note, note.user as any, undefined, Array.from(following))) {
readAntennaNotes.push(note);
}
});
}
}
}
async function careAntenna() {
const beforeUnread = await Users.getHasUnreadAntenna(userId);
if (!beforeUnread) return;
if ((readMentions.length > 0) || (readSpecifiedNotes.length > 0) || (readChannelNotes.length > 0)) {
// Remove the record
await NoteUnreads.delete({
userId: userId,
noteId: In([...readMentions.map(n => n.id), ...readSpecifiedNotes.map(n => n.id), ...readChannelNotes.map(n => n.id)]),
});
const antennas = await Antennas.find({ userId });
// TODO: ↓まとめてクエリしたい
await Promise.all(antennas.map(async antenna => {
const countBefore = await AntennaNotes.count({
NoteUnreads.count({
userId: userId,
isMentioned: true
}).then(mentionsCount => {
if (mentionsCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllUnreadMentions');
}
});
NoteUnreads.count({
userId: userId,
isSpecified: true
}).then(specifiedCount => {
if (specifiedCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllUnreadSpecifiedNotes');
}
});
NoteUnreads.count({
userId: userId,
noteChannelId: Not(IsNull())
}).then(channelNoteCount => {
if (channelNoteCount === 0) {
// 全て既読になったイベントを発行
publishMainStream(userId, 'readAllChannels');
}
});
}
if (readAntennaNotes.length > 0) {
await AntennaNotes.update({
antennaId: In(myAntennas.map(a => a.id)),
noteId: In(readAntennaNotes.map(n => n.id))
}, {
read: true
});
// TODO: まとめてクエリしたい
for (const antenna of myAntennas) {
const count = await AntennaNotes.count({
antennaId: antenna.id,
read: false
});
if (countBefore === 0) return;
await AntennaNotes.update({
antennaId: antenna.id,
noteId: noteId
}, {
read: true
});
const countAfter = await AntennaNotes.count({
antennaId: antenna.id,
read: false
});
if (countAfter === 0) {
if (count === 0) {
publishMainStream(userId, 'readAntenna', antenna);
}
}));
}
Users.getHasUnreadAntenna(userId).then(unread => {
if (!unread) {
@@ -99,7 +124,4 @@ export default async function(
}
});
}
careNoteUnreads();
careAntenna();
}

View File

@@ -2,7 +2,7 @@ import { Note } from '../../models/entities/note';
import { publishMainStream } from '../stream';
import { User } from '../../models/entities/user';
import { Mutings, NoteUnreads } from '../../models';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
export default async function(userId: User['id'], note: Note, params: {
// NOTE: isSpecifiedがtrueならisMentionedは必ずfalse
@@ -17,7 +17,7 @@ export default async function(userId: User['id'], note: Note, params: {
if (mute.map(m => m.muteeId).includes(note.userId)) return;
//#endregion
const unread = await NoteUnreads.save({
const unread = {
id: genId(),
noteId: note.id,
userId: userId,
@@ -25,7 +25,9 @@ export default async function(userId: User['id'], note: Note, params: {
isMentioned: params.isMentioned,
noteChannelId: note.channelId,
noteUserId: note.userId,
});
};
await NoteUnreads.insert(unread);
// 2秒経っても既読にならなかったら「未読の投稿がありますよ」イベントを発行する
setTimeout(async () => {

View File

@@ -1,7 +1,7 @@
import { User } from '../../models/entities/user';
import { Note } from '../../models/entities/note';
import { NoteWatchings } from '../../models';
import { genId } from '../../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { NoteWatching } from '../../models/entities/note-watching';
export default async (me: User['id'], note: Note) => {
@@ -10,7 +10,7 @@ export default async (me: User['id'], note: Note) => {
return;
}
await NoteWatchings.save({
await NoteWatchings.insert({
id: genId(),
createdAt: new Date(),
noteId: note.id,

View File

@@ -1,7 +1,7 @@
import * as push from 'web-push';
import config from '../config';
import config from '@/config';
import { SwSubscriptions } from '../models';
import { fetchMeta } from '../misc/fetch-meta';
import { fetchMeta } from '@/misc/fetch-meta';
import { PackedNotification } from '../models/repositories/notification';
import { PackedMessagingMessage } from '../models/repositories/messaging-message';
import { pushNotificationData } from '../types';

View File

@@ -1,12 +1,18 @@
import { Instance } from '../models/entities/instance';
import { Instances } from '../models';
import { federationChart } from './chart';
import { genId } from '../misc/gen-id';
import { toPuny } from '../misc/convert-host';
import { genId } from '@/misc/gen-id';
import { toPuny } from '@/misc/convert-host';
import { Cache } from '@/misc/cache';
const cache = new Cache<Instance>(1000 * 60 * 60);
export async function registerOrFetchInstanceDoc(host: string): Promise<Instance> {
host = toPuny(host);
const cached = cache.get(host);
if (cached) return cached;
const index = await Instances.findOne({ host });
if (index == null) {
@@ -19,8 +25,10 @@ export async function registerOrFetchInstanceDoc(host: string): Promise<Instance
federationChart.update(true);
cache.set(host, i);
return i;
} else {
cache.set(host, index);
return index;
}
}

View File

@@ -5,7 +5,7 @@ import renderUndo from '../remote/activitypub/renderer/undo';
import { deliver } from '../queue';
import { ILocalUser } from '../models/entities/user';
import { Users, Relays } from '../models';
import { genId } from '../misc/gen-id';
import { genId } from '@/misc/gen-id';
const ACTOR_USERNAME = 'relay.actor' as const;

View File

@@ -2,7 +2,7 @@ import { UserProfiles } from '../models';
import { User } from '../models/entities/user';
import { sendEmail } from './send-email';
import * as locales from '../../locales/';
import { I18n } from '../misc/i18n';
import { I18n } from '@/misc/i18n';
// TODO: locale ファイルをクライアント用とサーバー用で分けたい

View File

@@ -1,7 +1,7 @@
import * as nodemailer from 'nodemailer';
import { fetchMeta } from '../misc/fetch-meta';
import { fetchMeta } from '@/misc/fetch-meta';
import Logger from './logger';
import config from '../config';
import config from '@/config';
export const logger = new Logger('email');

View File

@@ -1,10 +1,10 @@
import redis from '../db/redis';
import { redisClient } from '../db/redis';
import { User } from '../models/entities/user';
import { Note } from '../models/entities/note';
import { UserList } from '../models/entities/user-list';
import { ReversiGame } from '../models/entities/games/reversi/game';
import { UserGroup } from '../models/entities/user-group';
import config from '../config';
import config from '@/config';
import { Antenna } from '../models/entities/antenna';
import { Channel } from '../models/entities/channel';
@@ -14,12 +14,20 @@ class Publisher {
{ type: type, body: null } :
{ type: type, body: value };
redis.publish(config.host, JSON.stringify({
redisClient.publish(config.host, JSON.stringify({
channel: channel,
message: message
}));
}
public publishInternalEvent = (type: string, value?: any): void => {
this.publish('internal', type, typeof value === 'undefined' ? null : value);
}
public publishUserEvent = (userId: User['id'], type: string, value?: any): void => {
this.publish(`user:${userId}`, type, typeof value === 'undefined' ? null : value);
}
public publishBroadcastStream = (type: string, value?: any): void => {
this.publish('broadcast', type, typeof value === 'undefined' ? null : value);
}
@@ -84,6 +92,8 @@ const publisher = new Publisher();
export default publisher;
export const publishInternalEvent = publisher.publishInternalEvent;
export const publishUserEvent = publisher.publishUserEvent;
export const publishBroadcastStream = publisher.publishBroadcastStream;
export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream;

View File

@@ -1,7 +1,7 @@
import renderDelete from '../remote/activitypub/renderer/delete';
import { renderActivity } from '../remote/activitypub/renderer';
import { deliver } from '../queue';
import config from '../config';
import config from '@/config';
import { User } from '../models/entities/user';
import { Users, Followings } from '../models';
import { Not, IsNull } from 'typeorm';

View File

@@ -2,7 +2,7 @@ import renderDelete from '../remote/activitypub/renderer/delete';
import renderUndo from '../remote/activitypub/renderer/undo';
import { renderActivity } from '../remote/activitypub/renderer';
import { deliver } from '../queue';
import config from '../config';
import config from '@/config';
import { User } from '../models/entities/user';
import { Users, Followings } from '../models';
import { Not, IsNull } from 'typeorm';

View File

@@ -1,9 +1,9 @@
import { User } from '../models/entities/user';
import { Hashtags, Users } from '../models';
import { hashtagChart } from './chart';
import { genId } from '../misc/gen-id';
import { genId } from '@/misc/gen-id';
import { Hashtag } from '../models/entities/hashtag';
import { normalizeForSearch } from '../misc/normalize-for-search';
import { normalizeForSearch } from '@/misc/normalize-for-search';
export async function updateHashtags(user: User, tags: string[]) {
for (const tag of tags) {
@@ -86,7 +86,7 @@ export async function updateHashtag(user: User, tag: string, isUserAttached = fa
}
} else {
if (isUserAttached) {
Hashtags.save({
Hashtags.insert({
id: genId(),
name: tag,
mentionedUserIds: [],
@@ -103,7 +103,7 @@ export async function updateHashtag(user: User, tag: string, isUserAttached = fa
attachedRemoteUsersCount: Users.isRemoteUser(user) ? 1 : 0,
} as Hashtag);
} else {
Hashtags.save({
Hashtags.insert({
id: genId(),
name: tag,
mentionedUserIds: [user.id],

View File

@@ -3,12 +3,12 @@ import { User } from '../../models/entities/user';
import { UserList } from '../../models/entities/user-list';
import { UserListJoinings, Users } from '../../models';
import { UserListJoining } from '../../models/entities/user-list-joining';
import { genId } from '../../misc/gen-id';
import { fetchProxyAccount } from '../../misc/fetch-proxy-account';
import { genId } from '@/misc/gen-id';
import { fetchProxyAccount } from '@/misc/fetch-proxy-account';
import createFollowing from '../following/create';
export async function pushUserToUserList(target: User, list: UserList) {
await UserListJoinings.save({
await UserListJoinings.insert({
id: genId(),
createdAt: new Date(),
userId: target.id,