Use PostgreSQL instead of MongoDB (#4572)
* wip * Update note.ts * Update timeline.ts * Update core.ts * wip * Update generate-visibility-query.ts * wip * wip * wip * wip * wip * Update global-timeline.ts * wip * wip * wip * Update vote.ts * wip * wip * Update create.ts * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * Update files.ts * wip * wip * Update CONTRIBUTING.md * wip * wip * wip * wip * wip * wip * wip * wip * Update read-notification.ts * wip * wip * wip * wip * wip * wip * wip * Update cancel.ts * wip * wip * wip * Update show.ts * wip * wip * Update gen-id.ts * Update create.ts * Update id.ts * wip * wip * wip * wip * wip * wip * wip * Docker: Update files about Docker (#4599) * Docker: Use cache if files used by `yarn install` was not updated This patch reduces the number of times to installing node_modules. For example, `yarn install` step will be skipped when only ".config/default.yml" is updated. * Docker: Migrate MongoDB to Postgresql Misskey uses Postgresql as a database instead of Mongodb since version 11. * Docker: Uncomment about data persistence This patch will save a lot of databases. * wip * wip * wip * Update activitypub.ts * wip * wip * wip * Update logs.ts * wip * Update drive-file.ts * Update register.ts * wip * wip * Update mentions.ts * wip * wip * wip * Update recommendation.ts * wip * Update index.ts * wip * Update recommendation.ts * Doc: Update docker.ja.md and docker.en.md (#1) (#4608) Update how to set up misskey. * wip * ✌️ * wip * Update note.ts * Update postgre.ts * wip * wip * wip * wip * Update add-file.ts * wip * wip * wip * Clean up * Update logs.ts * wip * 🍕 * wip * Ad notes * wip * Update api-visibility.ts * Update note.ts * Update add-file.ts * tests * tests * Update postgre.ts * Update utils.ts * wip * wip * Refactor * wip * Refactor * wip * wip * Update show-users.ts * Update update-instance.ts * wip * Update feed.ts * Update outbox.ts * Update outbox.ts * Update user.ts * wip * Update list.ts * Update update-hashtag.ts * wip * Update update-hashtag.ts * Refactor * Update update.ts * wip * wip * ✌️ * clean up * docs * Update push.ts * wip * Update api.ts * wip * ✌️ * Update make-pagination-query.ts * ✌️ * Delete hashtags.ts * Update instances.ts * Update instances.ts * Update create.ts * Update search.ts * Update reversi-game.ts * Update signup.ts * Update user.ts * id * Update example.yml * 🎨 * objectid * fix * reversi * reversi * Fix bug of chart engine * Add test of chart engine * Improve test * Better testing * Improve chart engine * Refactor * Add test of chart engine * Refactor * Add chart test * Fix bug * コミットし忘れ * Refactoring * ✌️ * Add tests * Add test * Extarct note tests * Refactor * 存在しないユーザーにメンションできなくなっていた問題を修正 * Fix bug * Update update-meta.ts * Fix bug * Update mention.vue * Fix bug * Update meta.ts * Update CONTRIBUTING.md * Fix bug * Fix bug * Fix bug * Clean up * Clean up * Update notification.ts * Clean up * Add mute tests * Add test * Refactor * Add test * Fix test * Refactor * Refactor * Add tests * Update utils.ts * Update utils.ts * Fix test * Update package.json * Update update.ts * Update manifest.ts * Fix bug * Fix bug * Add test * 🎨 * Update endpoint permissions * Updaye permisison * Update person.ts #4299 * データベースと同期しないように * Fix bug * Fix bug * Update reversi-game.ts * Use a feature of Node v11.7.0 to extract a public key (#4644) * wip * wip * ✌️ * Refactoring #1540 * test * test * test * test * test * test * test * Fix bug * Fix test * 🍣 * wip * #4471 * Add test for #4335 * Refactor * Fix test * Add tests * 🕓 * Fix bug * Add test * Add test * rename * Fix bug
This commit is contained in:
parent
13caf37991
commit
f0a29721c9
592 changed files with 13463 additions and 14147 deletions
|
@ -1,52 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
const AbuseUserReport = db.get<IAbuseUserReport>('abuseUserReports');
|
||||
AbuseUserReport.createIndex('userId');
|
||||
AbuseUserReport.createIndex('reporterId');
|
||||
AbuseUserReport.createIndex(['userId', 'reporterId'], { unique: true });
|
||||
export default AbuseUserReport;
|
||||
|
||||
export interface IAbuseUserReport {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID;
|
||||
reporterId: mongo.ObjectID;
|
||||
comment: string;
|
||||
}
|
||||
|
||||
export const packMany = (
|
||||
reports: (string | mongo.ObjectID | IAbuseUserReport)[]
|
||||
) => {
|
||||
return Promise.all(reports.map(x => pack(x)));
|
||||
};
|
||||
|
||||
export const pack = (
|
||||
report: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _report: any;
|
||||
|
||||
if (isObjectId(report)) {
|
||||
_report = await AbuseUserReport.findOne({
|
||||
_id: report
|
||||
});
|
||||
} else if (typeof report === 'string') {
|
||||
_report = await AbuseUserReport.findOne({
|
||||
_id: new mongo.ObjectID(report)
|
||||
});
|
||||
} else {
|
||||
_report = deepcopy(report);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_report.id = _report._id;
|
||||
delete _report._id;
|
||||
|
||||
_report.reporter = await packUser(_report.reporterId, null, { detail: true });
|
||||
_report.user = await packUser(_report.userId, null, { detail: true });
|
||||
|
||||
resolve(_report);
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const AccessToken = db.get<IAccessToken>('accessTokens');
|
||||
AccessToken.createIndex('token');
|
||||
AccessToken.createIndex('hash');
|
||||
export default AccessToken;
|
||||
|
||||
export type IAccessToken = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
appId: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
token: string;
|
||||
hash: string;
|
||||
};
|
|
@ -1,102 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import AccessToken from './access-token';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import config from '../config';
|
||||
import { dbLogger } from '../db/logger';
|
||||
|
||||
const App = db.get<IApp>('apps');
|
||||
App.createIndex('secret');
|
||||
export default App;
|
||||
|
||||
export type IApp = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID | null;
|
||||
secret: string;
|
||||
name: string;
|
||||
description: string;
|
||||
permission: string[];
|
||||
callbackUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack an app for API response
|
||||
*/
|
||||
export const pack = (
|
||||
app: any,
|
||||
me?: any,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
includeSecret?: boolean,
|
||||
includeProfileImageIds?: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
includeSecret: false,
|
||||
includeProfileImageIds: false
|
||||
}, options);
|
||||
|
||||
let _app: any;
|
||||
|
||||
const fields = opts.detail ? {} : {
|
||||
name: true
|
||||
};
|
||||
|
||||
// Populate the app if 'app' is ID
|
||||
if (isObjectId(app)) {
|
||||
_app = await App.findOne({
|
||||
_id: app
|
||||
});
|
||||
} else if (typeof app === 'string') {
|
||||
_app = await App.findOne({
|
||||
_id: new mongo.ObjectID(app)
|
||||
}, { fields });
|
||||
} else {
|
||||
_app = deepcopy(app);
|
||||
}
|
||||
|
||||
// Me
|
||||
if (me && !isObjectId(me)) {
|
||||
if (typeof me === 'string') {
|
||||
me = new mongo.ObjectID(me);
|
||||
} else {
|
||||
me = me._id;
|
||||
}
|
||||
}
|
||||
|
||||
// (データベースの欠損などで)アプリがデータベース上に見つからなかったとき
|
||||
if (_app == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: app :: ${app}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_app.id = _app._id;
|
||||
delete _app._id;
|
||||
|
||||
// Visible by only owner
|
||||
if (!opts.includeSecret) {
|
||||
delete _app.secret;
|
||||
}
|
||||
|
||||
_app.iconUrl = _app.icon != null
|
||||
? `${config.driveUrl}/${_app.icon}`
|
||||
: `${config.driveUrl}/app-default.jpg`;
|
||||
|
||||
if (me) {
|
||||
// 既に連携しているか
|
||||
const exist = await AccessToken.count({
|
||||
appId: _app.id,
|
||||
userId: me,
|
||||
}, {
|
||||
limit: 1
|
||||
});
|
||||
|
||||
_app.isAuthorized = exist === 1;
|
||||
}
|
||||
|
||||
resolve(_app);
|
||||
});
|
|
@ -1,49 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packApp } from './app';
|
||||
|
||||
const AuthSession = db.get<IAuthSession>('authSessions');
|
||||
export default AuthSession;
|
||||
|
||||
export interface IAuthSession {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
appId: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
token: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack an auth session for API response
|
||||
*
|
||||
* @param {any} session
|
||||
* @param {any} me?
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
export const pack = (
|
||||
session: any,
|
||||
me?: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _session: any;
|
||||
|
||||
// TODO: Populate session if it ID
|
||||
_session = deepcopy(session);
|
||||
|
||||
// Me
|
||||
if (me && !isObjectId(me)) {
|
||||
if (typeof me === 'string') {
|
||||
me = new mongo.ObjectID(me);
|
||||
} else {
|
||||
me = me._id;
|
||||
}
|
||||
}
|
||||
|
||||
delete _session._id;
|
||||
|
||||
// Populate app
|
||||
_session.app = await packApp(_session.appId, me);
|
||||
|
||||
resolve(_session);
|
||||
});
|
|
@ -1,56 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import { pack as packUser, IUser } from './user';
|
||||
|
||||
const Blocking = db.get<IBlocking>('blocking');
|
||||
Blocking.createIndex('blockerId');
|
||||
Blocking.createIndex('blockeeId');
|
||||
Blocking.createIndex(['blockerId', 'blockeeId'], { unique: true });
|
||||
export default Blocking;
|
||||
|
||||
export type IBlocking = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
blockeeId: mongo.ObjectID;
|
||||
blockerId: mongo.ObjectID;
|
||||
};
|
||||
|
||||
export const packMany = (
|
||||
blockings: (string | mongo.ObjectID | IBlocking)[],
|
||||
me?: string | mongo.ObjectID | IUser
|
||||
) => {
|
||||
return Promise.all(blockings.map(x => pack(x, me)));
|
||||
};
|
||||
|
||||
export const pack = (
|
||||
blocking: any,
|
||||
me?: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _blocking: any;
|
||||
|
||||
// Populate the blocking if 'blocking' is ID
|
||||
if (isObjectId(blocking)) {
|
||||
_blocking = await Blocking.findOne({
|
||||
_id: blocking
|
||||
});
|
||||
} else if (typeof blocking === 'string') {
|
||||
_blocking = await Blocking.findOne({
|
||||
_id: new mongo.ObjectID(blocking)
|
||||
});
|
||||
} else {
|
||||
_blocking = deepcopy(blocking);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_blocking.id = _blocking._id;
|
||||
delete _blocking._id;
|
||||
|
||||
// Populate blockee
|
||||
_blocking.blockee = await packUser(_blocking.blockeeId, me, {
|
||||
detail: true
|
||||
});
|
||||
|
||||
resolve(_blocking);
|
||||
});
|
|
@ -1,29 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
|
||||
const DriveFileThumbnail = monkDb.get<IDriveFileThumbnail>('driveFileThumbnails.files');
|
||||
DriveFileThumbnail.createIndex('metadata.originalId', { sparse: true, unique: true });
|
||||
export default DriveFileThumbnail;
|
||||
|
||||
export const DriveFileThumbnailChunk = monkDb.get('driveFileThumbnails.chunks');
|
||||
|
||||
export const getDriveFileThumbnailBucket = async (): Promise<mongo.GridFSBucket> => {
|
||||
const db = await nativeDbConn();
|
||||
const bucket = new mongo.GridFSBucket(db, {
|
||||
bucketName: 'driveFileThumbnails'
|
||||
});
|
||||
return bucket;
|
||||
};
|
||||
|
||||
export type IMetadata = {
|
||||
originalId: mongo.ObjectID;
|
||||
};
|
||||
|
||||
export type IDriveFileThumbnail = {
|
||||
_id: mongo.ObjectID;
|
||||
uploadDate: Date;
|
||||
md5: string;
|
||||
filename: string;
|
||||
contentType: string;
|
||||
metadata: IMetadata;
|
||||
};
|
|
@ -1,29 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
|
||||
const DriveFileWebpublic = monkDb.get<IDriveFileWebpublic>('driveFileWebpublics.files');
|
||||
DriveFileWebpublic.createIndex('metadata.originalId', { sparse: true, unique: true });
|
||||
export default DriveFileWebpublic;
|
||||
|
||||
export const DriveFileWebpublicChunk = monkDb.get('driveFileWebpublics.chunks');
|
||||
|
||||
export const getDriveFileWebpublicBucket = async (): Promise<mongo.GridFSBucket> => {
|
||||
const db = await nativeDbConn();
|
||||
const bucket = new mongo.GridFSBucket(db, {
|
||||
bucketName: 'driveFileWebpublics'
|
||||
});
|
||||
return bucket;
|
||||
};
|
||||
|
||||
export type IMetadata = {
|
||||
originalId: mongo.ObjectID;
|
||||
};
|
||||
|
||||
export type IDriveFileWebpublic = {
|
||||
_id: mongo.ObjectID;
|
||||
uploadDate: Date;
|
||||
md5: string;
|
||||
filename: string;
|
||||
contentType: string;
|
||||
metadata: IMetadata;
|
||||
};
|
|
@ -1,232 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import { pack as packFolder } from './drive-folder';
|
||||
import { pack as packUser } from './user';
|
||||
import monkDb, { nativeDbConn } from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import getDriveFileUrl, { getOriginalUrl } from '../misc/get-drive-file-url';
|
||||
import { dbLogger } from '../db/logger';
|
||||
|
||||
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
|
||||
DriveFile.createIndex('md5');
|
||||
DriveFile.createIndex('metadata.uri');
|
||||
DriveFile.createIndex('metadata.userId');
|
||||
DriveFile.createIndex('metadata.folderId');
|
||||
DriveFile.createIndex('metadata._user.host');
|
||||
export default DriveFile;
|
||||
|
||||
export const DriveFileChunk = monkDb.get('driveFiles.chunks');
|
||||
|
||||
export const getDriveFileBucket = async (): Promise<mongo.GridFSBucket> => {
|
||||
const db = await nativeDbConn();
|
||||
const bucket = new mongo.GridFSBucket(db, {
|
||||
bucketName: 'driveFiles'
|
||||
});
|
||||
return bucket;
|
||||
};
|
||||
|
||||
export type IMetadata = {
|
||||
properties: any;
|
||||
userId: mongo.ObjectID;
|
||||
_user: any;
|
||||
folderId: mongo.ObjectID;
|
||||
comment: string;
|
||||
|
||||
/**
|
||||
* リモートインスタンスから取得した場合の元URL
|
||||
*/
|
||||
uri?: string;
|
||||
|
||||
/**
|
||||
* URL for web(生成されている場合) or original
|
||||
* * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ
|
||||
*/
|
||||
url?: string;
|
||||
|
||||
/**
|
||||
* URL for thumbnail (thumbnailがなければなし)
|
||||
* * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ
|
||||
*/
|
||||
thumbnailUrl?: string;
|
||||
|
||||
/**
|
||||
* URL for original (web用が生成されてない場合はurlがoriginalを指す)
|
||||
* * オブジェクトストレージを利用している or リモートサーバーへの直リンクである 場合のみ
|
||||
*/
|
||||
webpublicUrl?: string;
|
||||
|
||||
accessKey?: string;
|
||||
|
||||
src?: string;
|
||||
deletedAt?: Date;
|
||||
|
||||
/**
|
||||
* このファイルの中身データがMongoDB内に保存されていないか否か
|
||||
* オブジェクトストレージを利用している or リモートサーバーへの直リンクである
|
||||
* な場合は true になります
|
||||
*/
|
||||
withoutChunks?: boolean;
|
||||
|
||||
storage?: string;
|
||||
|
||||
/***
|
||||
* ObjectStorage の格納先の情報
|
||||
*/
|
||||
storageProps?: IStorageProps;
|
||||
isSensitive?: boolean;
|
||||
|
||||
/**
|
||||
* このファイルが添付された投稿のID一覧
|
||||
*/
|
||||
attachedNoteIds?: mongo.ObjectID[];
|
||||
|
||||
/**
|
||||
* 外部の(信頼されていない)URLへの直リンクか否か
|
||||
*/
|
||||
isRemote?: boolean;
|
||||
};
|
||||
|
||||
export type IStorageProps = {
|
||||
/**
|
||||
* ObjectStorage key for original
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/***
|
||||
* ObjectStorage key for thumbnail (thumbnailがなければなし)
|
||||
*/
|
||||
thumbnailKey?: string;
|
||||
|
||||
/***
|
||||
* ObjectStorage key for webpublic (webpublicがなければなし)
|
||||
*/
|
||||
webpublicKey?: string;
|
||||
|
||||
id?: string;
|
||||
};
|
||||
|
||||
export type IDriveFile = {
|
||||
_id: mongo.ObjectID;
|
||||
uploadDate: Date;
|
||||
md5: string;
|
||||
filename: string;
|
||||
contentType: string;
|
||||
metadata: IMetadata;
|
||||
|
||||
/**
|
||||
* ファイルサイズ
|
||||
*/
|
||||
length: number;
|
||||
};
|
||||
|
||||
export function validateFileName(name: string): boolean {
|
||||
return (
|
||||
(name.trim().length > 0) &&
|
||||
(name.length <= 200) &&
|
||||
(name.indexOf('\\') === -1) &&
|
||||
(name.indexOf('/') === -1) &&
|
||||
(name.indexOf('..') === -1)
|
||||
);
|
||||
}
|
||||
|
||||
export const packMany = (
|
||||
files: any[],
|
||||
options?: {
|
||||
detail?: boolean
|
||||
self?: boolean,
|
||||
withUser?: boolean,
|
||||
}
|
||||
) => {
|
||||
return Promise.all(files.map(f => pack(f, options)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a drive file for API response
|
||||
*/
|
||||
export const pack = (
|
||||
file: any,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
self?: boolean,
|
||||
withUser?: boolean,
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
self: false
|
||||
}, options);
|
||||
|
||||
let _file: any;
|
||||
|
||||
// Populate the file if 'file' is ID
|
||||
if (isObjectId(file)) {
|
||||
_file = await DriveFile.findOne({
|
||||
_id: file
|
||||
});
|
||||
} else if (typeof file === 'string') {
|
||||
_file = await DriveFile.findOne({
|
||||
_id: new mongo.ObjectID(file)
|
||||
});
|
||||
} else {
|
||||
_file = deepcopy(file);
|
||||
}
|
||||
|
||||
// (データベースの欠損などで)ファイルがデータベース上に見つからなかったとき
|
||||
if (_file == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: driveFile :: ${file}`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
// rendered target
|
||||
let _target: any = {};
|
||||
|
||||
_target.id = _file._id;
|
||||
_target.createdAt = _file.uploadDate;
|
||||
_target.name = _file.filename;
|
||||
_target.type = _file.contentType;
|
||||
_target.datasize = _file.length;
|
||||
_target.md5 = _file.md5;
|
||||
|
||||
_target = Object.assign(_target, _file.metadata);
|
||||
|
||||
_target.url = getDriveFileUrl(_file);
|
||||
_target.thumbnailUrl = getDriveFileUrl(_file, true);
|
||||
_target.isRemote = _file.metadata.isRemote;
|
||||
|
||||
if (_target.properties == null) _target.properties = {};
|
||||
|
||||
if (opts.detail) {
|
||||
if (_target.folderId) {
|
||||
// Populate folder
|
||||
_target.folder = await packFolder(_target.folderId, {
|
||||
detail: true
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
if (_target.tags) {
|
||||
// Populate tags
|
||||
_target.tags = await _target.tags.map(async (tag: any) =>
|
||||
await serializeDriveTag(tag)
|
||||
);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
if (opts.withUser) {
|
||||
// Populate user
|
||||
_target.user = await packUser(_file.metadata.userId);
|
||||
}
|
||||
|
||||
delete _target.withoutChunks;
|
||||
delete _target.storage;
|
||||
delete _target.storageProps;
|
||||
delete _target.isRemote;
|
||||
delete _target._user;
|
||||
|
||||
if (opts.self) {
|
||||
_target.url = getOriginalUrl(_file);
|
||||
}
|
||||
|
||||
resolve(_target);
|
||||
});
|
|
@ -1,75 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import DriveFile from './drive-file';
|
||||
|
||||
const DriveFolder = db.get<IDriveFolder>('driveFolders');
|
||||
DriveFolder.createIndex('userId');
|
||||
export default DriveFolder;
|
||||
|
||||
export type IDriveFolder = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
name: string;
|
||||
userId: mongo.ObjectID;
|
||||
parentId: mongo.ObjectID;
|
||||
};
|
||||
|
||||
export function isValidFolderName(name: string): boolean {
|
||||
return (
|
||||
(name.trim().length > 0) &&
|
||||
(name.length <= 200)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a drive folder for API response
|
||||
*/
|
||||
export const pack = (
|
||||
folder: any,
|
||||
options?: {
|
||||
detail: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = Object.assign({
|
||||
detail: false
|
||||
}, options);
|
||||
|
||||
let _folder: any;
|
||||
|
||||
// Populate the folder if 'folder' is ID
|
||||
if (isObjectId(folder)) {
|
||||
_folder = await DriveFolder.findOne({ _id: folder });
|
||||
} else if (typeof folder === 'string') {
|
||||
_folder = await DriveFolder.findOne({ _id: new mongo.ObjectID(folder) });
|
||||
} else {
|
||||
_folder = deepcopy(folder);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_folder.id = _folder._id;
|
||||
delete _folder._id;
|
||||
|
||||
if (opts.detail) {
|
||||
const childFoldersCount = await DriveFolder.count({
|
||||
parentId: _folder.id
|
||||
});
|
||||
|
||||
const childFilesCount = await DriveFile.count({
|
||||
'metadata.folderId': _folder.id
|
||||
});
|
||||
|
||||
_folder.foldersCount = childFoldersCount;
|
||||
_folder.filesCount = childFilesCount;
|
||||
}
|
||||
|
||||
if (opts.detail && _folder.parentId) {
|
||||
// Populate parent folder
|
||||
_folder.parent = await pack(_folder.parentId, {
|
||||
detail: true
|
||||
});
|
||||
}
|
||||
|
||||
resolve(_folder);
|
||||
});
|
|
@ -1,21 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Emoji = db.get<IEmoji>('emoji');
|
||||
Emoji.createIndex('name');
|
||||
Emoji.createIndex('host');
|
||||
Emoji.createIndex(['name', 'host'], { unique: true });
|
||||
|
||||
export default Emoji;
|
||||
|
||||
export type IEmoji = {
|
||||
_id: mongo.ObjectID;
|
||||
name: string;
|
||||
host: string;
|
||||
url: string;
|
||||
aliases?: string[];
|
||||
updatedAt?: Date;
|
||||
/** AP object id */
|
||||
uri?: string;
|
||||
type?: string;
|
||||
};
|
41
src/models/entities/abuse-user-report.ts
Normal file
41
src/models/entities/abuse-user-report.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'reporterId'], { unique: true })
|
||||
export class AbuseUserReport {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the AbuseUserReport.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public reporterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public reporter: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
})
|
||||
public comment: string;
|
||||
}
|
45
src/models/entities/access-token.ts
Normal file
45
src/models/entities/access-token.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn, RelationId } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { App } from './app';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class AccessToken {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the AccessToken.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public token: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public hash: string;
|
||||
|
||||
@RelationId((self: AccessToken) => self.user)
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public appId: App['id'];
|
||||
|
||||
@ManyToOne(type => App, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public app: App | null;
|
||||
}
|
60
src/models/entities/app.ts
Normal file
60
src/models/entities/app.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
import { Entity, PrimaryColumn, Column, Index, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class App {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the App.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The owner ID.'
|
||||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'SET NULL',
|
||||
nullable: true,
|
||||
})
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 64,
|
||||
comment: 'The secret key of the App.'
|
||||
})
|
||||
public secret: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The name of the App.'
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
comment: 'The description of the App.'
|
||||
})
|
||||
public description: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, array: true,
|
||||
comment: 'The permission of the App.'
|
||||
})
|
||||
public permission: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The callbackUrl of the App.'
|
||||
})
|
||||
public callbackUrl: string | null;
|
||||
}
|
39
src/models/entities/auth-session.ts
Normal file
39
src/models/entities/auth-session.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { Entity, PrimaryColumn, Index, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { App } from './app';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class AuthSession {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the AuthSession.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public token: string;
|
||||
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public appId: App['id'];
|
||||
|
||||
@ManyToOne(type => App, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public app: App | null;
|
||||
}
|
42
src/models/entities/blocking.ts
Normal file
42
src/models/entities/blocking.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['blockerId', 'blockeeId'], { unique: true })
|
||||
export class Blocking {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Blocking.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The blockee user ID.'
|
||||
})
|
||||
public blockeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public blockee: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The blocker user ID.'
|
||||
})
|
||||
public blockerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public blocker: User | null;
|
||||
}
|
154
src/models/entities/drive-file.ts
Normal file
154
src/models/entities/drive-file.ts
Normal file
|
@ -0,0 +1,154 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { DriveFolder } from './drive-folder';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class DriveFile {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the DriveFile.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The owner ID.'
|
||||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The host of owner. It will be null if the user in local.'
|
||||
})
|
||||
public userHost: string | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 32,
|
||||
comment: 'The MD5 hash of the DriveFile.'
|
||||
})
|
||||
public md5: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
comment: 'The file name of the DriveFile.'
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The content type (MIME) of the DriveFile.'
|
||||
})
|
||||
public type: string;
|
||||
|
||||
@Column('integer', {
|
||||
comment: 'The file size (bytes) of the DriveFile.'
|
||||
})
|
||||
public size: number;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
comment: 'The comment of the DriveFile.'
|
||||
})
|
||||
public comment: string | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: {},
|
||||
comment: 'The any properties of the DriveFile. For example, it includes image width/height.'
|
||||
})
|
||||
public properties: Record<string, any>;
|
||||
|
||||
@Column('boolean')
|
||||
public storedInternal: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512,
|
||||
comment: 'The URL of the DriveFile.'
|
||||
})
|
||||
public url: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
comment: 'The URL of the thumbnail of the DriveFile.'
|
||||
})
|
||||
public thumbnailUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
comment: 'The URL of the webpublic of the DriveFile.'
|
||||
})
|
||||
public webpublicUrl: string | null;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public accessKey: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
})
|
||||
public thumbnailAccessKey: string | null;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
})
|
||||
public webpublicAccessKey: string | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
comment: 'The URI of the DriveFile. it will be null when the DriveFile is local.'
|
||||
})
|
||||
public uri: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true,
|
||||
})
|
||||
public src: string | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The parent folder ID. If null, it means the DriveFile is located in root.'
|
||||
})
|
||||
public folderId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public folder: DriveFolder | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the DriveFile is NSFW.'
|
||||
})
|
||||
public isSensitive: boolean;
|
||||
|
||||
/**
|
||||
* 外部の(信頼されていない)URLへの直リンクか否か
|
||||
*/
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the DriveFile is direct link to remote server.'
|
||||
})
|
||||
public isRemote: boolean;
|
||||
}
|
49
src/models/entities/drive-folder.ts
Normal file
49
src/models/entities/drive-folder.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { JoinColumn, ManyToOne, Entity, PrimaryColumn, Index, Column } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class DriveFolder {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the DriveFolder.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The name of the DriveFolder.'
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The owner ID.'
|
||||
})
|
||||
public userId: User['id'] | null;
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The parent folder ID. If null, it means the DriveFolder is located in root.'
|
||||
})
|
||||
public parentId: DriveFolder['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFolder, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public parent: DriveFolder | null;
|
||||
}
|
46
src/models/entities/emoji.ts
Normal file
46
src/models/entities/emoji.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['name', 'host'], { unique: true })
|
||||
export class Emoji {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public host: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public url: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true
|
||||
})
|
||||
public uri: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true
|
||||
})
|
||||
public type: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
array: true, length: 128, default: '{}'
|
||||
})
|
||||
public aliases: string[];
|
||||
}
|
85
src/models/entities/follow-request.ts
Normal file
85
src/models/entities/follow-request.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['followerId', 'followeeId'], { unique: true })
|
||||
export class FollowRequest {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the FollowRequest.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The followee user ID.'
|
||||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public followee: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The follower user ID.'
|
||||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public follower: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'id of Follow Activity.'
|
||||
})
|
||||
public requestId: string | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerHost: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerSharedInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeHost: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeSharedInbox: string | null;
|
||||
//#endregion
|
||||
}
|
80
src/models/entities/following.ts
Normal file
80
src/models/entities/following.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['followerId', 'followeeId'], { unique: true })
|
||||
export class Following {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Following.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The followee user ID.'
|
||||
})
|
||||
public followeeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public followee: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The follower user ID.'
|
||||
})
|
||||
public followerId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public follower: User | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerHost: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followerSharedInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeHost: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public followeeSharedInbox: string | null;
|
||||
//#endregion
|
||||
}
|
133
src/models/entities/games/reversi/game.ts
Normal file
133
src/models/entities/games/reversi/game.ts
Normal file
|
@ -0,0 +1,133 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from '../../user';
|
||||
import { id } from '../../../id';
|
||||
|
||||
@Entity()
|
||||
export class ReversiGame {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the ReversiGame.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
comment: 'The started date of the ReversiGame.'
|
||||
})
|
||||
public startedAt: Date | null;
|
||||
|
||||
@Column(id())
|
||||
public user1Id: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user1: User | null;
|
||||
|
||||
@Column(id())
|
||||
public user2Id: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user2: User | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public user1Accepted: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public user2Accepted: boolean;
|
||||
|
||||
/**
|
||||
* どちらのプレイヤーが先行(黒)か
|
||||
* 1 ... user1
|
||||
* 2 ... user2
|
||||
*/
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public black: number | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isStarted: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isEnded: boolean;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true
|
||||
})
|
||||
public winnerId: User['id'] | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true
|
||||
})
|
||||
public surrendered: User['id'] | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public logs: {
|
||||
at: Date;
|
||||
color: boolean;
|
||||
pos: number;
|
||||
}[];
|
||||
|
||||
@Column('varchar', {
|
||||
array: true, length: 64,
|
||||
})
|
||||
public map: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32
|
||||
})
|
||||
public bw: string;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isLlotheo: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public canPutEverywhere: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public loopedBoard: boolean;
|
||||
|
||||
@Column('jsonb', {
|
||||
nullable: true, default: null,
|
||||
})
|
||||
public form1: any | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
nullable: true, default: null,
|
||||
})
|
||||
public form2: any | null;
|
||||
|
||||
/**
|
||||
* ログのposを文字列としてすべて連結したもののCRC32値
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: true
|
||||
})
|
||||
public crc32: string | null;
|
||||
}
|
35
src/models/entities/games/reversi/matching.ts
Normal file
35
src/models/entities/games/reversi/matching.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from '../../user';
|
||||
import { id } from '../../../id';
|
||||
|
||||
@Entity()
|
||||
export class ReversiMatching {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the ReversiMatching.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public parentId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public parent: User | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public childId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public child: User | null;
|
||||
}
|
87
src/models/entities/hashtag.ts
Normal file
87
src/models/entities/hashtag.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
import { Entity, PrimaryColumn, Index, Column } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Hashtag {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public name: string;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public mentionedUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public mentionedUsersCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public mentionedLocalUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public mentionedLocalUsersCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public mentionedRemoteUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public mentionedRemoteUsersCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public attachedUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public attachedUsersCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public attachedLocalUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public attachedLocalUsersCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true,
|
||||
})
|
||||
public attachedRemoteUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public attachedRemoteUsersCount: number;
|
||||
}
|
132
src/models/entities/instance.ts
Normal file
132
src/models/entities/instance.ts
Normal file
|
@ -0,0 +1,132 @@
|
|||
import { Entity, PrimaryColumn, Index, Column } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Instance {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* このインスタンスを捕捉した日時
|
||||
*/
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The caught date of the Instance.'
|
||||
})
|
||||
public caughtAt: Date;
|
||||
|
||||
/**
|
||||
* ホスト
|
||||
*/
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The host of the Instance.'
|
||||
})
|
||||
public host: string;
|
||||
|
||||
/**
|
||||
* インスタンスのシステム (MastodonとかMisskeyとかPleromaとか)
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true,
|
||||
comment: 'The system of the Instance.'
|
||||
})
|
||||
public system: string | null;
|
||||
|
||||
/**
|
||||
* インスタンスのユーザー数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
comment: 'The count of the users of the Instance.'
|
||||
})
|
||||
public usersCount: number;
|
||||
|
||||
/**
|
||||
* インスタンスの投稿数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
comment: 'The count of the notes of the Instance.'
|
||||
})
|
||||
public notesCount: number;
|
||||
|
||||
/**
|
||||
* このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public followingCount: number;
|
||||
|
||||
/**
|
||||
* このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public followersCount: number;
|
||||
|
||||
/**
|
||||
* ドライブ使用量
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public driveUsage: number;
|
||||
|
||||
/**
|
||||
* ドライブのファイル数
|
||||
*/
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
})
|
||||
public driveFiles: number;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信日時
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestRequestSentAt: Date | null;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信時のHTTPステータスコード
|
||||
*/
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestStatus: number | null;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト受信日時
|
||||
*/
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
})
|
||||
public latestRequestReceivedAt: Date | null;
|
||||
|
||||
/**
|
||||
* このインスタンスと最後にやり取りした日時
|
||||
*/
|
||||
@Column('timestamp with time zone')
|
||||
public lastCommunicatedAt: Date;
|
||||
|
||||
/**
|
||||
* このインスタンスと不通かどうか
|
||||
*/
|
||||
@Column('boolean', {
|
||||
default: false
|
||||
})
|
||||
public isNotResponding: boolean;
|
||||
|
||||
/**
|
||||
* このインスタンスが閉鎖済みとしてマークされているか
|
||||
*/
|
||||
@Column('boolean', {
|
||||
default: false
|
||||
})
|
||||
public isMarkedAsClosed: boolean;
|
||||
}
|
46
src/models/entities/log.ts
Normal file
46
src/models/entities/log.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { Entity, PrimaryColumn, Index, Column } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Log {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Log.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 64, array: true, default: '{}'
|
||||
})
|
||||
public domain: string[];
|
||||
|
||||
@Index()
|
||||
@Column('enum', {
|
||||
enum: ['error', 'warning', 'info', 'success', 'debug']
|
||||
})
|
||||
public level: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 8
|
||||
})
|
||||
public worker: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128
|
||||
})
|
||||
public machine: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024
|
||||
})
|
||||
public message: string;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: {}
|
||||
})
|
||||
public data: Record<string, any>;
|
||||
}
|
64
src/models/entities/messaging-message.ts
Normal file
64
src/models/entities/messaging-message.ts
Normal file
|
@ -0,0 +1,64 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { DriveFile } from './drive-file';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class MessagingMessage {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the MessagingMessage.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The sender user ID.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The recipient user ID.'
|
||||
})
|
||||
public recipientId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public recipient: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 4096, nullable: true
|
||||
})
|
||||
public text: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isRead: boolean;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
})
|
||||
public fileId: DriveFile['id'] | null;
|
||||
|
||||
@ManyToOne(type => DriveFile, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public file: DriveFile | null;
|
||||
}
|
264
src/models/entities/meta.ts
Normal file
264
src/models/entities/meta.ts
Normal file
|
@ -0,0 +1,264 @@
|
|||
import { Entity, Column, PrimaryColumn } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Meta {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public name: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true
|
||||
})
|
||||
public description: string | null;
|
||||
|
||||
/**
|
||||
* メンテナの名前
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public maintainerName: string | null;
|
||||
|
||||
/**
|
||||
* メンテナの連絡先
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public maintainerEmail: string | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: [],
|
||||
})
|
||||
public announcements: Record<string, any>[];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public disableRegistration: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public disableLocalTimeline: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public disableGlobalTimeline: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public enableEmojiReaction: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public useStarForReactionFallback: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, array: true, default: '{}'
|
||||
})
|
||||
public langs: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}'
|
||||
})
|
||||
public hiddenTags: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}'
|
||||
})
|
||||
public blockedHosts: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
nullable: true,
|
||||
default: '/assets/ai.png'
|
||||
})
|
||||
public mascotImageUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
nullable: true
|
||||
})
|
||||
public bannerUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
nullable: true,
|
||||
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
|
||||
})
|
||||
public errorImageUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
nullable: true
|
||||
})
|
||||
public iconUrl: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: true,
|
||||
})
|
||||
public cacheRemoteFiles: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public proxyAccount: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableRecaptcha: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64,
|
||||
nullable: true
|
||||
})
|
||||
public recaptchaSiteKey: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64,
|
||||
nullable: true
|
||||
})
|
||||
public recaptchaSecretKey: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
default: 1024,
|
||||
comment: 'Drive capacity of a local user (MB)'
|
||||
})
|
||||
public localDriveCapacityMb: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 32,
|
||||
comment: 'Drive capacity of a remote user (MB)'
|
||||
})
|
||||
public remoteDriveCapacityMb: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 500,
|
||||
comment: 'Max allowed note text length in characters'
|
||||
})
|
||||
public maxNoteTextLength: number;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public summalyProxy: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableEmail: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public email: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public smtpSecure: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public smtpHost: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: true
|
||||
})
|
||||
public smtpPort: number | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public smtpUser: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public smtpPass: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableServiceWorker: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public swPublicKey: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public swPrivateKey: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableTwitterIntegration: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public twitterConsumerKey: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public twitterConsumerSecret: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableGithubIntegration: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public githubClientId: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public githubClientSecret: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public enableDiscordIntegration: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public discordClientId: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
nullable: true
|
||||
})
|
||||
public discordClientSecret: string | null;
|
||||
}
|
42
src/models/entities/muting.ts
Normal file
42
src/models/entities/muting.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['muterId', 'muteeId'], { unique: true })
|
||||
export class Muting {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Muting.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The mutee user ID.'
|
||||
})
|
||||
public muteeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public mutee: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The muter user ID.'
|
||||
})
|
||||
public muterId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public muter: User | null;
|
||||
}
|
35
src/models/entities/note-favorite.ts
Normal file
35
src/models/entities/note-favorite.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Note } from './note';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class NoteFavorite {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the NoteFavorite.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
}
|
42
src/models/entities/note-reaction.ts
Normal file
42
src/models/entities/note-reaction.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { Note } from './note';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class NoteReaction {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the NoteReaction.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32
|
||||
})
|
||||
public reaction: string;
|
||||
}
|
43
src/models/entities/note-unread.ts
Normal file
43
src/models/entities/note-unread.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { Note } from './note';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class NoteUnread {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public noteUserId: User['id'];
|
||||
|
||||
/**
|
||||
* ダイレクト投稿か
|
||||
*/
|
||||
@Column('boolean')
|
||||
public isSpecified: boolean;
|
||||
}
|
52
src/models/entities/note-watching.ts
Normal file
52
src/models/entities/note-watching.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { Note } from './note';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class NoteWatching {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the NoteWatching.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The watcher ID.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The target Note ID.'
|
||||
})
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public noteUserId: Note['userId'];
|
||||
//#endregion
|
||||
}
|
236
src/models/entities/note.ts
Normal file
236
src/models/entities/note.ts
Normal file
|
@ -0,0 +1,236 @@
|
|||
import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { App } from './app';
|
||||
import { DriveFile } from './drive-file';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Note {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Note.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
comment: 'The updated date of the Note.'
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The ID of reply target.'
|
||||
})
|
||||
public replyId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public reply: Note | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The ID of renote target.'
|
||||
})
|
||||
public renoteId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public renote: Note | null;
|
||||
|
||||
@Column({
|
||||
type: 'text', nullable: true
|
||||
})
|
||||
public text: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true
|
||||
})
|
||||
public name: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 512, nullable: true
|
||||
})
|
||||
public cw: string | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true
|
||||
})
|
||||
public appId: App['id'] | null;
|
||||
|
||||
@ManyToOne(type => App, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public app: App | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The ID of author.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false
|
||||
})
|
||||
public viaMobile: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false
|
||||
})
|
||||
public localOnly: boolean;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public renoteCount: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public repliesCount: number;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: {}
|
||||
})
|
||||
public reactions: Record<string, number>;
|
||||
|
||||
/**
|
||||
* public ... 公開
|
||||
* home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
|
||||
* followers ... フォロワーのみ
|
||||
* specified ... visibleUserIds で指定したユーザーのみ
|
||||
*/
|
||||
@Column('enum', { enum: ['public', 'home', 'followers', 'specified'] })
|
||||
public visibility: 'public' | 'home' | 'followers' | 'specified';
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The URI of a note. it will be null when the note is local.'
|
||||
})
|
||||
public uri: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0
|
||||
})
|
||||
public score: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
array: true, default: '{}'
|
||||
})
|
||||
public fileIds: DriveFile['id'][];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, array: true, default: '{}'
|
||||
})
|
||||
public attachedFileTypes: string[];
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
array: true, default: '{}'
|
||||
})
|
||||
public visibleUserIds: User['id'][];
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
array: true, default: '{}'
|
||||
})
|
||||
public mentions: User['id'][];
|
||||
|
||||
@Column('text', {
|
||||
default: '[]'
|
||||
})
|
||||
public mentionedRemoteUsers: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
public emojis: string[];
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
public tags: string[];
|
||||
|
||||
@Column('boolean', {
|
||||
default: false
|
||||
})
|
||||
public hasPoll: boolean;
|
||||
|
||||
@Column('jsonb', {
|
||||
nullable: true, default: {}
|
||||
})
|
||||
public geo: any | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public userHost: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public userInbox: string | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public replyUserId: User['id'] | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public replyUserHost: string | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public renoteUserId: User['id'] | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public renoteUserHost: string | null;
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export type IMentionedRemoteUsers = {
|
||||
uri: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}[];
|
94
src/models/entities/notification.ts
Normal file
94
src/models/entities/notification.ts
Normal file
|
@ -0,0 +1,94 @@
|
|||
import { Entity, Index, JoinColumn, ManyToOne, Column, PrimaryColumn } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
import { Note } from './note';
|
||||
|
||||
@Entity()
|
||||
export class Notification {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Notification.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
/**
|
||||
* 通知の受信者
|
||||
*/
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The ID of recipient user of the Notification.'
|
||||
})
|
||||
public notifieeId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public notifiee: User | null;
|
||||
|
||||
/**
|
||||
* 通知の送信者(initiator)
|
||||
*/
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The ID of sender user of the Notification.'
|
||||
})
|
||||
public notifierId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public notifier: User | null;
|
||||
|
||||
/**
|
||||
* 通知の種類。
|
||||
* follow - フォローされた
|
||||
* mention - 投稿で自分が言及された
|
||||
* reply - (自分または自分がWatchしている)投稿が返信された
|
||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
||||
* pollVote - (自分または自分がWatchしている)投稿の投票に投票された
|
||||
*/
|
||||
@Column('varchar', {
|
||||
length: 32,
|
||||
comment: 'The type of the Notification.'
|
||||
})
|
||||
public type: string;
|
||||
|
||||
/**
|
||||
* 通知が読まれたかどうか
|
||||
*/
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the Notification is read.'
|
||||
})
|
||||
public isRead: boolean;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true
|
||||
})
|
||||
public noteId: Note['id'] | null;
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true
|
||||
})
|
||||
public reaction: string;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: true
|
||||
})
|
||||
public choice: number;
|
||||
}
|
40
src/models/entities/poll-vote.ts
Normal file
40
src/models/entities/poll-vote.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { Note } from './note';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId', 'choice'], { unique: true })
|
||||
export class PollVote {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the PollVote.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Column('integer')
|
||||
public choice: number;
|
||||
}
|
67
src/models/entities/poll.ts
Normal file
67
src/models/entities/poll.ts
Normal file
|
@ -0,0 +1,67 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
import { Note } from './note';
|
||||
import { User } from './user';
|
||||
|
||||
@Entity()
|
||||
export class Poll {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@OneToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true
|
||||
})
|
||||
public expiresAt: Date | null;
|
||||
|
||||
@Column('boolean')
|
||||
public multiple: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
public choices: string[];
|
||||
|
||||
@Column('integer', {
|
||||
array: true,
|
||||
})
|
||||
public votes: number[];
|
||||
|
||||
//#region Denormalized fields
|
||||
@Column('enum', {
|
||||
enum: ['public', 'home', 'followers', 'specified'],
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public noteVisibility: 'public' | 'home' | 'followers' | 'specified';
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public userHost: string | null;
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export type IPoll = {
|
||||
choices: string[];
|
||||
votes?: number[];
|
||||
multiple: boolean;
|
||||
expiresAt: Date;
|
||||
};
|
17
src/models/entities/registration-tickets.ts
Normal file
17
src/models/entities/registration-tickets.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class RegistrationTicket {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 64,
|
||||
})
|
||||
public code: string;
|
||||
}
|
35
src/models/entities/signin.ts
Normal file
35
src/models/entities/signin.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class Signin {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the Signin.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
})
|
||||
public ip: string;
|
||||
|
||||
@Column('jsonb')
|
||||
public headers: Record<string, any>;
|
||||
|
||||
@Column('boolean')
|
||||
public success: boolean;
|
||||
}
|
37
src/models/entities/sw-subscription.ts
Normal file
37
src/models/entities/sw-subscription.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class SwSubscription {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone')
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public endpoint: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public auth: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
})
|
||||
public publickey: string;
|
||||
}
|
24
src/models/entities/user-keypair.ts
Normal file
24
src/models/entities/user-keypair.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class UserKeypair {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 4096,
|
||||
})
|
||||
public keyPem: string;
|
||||
}
|
41
src/models/entities/user-list-joining.ts
Normal file
41
src/models/entities/user-list-joining.ts
Normal file
|
@ -0,0 +1,41 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { UserList } from './user-list';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class UserListJoining {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserListJoining.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The user ID.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The list ID.'
|
||||
})
|
||||
public userListId: UserList['id'];
|
||||
|
||||
@ManyToOne(type => UserList, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public userList: UserList | null;
|
||||
}
|
33
src/models/entities/user-list.ts
Normal file
33
src/models/entities/user-list.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class UserList {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserList.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column({
|
||||
...id(),
|
||||
comment: 'The owner ID.'
|
||||
})
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The name of the UserList.'
|
||||
})
|
||||
public name: string;
|
||||
}
|
35
src/models/entities/user-note-pinings.ts
Normal file
35
src/models/entities/user-note-pinings.ts
Normal file
|
@ -0,0 +1,35 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm';
|
||||
import { Note } from './note';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['userId', 'noteId'], { unique: true })
|
||||
export class UserNotePining {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the UserNotePinings.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@ManyToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column(id())
|
||||
public noteId: Note['id'];
|
||||
|
||||
@ManyToOne(type => Note, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public note: Note | null;
|
||||
}
|
30
src/models/entities/user-publickey.ts
Normal file
30
src/models/entities/user-publickey.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class UserPublickey {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 256,
|
||||
})
|
||||
public keyId: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 4096,
|
||||
})
|
||||
public keyPem: string;
|
||||
}
|
108
src/models/entities/user-service-linking.ts
Normal file
108
src/models/entities/user-service-linking.ts
Normal file
|
@ -0,0 +1,108 @@
|
|||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
|
||||
import { User } from './user';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
export class UserServiceLinking {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column(id())
|
||||
public userId: User['id'];
|
||||
|
||||
@OneToOne(type => User, {
|
||||
onDelete: 'CASCADE'
|
||||
})
|
||||
@JoinColumn()
|
||||
public user: User | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public twitter: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public twitterAccessToken: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public twitterAccessTokenSecret: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public twitterUserId: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public twitterScreenName: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public github: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public githubAccessToken: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: true, default: null,
|
||||
})
|
||||
public githubId: number | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public githubLogin: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public discord: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public discordAccessToken: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public discordRefreshToken: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
nullable: true, default: null,
|
||||
})
|
||||
public discordExpiresDate: number | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public discordId: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public discordUsername: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 64, nullable: true, default: null,
|
||||
})
|
||||
public discordDiscriminator: string | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: '[Denormalized]'
|
||||
})
|
||||
public userHost: string | null;
|
||||
//#endregion
|
||||
}
|
297
src/models/entities/user.ts
Normal file
297
src/models/entities/user.ts
Normal file
|
@ -0,0 +1,297 @@
|
|||
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||
import { DriveFile } from './drive-file';
|
||||
import { id } from '../id';
|
||||
|
||||
@Entity()
|
||||
@Index(['usernameLower', 'host'], { unique: true })
|
||||
export class User {
|
||||
@PrimaryColumn(id())
|
||||
public id: string;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
comment: 'The created date of the User.'
|
||||
})
|
||||
public createdAt: Date;
|
||||
|
||||
@Index()
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true,
|
||||
comment: 'The updated date of the User.'
|
||||
})
|
||||
public updatedAt: Date | null;
|
||||
|
||||
@Column('timestamp with time zone', {
|
||||
nullable: true
|
||||
})
|
||||
public lastFetchedAt: Date | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128,
|
||||
comment: 'The username of the User.'
|
||||
})
|
||||
public username: string;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, select: false,
|
||||
comment: 'The username (lowercased) of the User.'
|
||||
})
|
||||
public usernameLower: string;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The name of the User.'
|
||||
})
|
||||
public name: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The location of the User.'
|
||||
})
|
||||
public location: string | null;
|
||||
|
||||
@Column('char', {
|
||||
length: 10, nullable: true,
|
||||
comment: 'The birthday (YYYY-MM-DD) of the User.'
|
||||
})
|
||||
public birthday: string | null;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
comment: 'The count of followers.'
|
||||
})
|
||||
public followersCount: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
comment: 'The count of following.'
|
||||
})
|
||||
public followingCount: number;
|
||||
|
||||
@Column('integer', {
|
||||
default: 0,
|
||||
comment: 'The count of notes.'
|
||||
})
|
||||
public notesCount: number;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The ID of avatar DriveFile.'
|
||||
})
|
||||
public avatarId: DriveFile['id'] | null;
|
||||
|
||||
@OneToOne(type => DriveFile, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public avatar: DriveFile | null;
|
||||
|
||||
@Column({
|
||||
...id(),
|
||||
nullable: true,
|
||||
comment: 'The ID of banner DriveFile.'
|
||||
})
|
||||
public bannerId: DriveFile['id'] | null;
|
||||
|
||||
@OneToOne(type => DriveFile, {
|
||||
onDelete: 'SET NULL'
|
||||
})
|
||||
@JoinColumn()
|
||||
public banner: DriveFile | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 1024, nullable: true,
|
||||
comment: 'The description (bio) of the User.'
|
||||
})
|
||||
public description: string | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
public tags: string[];
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The email address of the User.'
|
||||
})
|
||||
public email: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public emailVerifyCode: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public emailVerified: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public twoFactorTempSecret: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public twoFactorSecret: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
})
|
||||
public avatarUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
})
|
||||
public bannerUrl: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: true,
|
||||
})
|
||||
public avatarColor: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: true,
|
||||
})
|
||||
public bannerColor: string | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is suspended.'
|
||||
})
|
||||
public isSuspended: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is silenced.'
|
||||
})
|
||||
public isSilenced: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is locked.'
|
||||
})
|
||||
public isLocked: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is a bot.'
|
||||
})
|
||||
public isBot: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is a cat.'
|
||||
})
|
||||
public isCat: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is the admin.'
|
||||
})
|
||||
public isAdmin: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
comment: 'Whether the User is a moderator.'
|
||||
})
|
||||
public isModerator: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public isVerified: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public twoFactorEnabled: boolean;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, array: true, default: '{}'
|
||||
})
|
||||
public emojis: string[];
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The host of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public host: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The inbox of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public inbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The sharedInbox of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public sharedInbox: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The featured of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public featured: string | null;
|
||||
|
||||
@Index()
|
||||
@Column('varchar', {
|
||||
length: 256, nullable: true,
|
||||
comment: 'The URI of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public uri: string | null;
|
||||
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
comment: 'The password hash of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public password: string | null;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column('varchar', {
|
||||
length: 32, nullable: true, unique: true,
|
||||
comment: 'The native access token of the User. It will be null if the origin of the user is local.'
|
||||
})
|
||||
public token: string | null;
|
||||
|
||||
@Column('jsonb', {
|
||||
default: {},
|
||||
comment: 'The client-specific data of the User.'
|
||||
})
|
||||
public clientData: Record<string, any>;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public autoWatch: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public autoAcceptFollowed: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public alwaysMarkNsfw: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public carefulBot: boolean;
|
||||
}
|
||||
|
||||
export interface ILocalUser extends User {
|
||||
host: null;
|
||||
}
|
||||
|
||||
export interface IRemoteUser extends User {
|
||||
host: string;
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packNote } from './note';
|
||||
import { dbLogger } from '../db/logger';
|
||||
|
||||
const Favorite = db.get<IFavorite>('favorites');
|
||||
Favorite.createIndex('userId');
|
||||
Favorite.createIndex(['userId', 'noteId'], { unique: true });
|
||||
export default Favorite;
|
||||
|
||||
export type IFavorite = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID;
|
||||
noteId: mongo.ObjectID;
|
||||
};
|
||||
|
||||
export const packMany = (
|
||||
favorites: any[],
|
||||
me: any
|
||||
) => {
|
||||
return Promise.all(favorites.map(f => pack(f, me)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a favorite for API response
|
||||
*/
|
||||
export const pack = (
|
||||
favorite: any,
|
||||
me: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _favorite: any;
|
||||
|
||||
// Populate the favorite if 'favorite' is ID
|
||||
if (isObjectId(favorite)) {
|
||||
_favorite = await Favorite.findOne({
|
||||
_id: favorite
|
||||
});
|
||||
} else if (typeof favorite === 'string') {
|
||||
_favorite = await Favorite.findOne({
|
||||
_id: new mongo.ObjectID(favorite)
|
||||
});
|
||||
} else {
|
||||
_favorite = deepcopy(favorite);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_favorite.id = _favorite._id;
|
||||
delete _favorite._id;
|
||||
|
||||
// Populate note
|
||||
_favorite.note = await packNote(_favorite.noteId, me, {
|
||||
detail: true
|
||||
});
|
||||
|
||||
// (データベースの不具合などで)投稿が見つからなかったら
|
||||
if (_favorite.note == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: favorite -> note :: ${_favorite.id} (note ${_favorite.noteId})`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
resolve(_favorite);
|
||||
});
|
|
@ -1,66 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
const FollowRequest = db.get<IFollowRequest>('followRequests');
|
||||
FollowRequest.createIndex('followerId');
|
||||
FollowRequest.createIndex('followeeId');
|
||||
FollowRequest.createIndex(['followerId', 'followeeId'], { unique: true });
|
||||
export default FollowRequest;
|
||||
|
||||
export type IFollowRequest = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
followeeId: mongo.ObjectID;
|
||||
followerId: mongo.ObjectID;
|
||||
requestId?: string; // id of Follow Activity
|
||||
|
||||
// 非正規化
|
||||
_followee: {
|
||||
host: string;
|
||||
inbox?: string;
|
||||
sharedInbox?: string;
|
||||
},
|
||||
_follower: {
|
||||
host: string;
|
||||
inbox?: string;
|
||||
sharedInbox?: string;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a request for API response
|
||||
*/
|
||||
export const pack = (
|
||||
request: any,
|
||||
me?: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _request: any;
|
||||
|
||||
// Populate the request if 'request' is ID
|
||||
if (isObjectId(request)) {
|
||||
_request = await FollowRequest.findOne({
|
||||
_id: request
|
||||
});
|
||||
} else if (typeof request === 'string') {
|
||||
_request = await FollowRequest.findOne({
|
||||
_id: new mongo.ObjectID(request)
|
||||
});
|
||||
} else {
|
||||
_request = deepcopy(request);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_request.id = _request._id;
|
||||
delete _request._id;
|
||||
|
||||
// Populate follower
|
||||
_request.follower = await packUser(_request.followerId, me);
|
||||
|
||||
// Populate followee
|
||||
_request.followee = await packUser(_request.followeeId, me);
|
||||
|
||||
resolve(_request);
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Following = db.get<IFollowing>('following');
|
||||
Following.createIndex('followerId');
|
||||
Following.createIndex('followeeId');
|
||||
Following.createIndex(['followerId', 'followeeId'], { unique: true });
|
||||
export default Following;
|
||||
|
||||
export type IFollowing = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
followeeId: mongo.ObjectID;
|
||||
followerId: mongo.ObjectID;
|
||||
|
||||
// 非正規化
|
||||
_followee: {
|
||||
host: string;
|
||||
inbox?: string;
|
||||
sharedInbox?: string;
|
||||
},
|
||||
_follower: {
|
||||
host: string;
|
||||
inbox?: string;
|
||||
sharedInbox?: string;
|
||||
}
|
||||
};
|
|
@ -1,111 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../../../db/mongodb';
|
||||
import isObjectId from '../../../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from '../../user';
|
||||
|
||||
const ReversiGame = db.get<IReversiGame>('reversiGames');
|
||||
export default ReversiGame;
|
||||
|
||||
export interface IReversiGame {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
startedAt: Date;
|
||||
user1Id: mongo.ObjectID;
|
||||
user2Id: mongo.ObjectID;
|
||||
user1Accepted: boolean;
|
||||
user2Accepted: boolean;
|
||||
|
||||
/**
|
||||
* どちらのプレイヤーが先行(黒)か
|
||||
* 1 ... user1
|
||||
* 2 ... user2
|
||||
*/
|
||||
black: number;
|
||||
|
||||
isStarted: boolean;
|
||||
isEnded: boolean;
|
||||
winnerId: mongo.ObjectID;
|
||||
surrendered: mongo.ObjectID;
|
||||
logs: {
|
||||
at: Date;
|
||||
color: boolean;
|
||||
pos: number;
|
||||
}[];
|
||||
settings: {
|
||||
map: string[];
|
||||
bw: string | number;
|
||||
isLlotheo: boolean;
|
||||
canPutEverywhere: boolean;
|
||||
loopedBoard: boolean;
|
||||
};
|
||||
form1: any;
|
||||
form2: any;
|
||||
|
||||
// ログのposを文字列としてすべて連結したもののCRC32値
|
||||
crc32: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack an reversi game for API response
|
||||
*/
|
||||
export const pack = (
|
||||
game: any,
|
||||
me?: string | mongo.ObjectID | IUser,
|
||||
options?: {
|
||||
detail?: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = Object.assign({
|
||||
detail: true
|
||||
}, options);
|
||||
|
||||
let _game: any;
|
||||
|
||||
// Populate the game if 'game' is ID
|
||||
if (isObjectId(game)) {
|
||||
_game = await ReversiGame.findOne({
|
||||
_id: game
|
||||
});
|
||||
} else if (typeof game === 'string') {
|
||||
_game = await ReversiGame.findOne({
|
||||
_id: new mongo.ObjectID(game)
|
||||
});
|
||||
} else {
|
||||
_game = deepcopy(game);
|
||||
}
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
: (me as IUser)._id
|
||||
: null;
|
||||
|
||||
// Rename _id to id
|
||||
_game.id = _game._id;
|
||||
delete _game._id;
|
||||
|
||||
if (opts.detail === false) {
|
||||
delete _game.logs;
|
||||
delete _game.settings.map;
|
||||
} else {
|
||||
// 互換性のため
|
||||
if (_game.settings.map.hasOwnProperty('size')) {
|
||||
_game.settings.map = _game.settings.map.data.match(new RegExp(`.{1,${_game.settings.map.size}}`, 'g'));
|
||||
}
|
||||
}
|
||||
|
||||
// Populate user
|
||||
_game.user1 = await packUser(_game.user1Id, meId);
|
||||
_game.user2 = await packUser(_game.user2Id, meId);
|
||||
if (_game.winnerId) {
|
||||
_game.winner = await packUser(_game.winnerId, meId);
|
||||
} else {
|
||||
_game.winner = null;
|
||||
}
|
||||
|
||||
resolve(_game);
|
||||
});
|
|
@ -1,45 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../../../db/mongodb';
|
||||
import isObjectId from '../../../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from '../../user';
|
||||
|
||||
const Matching = db.get<IMatching>('reversiMatchings');
|
||||
export default Matching;
|
||||
|
||||
export interface IMatching {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
parentId: mongo.ObjectID;
|
||||
childId: mongo.ObjectID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack an reversi matching for API response
|
||||
*/
|
||||
export const pack = (
|
||||
matching: any,
|
||||
me?: string | mongo.ObjectID | IUser
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
: (me as IUser)._id
|
||||
: null;
|
||||
|
||||
const _matching = deepcopy(matching);
|
||||
|
||||
// Rename _id to id
|
||||
_matching.id = _matching._id;
|
||||
delete _matching._id;
|
||||
|
||||
// Populate user
|
||||
_matching.parent = await packUser(_matching.parentId, meId);
|
||||
_matching.child = await packUser(_matching.childId, meId);
|
||||
|
||||
resolve(_matching);
|
||||
});
|
|
@ -1,63 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Hashtag = db.get<IHashtags>('hashtags');
|
||||
Hashtag.createIndex('tag', { unique: true });
|
||||
Hashtag.createIndex('mentionedUsersCount');
|
||||
Hashtag.createIndex('mentionedLocalUsersCount');
|
||||
Hashtag.createIndex('mentionedRemoteUsersCount');
|
||||
Hashtag.createIndex('attachedUsersCount');
|
||||
Hashtag.createIndex('attachedLocalUsersCount');
|
||||
Hashtag.createIndex('attachedRemoteUsersCount');
|
||||
export default Hashtag;
|
||||
|
||||
// 後方互換性のため
|
||||
Hashtag.findOne({ attachedUserIds: { $exists: false }}).then(h => {
|
||||
if (h != null) {
|
||||
Hashtag.update({}, {
|
||||
$rename: {
|
||||
mentionedUserIdsCount: 'mentionedUsersCount'
|
||||
},
|
||||
$set: {
|
||||
mentionedLocalUserIds: [],
|
||||
mentionedLocalUsersCount: 0,
|
||||
attachedUserIds: [],
|
||||
attachedUsersCount: 0,
|
||||
attachedLocalUserIds: [],
|
||||
attachedLocalUsersCount: 0,
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
});
|
||||
}
|
||||
});
|
||||
Hashtag.findOne({ attachedRemoteUserIds: { $exists: false }}).then(h => {
|
||||
if (h != null) {
|
||||
Hashtag.update({}, {
|
||||
$set: {
|
||||
mentionedRemoteUserIds: [],
|
||||
mentionedRemoteUsersCount: 0,
|
||||
attachedRemoteUserIds: [],
|
||||
attachedRemoteUsersCount: 0,
|
||||
}
|
||||
}, {
|
||||
multi: true
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export interface IHashtags {
|
||||
tag: string;
|
||||
mentionedUserIds: mongo.ObjectID[];
|
||||
mentionedUsersCount: number;
|
||||
mentionedLocalUserIds: mongo.ObjectID[];
|
||||
mentionedLocalUsersCount: number;
|
||||
mentionedRemoteUserIds: mongo.ObjectID[];
|
||||
mentionedRemoteUsersCount: number;
|
||||
attachedUserIds: mongo.ObjectID[];
|
||||
attachedUsersCount: number;
|
||||
attachedLocalUserIds: mongo.ObjectID[];
|
||||
attachedLocalUsersCount: number;
|
||||
attachedRemoteUserIds: mongo.ObjectID[];
|
||||
attachedRemoteUsersCount: number;
|
||||
}
|
4
src/models/id.ts
Normal file
4
src/models/id.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
export const id = () => ({
|
||||
type: 'varchar' as 'varchar',
|
||||
length: 32
|
||||
});
|
74
src/models/index.ts
Normal file
74
src/models/index.ts
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { getRepository, getCustomRepository } from 'typeorm';
|
||||
import { Instance } from './entities/instance';
|
||||
import { Emoji } from './entities/emoji';
|
||||
import { Poll } from './entities/poll';
|
||||
import { PollVote } from './entities/poll-vote';
|
||||
import { Meta } from './entities/meta';
|
||||
import { SwSubscription } from './entities/sw-subscription';
|
||||
import { NoteWatching } from './entities/note-watching';
|
||||
import { UserListJoining } from './entities/user-list-joining';
|
||||
import { Hashtag } from './entities/hashtag';
|
||||
import { NoteUnread } from './entities/note-unread';
|
||||
import { RegistrationTicket } from './entities/registration-tickets';
|
||||
import { UserRepository } from './repositories/user';
|
||||
import { NoteRepository } from './repositories/note';
|
||||
import { DriveFileRepository } from './repositories/drive-file';
|
||||
import { DriveFolderRepository } from './repositories/drive-folder';
|
||||
import { Log } from './entities/log';
|
||||
import { AccessToken } from './entities/access-token';
|
||||
import { UserNotePining } from './entities/user-note-pinings';
|
||||
import { SigninRepository } from './repositories/signin';
|
||||
import { MessagingMessageRepository } from './repositories/messaging-message';
|
||||
import { ReversiGameRepository } from './repositories/games/reversi/game';
|
||||
import { UserListRepository } from './repositories/user-list';
|
||||
import { FollowRequestRepository } from './repositories/follow-request';
|
||||
import { MutingRepository } from './repositories/muting';
|
||||
import { BlockingRepository } from './repositories/blocking';
|
||||
import { NoteReactionRepository } from './repositories/note-reaction';
|
||||
import { UserServiceLinking } from './entities/user-service-linking';
|
||||
import { NotificationRepository } from './repositories/notification';
|
||||
import { NoteFavoriteRepository } from './repositories/note-favorite';
|
||||
import { ReversiMatchingRepository } from './repositories/games/reversi/matching';
|
||||
import { UserPublickey } from './entities/user-publickey';
|
||||
import { UserKeypair } from './entities/user-keypair';
|
||||
import { AppRepository } from './repositories/app';
|
||||
import { FollowingRepository } from './repositories/following';
|
||||
import { AbuseUserReportRepository } from './repositories/abuse-user-report';
|
||||
import { AuthSessionRepository } from './repositories/auth-session';
|
||||
|
||||
export const Apps = getCustomRepository(AppRepository);
|
||||
export const Notes = getCustomRepository(NoteRepository);
|
||||
export const NoteFavorites = getCustomRepository(NoteFavoriteRepository);
|
||||
export const NoteWatchings = getRepository(NoteWatching);
|
||||
export const NoteReactions = getCustomRepository(NoteReactionRepository);
|
||||
export const NoteUnreads = getRepository(NoteUnread);
|
||||
export const Polls = getRepository(Poll);
|
||||
export const PollVotes = getRepository(PollVote);
|
||||
export const Users = getCustomRepository(UserRepository);
|
||||
export const UserKeypairs = getRepository(UserKeypair);
|
||||
export const UserPublickeys = getRepository(UserPublickey);
|
||||
export const UserLists = getCustomRepository(UserListRepository);
|
||||
export const UserListJoinings = getRepository(UserListJoining);
|
||||
export const UserNotePinings = getRepository(UserNotePining);
|
||||
export const UserServiceLinkings = getRepository(UserServiceLinking);
|
||||
export const Followings = getCustomRepository(FollowingRepository);
|
||||
export const FollowRequests = getCustomRepository(FollowRequestRepository);
|
||||
export const Instances = getRepository(Instance);
|
||||
export const Emojis = getRepository(Emoji);
|
||||
export const DriveFiles = getCustomRepository(DriveFileRepository);
|
||||
export const DriveFolders = getCustomRepository(DriveFolderRepository);
|
||||
export const Notifications = getCustomRepository(NotificationRepository);
|
||||
export const Metas = getRepository(Meta);
|
||||
export const Mutings = getCustomRepository(MutingRepository);
|
||||
export const Blockings = getCustomRepository(BlockingRepository);
|
||||
export const SwSubscriptions = getRepository(SwSubscription);
|
||||
export const Hashtags = getRepository(Hashtag);
|
||||
export const AbuseUserReports = getCustomRepository(AbuseUserReportRepository);
|
||||
export const RegistrationTickets = getRepository(RegistrationTicket);
|
||||
export const AuthSessions = getCustomRepository(AuthSessionRepository);
|
||||
export const AccessTokens = getRepository(AccessToken);
|
||||
export const Signins = getCustomRepository(SigninRepository);
|
||||
export const MessagingMessages = getCustomRepository(MessagingMessageRepository);
|
||||
export const ReversiGames = getCustomRepository(ReversiGameRepository);
|
||||
export const ReversiMatchings = getCustomRepository(ReversiMatchingRepository);
|
||||
export const Logs = getRepository(Log);
|
|
@ -1,90 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Instance = db.get<IInstance>('instances');
|
||||
Instance.createIndex('host', { unique: true });
|
||||
export default Instance;
|
||||
|
||||
export interface IInstance {
|
||||
_id: mongo.ObjectID;
|
||||
|
||||
/**
|
||||
* ホスト
|
||||
*/
|
||||
host: string;
|
||||
|
||||
/**
|
||||
* このインスタンスを捕捉した日時
|
||||
*/
|
||||
caughtAt: Date;
|
||||
|
||||
/**
|
||||
* このインスタンスのシステム (MastodonとかMisskeyとかPleromaとか)
|
||||
*/
|
||||
system: string;
|
||||
|
||||
/**
|
||||
* このインスタンスのユーザー数
|
||||
*/
|
||||
usersCount: number;
|
||||
|
||||
/**
|
||||
* このインスタンスから受け取った投稿数
|
||||
*/
|
||||
notesCount: number;
|
||||
|
||||
/**
|
||||
* このインスタンスのユーザーからフォローされている、自インスタンスのユーザーの数
|
||||
*/
|
||||
followingCount: number;
|
||||
|
||||
/**
|
||||
* このインスタンスのユーザーをフォローしている、自インスタンスのユーザーの数
|
||||
*/
|
||||
followersCount: number;
|
||||
|
||||
/**
|
||||
* ドライブ使用量
|
||||
*/
|
||||
driveUsage: number;
|
||||
|
||||
/**
|
||||
* ドライブのファイル数
|
||||
*/
|
||||
driveFiles: number;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信日時
|
||||
*/
|
||||
latestRequestSentAt?: Date;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト送信時のHTTPステータスコード
|
||||
*/
|
||||
latestStatus?: number;
|
||||
|
||||
/**
|
||||
* 直近のリクエスト受信日時
|
||||
*/
|
||||
latestRequestReceivedAt?: Date;
|
||||
|
||||
/**
|
||||
* このインスタンスと不通かどうか
|
||||
*/
|
||||
isNotResponding: boolean;
|
||||
|
||||
/**
|
||||
* このインスタンスと最後にやり取りした日時
|
||||
*/
|
||||
lastCommunicatedAt: Date;
|
||||
|
||||
/**
|
||||
* このインスタンスをブロックしているか
|
||||
*/
|
||||
isBlocked: boolean;
|
||||
|
||||
/**
|
||||
* このインスタンスが閉鎖済みとしてマークされているか
|
||||
*/
|
||||
isMarkedAsClosed: boolean;
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Log = db.get<ILog>('logs');
|
||||
Log.createIndex('createdAt', { expireAfterSeconds: 3600 * 24 * 3 });
|
||||
Log.createIndex('level');
|
||||
Log.createIndex('domain');
|
||||
export default Log;
|
||||
|
||||
export interface ILog {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
machine: string;
|
||||
worker: string;
|
||||
domain: string[];
|
||||
level: string;
|
||||
message: string;
|
||||
data: any;
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import { pack as packUser } from './user';
|
||||
import { pack as packFile } from './drive-file';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { length } from 'stringz';
|
||||
|
||||
const MessagingMessage = db.get<IMessagingMessage>('messagingMessages');
|
||||
MessagingMessage.createIndex('userId');
|
||||
MessagingMessage.createIndex('recipientId');
|
||||
export default MessagingMessage;
|
||||
|
||||
export interface IMessagingMessage {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
text: string;
|
||||
userId: mongo.ObjectID;
|
||||
recipientId: mongo.ObjectID;
|
||||
isRead: boolean;
|
||||
fileId: mongo.ObjectID;
|
||||
}
|
||||
|
||||
export function isValidText(text: string): boolean {
|
||||
return length(text.trim()) <= 1000 && text.trim() != '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a messaging message for API response
|
||||
*/
|
||||
export const pack = (
|
||||
message: any,
|
||||
me?: any,
|
||||
options?: {
|
||||
populateRecipient: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = options || {
|
||||
populateRecipient: true
|
||||
};
|
||||
|
||||
let _message: any;
|
||||
|
||||
// Populate the message if 'message' is ID
|
||||
if (isObjectId(message)) {
|
||||
_message = await MessagingMessage.findOne({
|
||||
_id: message
|
||||
});
|
||||
} else if (typeof message === 'string') {
|
||||
_message = await MessagingMessage.findOne({
|
||||
_id: new mongo.ObjectID(message)
|
||||
});
|
||||
} else {
|
||||
_message = deepcopy(message);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_message.id = _message._id;
|
||||
delete _message._id;
|
||||
|
||||
// Populate user
|
||||
_message.user = await packUser(_message.userId, me);
|
||||
|
||||
if (_message.fileId) {
|
||||
// Populate file
|
||||
_message.file = await packFile(_message.fileId);
|
||||
}
|
||||
|
||||
if (opts.populateRecipient) {
|
||||
// Populate recipient
|
||||
_message.recipient = await packUser(_message.recipientId, me);
|
||||
}
|
||||
|
||||
resolve(_message);
|
||||
});
|
|
@ -1,257 +0,0 @@
|
|||
import db from '../db/mongodb';
|
||||
import config from '../config';
|
||||
import User from './user';
|
||||
import { transform } from '../misc/cafy-id';
|
||||
|
||||
const Meta = db.get<IMeta>('meta');
|
||||
export default Meta;
|
||||
|
||||
// 後方互換性のため。
|
||||
// 過去のMisskeyではインスタンス名や紹介を設定ファイルに記述していたのでそれを移行
|
||||
if ((config as any).name) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.name == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
name: (config as any).name
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).description) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.description == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
description: (config as any).description
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).localDriveCapacityMb) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.localDriveCapacityMb == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
localDriveCapacityMb: (config as any).localDriveCapacityMb
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).remoteDriveCapacityMb) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.remoteDriveCapacityMb == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
remoteDriveCapacityMb: (config as any).remoteDriveCapacityMb
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).preventCacheRemoteFiles) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.cacheRemoteFiles == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
cacheRemoteFiles: !(config as any).preventCacheRemoteFiles
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).recaptcha) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.enableRecaptcha == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
enableRecaptcha: (config as any).recaptcha != null,
|
||||
recaptchaSiteKey: (config as any).recaptcha.site_key,
|
||||
recaptchaSecretKey: (config as any).recaptcha.secret_key,
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).ghost) {
|
||||
Meta.findOne({}).then(async m => {
|
||||
if (m != null && m.proxyAccount == null) {
|
||||
const account = await User.findOne({ _id: transform((config as any).ghost) });
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
proxyAccount: account.username
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).maintainer) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.maintainer == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
maintainer: (config as any).maintainer
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).twitter) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.enableTwitterIntegration == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
enableTwitterIntegration: true,
|
||||
twitterConsumerKey: (config as any).twitter.consumer_key,
|
||||
twitterConsumerSecret: (config as any).twitter.consumer_secret
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).github) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.enableGithubIntegration == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
enableGithubIntegration: true,
|
||||
githubClientId: (config as any).github.client_id,
|
||||
githubClientSecret: (config as any).github.client_secret
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).user_recommendation) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.enableExternalUserRecommendation == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
enableExternalUserRecommendation: true,
|
||||
externalUserRecommendationEngine: (config as any).user_recommendation.engine,
|
||||
externalUserRecommendationTimeout: (config as any).user_recommendation.timeout
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
if ((config as any).sw) {
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && m.enableServiceWorker == null) {
|
||||
Meta.update({}, {
|
||||
$set: {
|
||||
enableServiceWorker: true,
|
||||
swPublicKey: (config as any).sw.public_key,
|
||||
swPrivateKey: (config as any).sw.private_key
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
Meta.findOne({}).then(m => {
|
||||
if (m != null && (m as any).broadcasts != null) {
|
||||
Meta.update({}, {
|
||||
$rename: {
|
||||
broadcasts: 'announcements'
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type IMeta = {
|
||||
name?: string;
|
||||
description?: string;
|
||||
|
||||
/**
|
||||
* メンテナ情報
|
||||
*/
|
||||
maintainer: {
|
||||
/**
|
||||
* メンテナの名前
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* メンテナの連絡先
|
||||
*/
|
||||
email?: string;
|
||||
};
|
||||
|
||||
langs?: string[];
|
||||
|
||||
announcements?: any[];
|
||||
|
||||
stats?: {
|
||||
notesCount: number;
|
||||
originalNotesCount: number;
|
||||
usersCount: number;
|
||||
originalUsersCount: number;
|
||||
};
|
||||
|
||||
disableRegistration?: boolean;
|
||||
disableLocalTimeline?: boolean;
|
||||
disableGlobalTimeline?: boolean;
|
||||
enableEmojiReaction?: boolean;
|
||||
useStarForReactionFallback?: boolean;
|
||||
hidedTags?: string[];
|
||||
mascotImageUrl?: string;
|
||||
bannerUrl?: string;
|
||||
errorImageUrl?: string;
|
||||
iconUrl?: string;
|
||||
|
||||
cacheRemoteFiles?: boolean;
|
||||
|
||||
proxyAccount?: string;
|
||||
|
||||
enableRecaptcha?: boolean;
|
||||
recaptchaSiteKey?: string;
|
||||
recaptchaSecretKey?: string;
|
||||
|
||||
/**
|
||||
* Drive capacity of a local user (MB)
|
||||
*/
|
||||
localDriveCapacityMb?: number;
|
||||
|
||||
/**
|
||||
* Drive capacity of a remote user (MB)
|
||||
*/
|
||||
remoteDriveCapacityMb?: number;
|
||||
|
||||
/**
|
||||
* Max allowed note text length in characters
|
||||
*/
|
||||
maxNoteTextLength?: number;
|
||||
|
||||
summalyProxy?: string;
|
||||
|
||||
enableTwitterIntegration?: boolean;
|
||||
twitterConsumerKey?: string;
|
||||
twitterConsumerSecret?: string;
|
||||
|
||||
enableGithubIntegration?: boolean;
|
||||
githubClientId?: string;
|
||||
githubClientSecret?: string;
|
||||
|
||||
enableDiscordIntegration?: boolean;
|
||||
discordClientId?: string;
|
||||
discordClientSecret?: string;
|
||||
|
||||
enableExternalUserRecommendation?: boolean;
|
||||
externalUserRecommendationEngine?: string;
|
||||
externalUserRecommendationTimeout?: number;
|
||||
|
||||
enableEmail?: boolean;
|
||||
email?: string;
|
||||
smtpSecure?: boolean;
|
||||
smtpHost?: string;
|
||||
smtpPort?: number;
|
||||
smtpUser?: string;
|
||||
smtpPass?: string;
|
||||
|
||||
enableServiceWorker?: boolean;
|
||||
swPublicKey?: string;
|
||||
swPrivateKey?: string;
|
||||
};
|
|
@ -1,56 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import { pack as packUser, IUser } from './user';
|
||||
|
||||
const Mute = db.get<IMute>('mute');
|
||||
Mute.createIndex('muterId');
|
||||
Mute.createIndex('muteeId');
|
||||
Mute.createIndex(['muterId', 'muteeId'], { unique: true });
|
||||
export default Mute;
|
||||
|
||||
export interface IMute {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
muterId: mongo.ObjectID;
|
||||
muteeId: mongo.ObjectID;
|
||||
}
|
||||
|
||||
export const packMany = (
|
||||
mutes: (string | mongo.ObjectID | IMute)[],
|
||||
me?: string | mongo.ObjectID | IUser
|
||||
) => {
|
||||
return Promise.all(mutes.map(x => pack(x, me)));
|
||||
};
|
||||
|
||||
export const pack = (
|
||||
mute: any,
|
||||
me?: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _mute: any;
|
||||
|
||||
// Populate the mute if 'mute' is ID
|
||||
if (isObjectId(mute)) {
|
||||
_mute = await Mute.findOne({
|
||||
_id: mute
|
||||
});
|
||||
} else if (typeof mute === 'string') {
|
||||
_mute = await Mute.findOne({
|
||||
_id: new mongo.ObjectID(mute)
|
||||
});
|
||||
} else {
|
||||
_mute = deepcopy(mute);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_mute.id = _mute._id;
|
||||
delete _mute._id;
|
||||
|
||||
// Populate mutee
|
||||
_mute.mutee = await packUser(_mute.muteeId, me, {
|
||||
detail: true
|
||||
});
|
||||
|
||||
resolve(_mute);
|
||||
});
|
|
@ -1,51 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { pack as packUser } from './user';
|
||||
|
||||
const NoteReaction = db.get<INoteReaction>('noteReactions');
|
||||
NoteReaction.createIndex('noteId');
|
||||
NoteReaction.createIndex('userId');
|
||||
NoteReaction.createIndex(['userId', 'noteId'], { unique: true });
|
||||
export default NoteReaction;
|
||||
|
||||
export interface INoteReaction {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
noteId: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
reaction: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a reaction for API response
|
||||
*/
|
||||
export const pack = (
|
||||
reaction: any,
|
||||
me?: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _reaction: any;
|
||||
|
||||
// Populate the reaction if 'reaction' is ID
|
||||
if (isObjectId(reaction)) {
|
||||
_reaction = await NoteReaction.findOne({
|
||||
_id: reaction
|
||||
});
|
||||
} else if (typeof reaction === 'string') {
|
||||
_reaction = await NoteReaction.findOne({
|
||||
_id: new mongo.ObjectID(reaction)
|
||||
});
|
||||
} else {
|
||||
_reaction = deepcopy(reaction);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_reaction.id = _reaction._id;
|
||||
delete _reaction._id;
|
||||
|
||||
// Populate user
|
||||
_reaction.user = await packUser(_reaction.userId, me);
|
||||
|
||||
resolve(_reaction);
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const NoteUnread = db.get<INoteUnread>('noteUnreads');
|
||||
NoteUnread.createIndex('userId');
|
||||
NoteUnread.createIndex('noteId');
|
||||
NoteUnread.createIndex(['userId', 'noteId'], { unique: true });
|
||||
export default NoteUnread;
|
||||
|
||||
export interface INoteUnread {
|
||||
_id: mongo.ObjectID;
|
||||
noteId: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
isSpecified: boolean;
|
||||
|
||||
_note: {
|
||||
userId: mongo.ObjectID;
|
||||
};
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const NoteWatching = db.get<INoteWatching>('noteWatching');
|
||||
NoteWatching.createIndex('userId');
|
||||
NoteWatching.createIndex('noteId');
|
||||
NoteWatching.createIndex(['userId', 'noteId'], { unique: true });
|
||||
export default NoteWatching;
|
||||
|
||||
export interface INoteWatching {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID;
|
||||
noteId: mongo.ObjectID;
|
||||
}
|
|
@ -1,418 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { length } from 'stringz';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packApp } from './app';
|
||||
import PollVote from './poll-vote';
|
||||
import NoteReaction from './note-reaction';
|
||||
import { packMany as packFileMany, IDriveFile } from './drive-file';
|
||||
import Following from './following';
|
||||
import Emoji from './emoji';
|
||||
import { dbLogger } from '../db/logger';
|
||||
import { unique, concat } from '../prelude/array';
|
||||
|
||||
const Note = db.get<INote>('notes');
|
||||
Note.createIndex('uri', { sparse: true, unique: true });
|
||||
Note.createIndex('userId');
|
||||
Note.createIndex('mentions');
|
||||
Note.createIndex('visibleUserIds');
|
||||
Note.createIndex('replyId');
|
||||
Note.createIndex('renoteId');
|
||||
Note.createIndex('tagsLower');
|
||||
Note.createIndex('_user.host');
|
||||
Note.createIndex('_files._id');
|
||||
Note.createIndex('_files.contentType');
|
||||
Note.createIndex({ createdAt: -1 });
|
||||
Note.createIndex({ score: -1 }, { sparse: true });
|
||||
export default Note;
|
||||
|
||||
export function isValidCw(text: string): boolean {
|
||||
return length(text.trim()) <= 100;
|
||||
}
|
||||
|
||||
export type INote = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
deletedAt: Date;
|
||||
updatedAt?: Date;
|
||||
fileIds: mongo.ObjectID[];
|
||||
replyId: mongo.ObjectID;
|
||||
renoteId: mongo.ObjectID;
|
||||
poll: IPoll;
|
||||
name?: string;
|
||||
text: string;
|
||||
tags: string[];
|
||||
tagsLower: string[];
|
||||
emojis: string[];
|
||||
cw: string;
|
||||
userId: mongo.ObjectID;
|
||||
appId: mongo.ObjectID;
|
||||
viaMobile: boolean;
|
||||
localOnly: boolean;
|
||||
renoteCount: number;
|
||||
repliesCount: number;
|
||||
reactionCounts: Record<string, number>;
|
||||
mentions: mongo.ObjectID[];
|
||||
mentionedRemoteUsers: {
|
||||
uri: string;
|
||||
username: string;
|
||||
host: string;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* public ... 公開
|
||||
* home ... ホームタイムライン(ユーザーページのタイムライン含む)のみに流す
|
||||
* followers ... フォロワーのみ
|
||||
* specified ... visibleUserIds で指定したユーザーのみ
|
||||
*/
|
||||
visibility: 'public' | 'home' | 'followers' | 'specified';
|
||||
|
||||
visibleUserIds: mongo.ObjectID[];
|
||||
|
||||
geo: {
|
||||
coordinates: number[];
|
||||
altitude: number;
|
||||
accuracy: number;
|
||||
altitudeAccuracy: number;
|
||||
heading: number;
|
||||
speed: number;
|
||||
};
|
||||
|
||||
uri: string;
|
||||
|
||||
/**
|
||||
* 人気の投稿度合いを表すスコア
|
||||
*/
|
||||
score: number;
|
||||
|
||||
// 非正規化
|
||||
_reply?: {
|
||||
userId: mongo.ObjectID;
|
||||
};
|
||||
_renote?: {
|
||||
userId: mongo.ObjectID;
|
||||
};
|
||||
_user: {
|
||||
host: string;
|
||||
inbox?: string;
|
||||
};
|
||||
_files?: IDriveFile[];
|
||||
};
|
||||
|
||||
export type IPoll = {
|
||||
choices: IChoice[];
|
||||
multiple?: boolean;
|
||||
expiresAt?: Date;
|
||||
};
|
||||
|
||||
export type IChoice = {
|
||||
id: number;
|
||||
text: string;
|
||||
votes: number;
|
||||
};
|
||||
|
||||
export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => {
|
||||
let hide = false;
|
||||
|
||||
// visibility が private かつ投稿者のIDが自分のIDではなかったら非表示(後方互換性のため)
|
||||
if (packedNote.visibility == 'private' && (meId == null || !meId.equals(packedNote.userId))) {
|
||||
hide = true;
|
||||
}
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (packedNote.visibility == 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId.equals(packedNote.userId)) {
|
||||
hide = false;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds.some((id: any) => meId.equals(id));
|
||||
|
||||
if (specified) {
|
||||
hide = false;
|
||||
} else {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (packedNote.visibility == 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId.equals(packedNote.userId)) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && meId.equals(packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId.equals(id))) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const following = await Following.findOne({
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId
|
||||
});
|
||||
|
||||
if (following == null) {
|
||||
hide = true;
|
||||
} else {
|
||||
hide = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = null;
|
||||
packedNote.cw = null;
|
||||
packedNote.tags = [];
|
||||
packedNote.geo = null;
|
||||
packedNote.isHidden = true;
|
||||
}
|
||||
};
|
||||
|
||||
export const packMany = (
|
||||
notes: (string | mongo.ObjectID | INote)[],
|
||||
me?: string | mongo.ObjectID | IUser,
|
||||
options?: {
|
||||
detail?: boolean;
|
||||
skipHide?: boolean;
|
||||
}
|
||||
) => {
|
||||
return Promise.all(notes.map(n => pack(n, me, options)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a note for API response
|
||||
*
|
||||
* @param note target
|
||||
* @param me? serializee
|
||||
* @param options? serialize options
|
||||
* @return response
|
||||
*/
|
||||
export const pack = async (
|
||||
note: string | mongo.ObjectID | INote,
|
||||
me?: string | mongo.ObjectID | IUser,
|
||||
options?: {
|
||||
detail?: boolean;
|
||||
skipHide?: boolean;
|
||||
}
|
||||
) => {
|
||||
const opts = Object.assign({
|
||||
detail: true,
|
||||
skipHide: false
|
||||
}, options);
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
: (me as IUser)._id
|
||||
: null;
|
||||
|
||||
let _note: any;
|
||||
|
||||
// Populate the note if 'note' is ID
|
||||
if (isObjectId(note)) {
|
||||
_note = await Note.findOne({
|
||||
_id: note
|
||||
});
|
||||
} else if (typeof note === 'string') {
|
||||
_note = await Note.findOne({
|
||||
_id: new mongo.ObjectID(note)
|
||||
});
|
||||
} else {
|
||||
_note = deepcopy(note);
|
||||
}
|
||||
|
||||
// (データベースの欠損などで)投稿がデータベース上に見つからなかったとき
|
||||
if (_note == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: note :: ${note}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const id = _note._id;
|
||||
|
||||
// Some counts
|
||||
_note.renoteCount = _note.renoteCount || 0;
|
||||
_note.repliesCount = _note.repliesCount || 0;
|
||||
_note.reactionCounts = _note.reactionCounts || {};
|
||||
|
||||
// _note._userを消す前か、_note.userを解決した後でないとホストがわからない
|
||||
if (_note._user) {
|
||||
const host = _note._user.host;
|
||||
// 互換性のため。(古いMisskeyではNoteにemojisが無い)
|
||||
if (_note.emojis == null) {
|
||||
_note.emojis = Emoji.find({
|
||||
host: host
|
||||
}, {
|
||||
fields: { _id: false }
|
||||
});
|
||||
} else {
|
||||
_note.emojis = unique(concat([_note.emojis, Object.keys(_note.reactionCounts).map(x => x.replace(/:/g, ''))]));
|
||||
|
||||
_note.emojis = Emoji.find({
|
||||
name: { $in: _note.emojis },
|
||||
host: host
|
||||
}, {
|
||||
fields: { _id: false }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_note.id = _note._id;
|
||||
delete _note._id;
|
||||
|
||||
delete _note.prev;
|
||||
delete _note.next;
|
||||
delete _note.tagsLower;
|
||||
delete _note.score;
|
||||
delete _note._user;
|
||||
delete _note._reply;
|
||||
delete _note._renote;
|
||||
delete _note._files;
|
||||
delete _note._replyIds;
|
||||
delete _note.mentionedRemoteUsers;
|
||||
|
||||
if (_note.geo) delete _note.geo.type;
|
||||
|
||||
// Populate user
|
||||
_note.user = packUser(_note.userId, meId);
|
||||
|
||||
// Populate app
|
||||
if (_note.appId) {
|
||||
_note.app = packApp(_note.appId);
|
||||
}
|
||||
|
||||
// Populate files
|
||||
_note.files = packFileMany(_note.fileIds || []);
|
||||
|
||||
// 後方互換性のため
|
||||
_note.mediaIds = _note.fileIds;
|
||||
_note.media = _note.files;
|
||||
|
||||
// When requested a detailed note data
|
||||
if (opts.detail) {
|
||||
if (_note.replyId) {
|
||||
// Populate reply to note
|
||||
_note.reply = pack(_note.replyId, meId, {
|
||||
detail: false
|
||||
});
|
||||
}
|
||||
|
||||
if (_note.renoteId) {
|
||||
// Populate renote
|
||||
_note.renote = pack(_note.renoteId, meId, {
|
||||
detail: _note.text == null
|
||||
});
|
||||
}
|
||||
|
||||
// Poll
|
||||
if (meId && _note.poll) {
|
||||
_note.poll = (async poll => {
|
||||
if (poll.multiple) {
|
||||
const votes = await PollVote.find({
|
||||
userId: meId,
|
||||
noteId: id
|
||||
});
|
||||
|
||||
const myChoices = (poll.choices as IChoice[]).filter(x => votes.some(y => x.id == y.choice));
|
||||
for (const myChoice of myChoices) {
|
||||
(myChoice as any).isVoted = true;
|
||||
}
|
||||
|
||||
return poll;
|
||||
} else {
|
||||
poll.multiple = false;
|
||||
}
|
||||
|
||||
const vote = await PollVote
|
||||
.findOne({
|
||||
userId: meId,
|
||||
noteId: id
|
||||
});
|
||||
|
||||
if (vote) {
|
||||
const myChoice = (poll.choices as IChoice[])
|
||||
.filter(x => x.id == vote.choice)[0] as any;
|
||||
|
||||
myChoice.isVoted = true;
|
||||
}
|
||||
|
||||
return poll;
|
||||
})(_note.poll);
|
||||
}
|
||||
|
||||
if (meId) {
|
||||
// Fetch my reaction
|
||||
_note.myReaction = (async () => {
|
||||
const reaction = await NoteReaction
|
||||
.findOne({
|
||||
userId: meId,
|
||||
noteId: id,
|
||||
deletedAt: { $exists: false }
|
||||
});
|
||||
|
||||
if (reaction) {
|
||||
return reaction.reaction;
|
||||
}
|
||||
|
||||
return null;
|
||||
})();
|
||||
}
|
||||
}
|
||||
|
||||
// resolve promises in _note object
|
||||
_note = await rap(_note);
|
||||
|
||||
//#region (データベースの欠損などで)参照しているデータがデータベース上に見つからなかったとき
|
||||
if (_note.user == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> user :: ${_note.id} (user ${_note.userId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (opts.detail) {
|
||||
if (_note.replyId != null && _note.reply == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> reply :: ${_note.id} (reply ${_note.replyId})`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_note.renoteId != null && _note.renote == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: note -> renote :: ${_note.id} (renote ${_note.renoteId})`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
//#endregion
|
||||
|
||||
if (_note.name) {
|
||||
_note.text = `【${_note.name}】\n${_note.text}`;
|
||||
}
|
||||
|
||||
if (_note.user.isCat && _note.text) {
|
||||
_note.text = (_note.text
|
||||
// ja-JP
|
||||
.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ')
|
||||
// ko-KR
|
||||
.replace(/[나-낳]/g, (match: string) => String.fromCharCode(
|
||||
match.codePointAt(0) + '냐'.charCodeAt(0) - '나'.charCodeAt(0)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
if (!opts.skipHide) {
|
||||
await hideNote(_note, meId);
|
||||
}
|
||||
|
||||
return _note;
|
||||
};
|
|
@ -1,120 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { IUser, pack as packUser } from './user';
|
||||
import { pack as packNote } from './note';
|
||||
import { dbLogger } from '../db/logger';
|
||||
|
||||
const Notification = db.get<INotification>('notifications');
|
||||
Notification.createIndex('notifieeId');
|
||||
export default Notification;
|
||||
|
||||
export interface INotification {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
|
||||
/**
|
||||
* 通知の受信者
|
||||
*/
|
||||
notifiee?: IUser;
|
||||
|
||||
/**
|
||||
* 通知の受信者
|
||||
*/
|
||||
notifieeId: mongo.ObjectID;
|
||||
|
||||
/**
|
||||
* イニシエータ(initiator)、Origin。通知を行う原因となったユーザー
|
||||
*/
|
||||
notifier?: IUser;
|
||||
|
||||
/**
|
||||
* イニシエータ(initiator)、Origin。通知を行う原因となったユーザー
|
||||
*/
|
||||
notifierId: mongo.ObjectID;
|
||||
|
||||
/**
|
||||
* 通知の種類。
|
||||
* follow - フォローされた
|
||||
* mention - 投稿で自分が言及された
|
||||
* reply - (自分または自分がWatchしている)投稿が返信された
|
||||
* renote - (自分または自分がWatchしている)投稿がRenoteされた
|
||||
* quote - (自分または自分がWatchしている)投稿が引用Renoteされた
|
||||
* reaction - (自分または自分がWatchしている)投稿にリアクションされた
|
||||
* poll_vote - (自分または自分がWatchしている)投稿の投票に投票された
|
||||
*/
|
||||
type: 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'poll_vote';
|
||||
|
||||
/**
|
||||
* 通知が読まれたかどうか
|
||||
*/
|
||||
isRead: boolean;
|
||||
}
|
||||
|
||||
export const packMany = (
|
||||
notifications: any[]
|
||||
) => {
|
||||
return Promise.all(notifications.map(n => pack(n)));
|
||||
};
|
||||
|
||||
/**
|
||||
* Pack a notification for API response
|
||||
*/
|
||||
export const pack = (notification: any) => new Promise<any>(async (resolve, reject) => {
|
||||
let _notification: any;
|
||||
|
||||
// Populate the notification if 'notification' is ID
|
||||
if (isObjectId(notification)) {
|
||||
_notification = await Notification.findOne({
|
||||
_id: notification
|
||||
});
|
||||
} else if (typeof notification === 'string') {
|
||||
_notification = await Notification.findOne({
|
||||
_id: new mongo.ObjectID(notification)
|
||||
});
|
||||
} else {
|
||||
_notification = deepcopy(notification);
|
||||
}
|
||||
|
||||
// Rename _id to id
|
||||
_notification.id = _notification._id;
|
||||
delete _notification._id;
|
||||
|
||||
// Rename notifierId to userId
|
||||
_notification.userId = _notification.notifierId;
|
||||
delete _notification.notifierId;
|
||||
|
||||
const me = _notification.notifieeId;
|
||||
delete _notification.notifieeId;
|
||||
|
||||
// Populate notifier
|
||||
_notification.user = await packUser(_notification.userId, me);
|
||||
|
||||
switch (_notification.type) {
|
||||
case 'follow':
|
||||
case 'receiveFollowRequest':
|
||||
// nope
|
||||
break;
|
||||
case 'mention':
|
||||
case 'reply':
|
||||
case 'renote':
|
||||
case 'quote':
|
||||
case 'reaction':
|
||||
case 'poll_vote':
|
||||
// Populate note
|
||||
_notification.note = await packNote(_notification.noteId, me);
|
||||
|
||||
// (データベースの不具合などで)投稿が見つからなかったら
|
||||
if (_notification.note == null) {
|
||||
dbLogger.warn(`[DAMAGED DB] (missing) pkg: notification -> note :: ${_notification.id} (note ${_notification.noteId})`);
|
||||
return resolve(null);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dbLogger.error(`Unknown type: ${_notification.type}`);
|
||||
break;
|
||||
}
|
||||
|
||||
resolve(_notification);
|
||||
});
|
|
@ -1,17 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const PollVote = db.get<IPollVote>('pollVotes');
|
||||
PollVote.dropIndex(['userId', 'noteId'], { unique: true }).catch(() => {});
|
||||
PollVote.createIndex('userId');
|
||||
PollVote.createIndex('noteId');
|
||||
PollVote.createIndex(['userId', 'noteId', 'choice'], { unique: true });
|
||||
export default PollVote;
|
||||
|
||||
export interface IPollVote {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID;
|
||||
noteId: mongo.ObjectID;
|
||||
choice: number;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const RegistrationTicket = db.get<IRegistrationTicket>('registrationTickets');
|
||||
RegistrationTicket.createIndex('code', { unique: true });
|
||||
export default RegistrationTicket;
|
||||
|
||||
export interface IRegistrationTicket {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
code: string;
|
||||
}
|
32
src/models/repositories/abuse-user-report.ts
Normal file
32
src/models/repositories/abuse-user-report.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { AbuseUserReport } from '../entities/abuse-user-report';
|
||||
|
||||
@EntityRepository(AbuseUserReport)
|
||||
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
|
||||
public packMany(
|
||||
reports: any[],
|
||||
) {
|
||||
return Promise.all(reports.map(x => this.pack(x)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: AbuseUserReport['id'] | AbuseUserReport,
|
||||
) {
|
||||
const report = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: report.id,
|
||||
createdAt: report.createdAt,
|
||||
reporterId: report.reporterId,
|
||||
userId: report.userId,
|
||||
reporter: Users.pack(report.reporter || report.reporterId, null, {
|
||||
detail: true
|
||||
}),
|
||||
user: Users.pack(report.user || report.userId, null, {
|
||||
detail: true
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
36
src/models/repositories/app.ts
Normal file
36
src/models/repositories/app.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { App } from '../entities/app';
|
||||
import { AccessTokens } from '..';
|
||||
|
||||
@EntityRepository(App)
|
||||
export class AppRepository extends Repository<App> {
|
||||
public async pack(
|
||||
src: App['id'] | App,
|
||||
me?: any,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
includeSecret?: boolean,
|
||||
includeProfileImageIds?: boolean
|
||||
}
|
||||
) {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
includeSecret: false,
|
||||
includeProfileImageIds: false
|
||||
}, options);
|
||||
|
||||
const app = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: app.id,
|
||||
name: app.name,
|
||||
...(opts.includeSecret ? { secret: app.secret } : {}),
|
||||
...(me ? {
|
||||
isAuthorized: await AccessTokens.count({
|
||||
appId: app.id,
|
||||
userId: me,
|
||||
}).then(count => count > 0)
|
||||
} : {})
|
||||
};
|
||||
}
|
||||
}
|
19
src/models/repositories/auth-session.ts
Normal file
19
src/models/repositories/auth-session.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Apps } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { AuthSession } from '../entities/auth-session';
|
||||
|
||||
@EntityRepository(AuthSession)
|
||||
export class AuthSessionRepository extends Repository<AuthSession> {
|
||||
public async pack(
|
||||
src: AuthSession['id'] | AuthSession,
|
||||
me?: any
|
||||
) {
|
||||
const session = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: session.id,
|
||||
app: Apps.pack(session.appId, me)
|
||||
});
|
||||
}
|
||||
}
|
28
src/models/repositories/blocking.ts
Normal file
28
src/models/repositories/blocking.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { Blocking } from '../entities/blocking';
|
||||
|
||||
@EntityRepository(Blocking)
|
||||
export class BlockingRepository extends Repository<Blocking> {
|
||||
public packMany(
|
||||
blockings: any[],
|
||||
me: any
|
||||
) {
|
||||
return Promise.all(blockings.map(x => this.pack(x, me)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: Blocking['id'] | Blocking,
|
||||
me?: any
|
||||
) {
|
||||
const blocking = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: blocking.id,
|
||||
blockee: Users.pack(blocking.blockeeId, me, {
|
||||
detail: true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
113
src/models/repositories/drive-file.ts
Normal file
113
src/models/repositories/drive-file.ts
Normal file
|
@ -0,0 +1,113 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { DriveFile } from '../entities/drive-file';
|
||||
import { Users, DriveFolders } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { User } from '../entities/user';
|
||||
|
||||
@EntityRepository(DriveFile)
|
||||
export class DriveFileRepository extends Repository<DriveFile> {
|
||||
public validateFileName(name: string): boolean {
|
||||
return (
|
||||
(name.trim().length > 0) &&
|
||||
(name.length <= 200) &&
|
||||
(name.indexOf('\\') === -1) &&
|
||||
(name.indexOf('/') === -1) &&
|
||||
(name.indexOf('..') === -1)
|
||||
);
|
||||
}
|
||||
|
||||
public getPublicUrl(file: DriveFile, thumbnail = false): string {
|
||||
if (thumbnail) {
|
||||
return file.thumbnailUrl || file.webpublicUrl || file.url;
|
||||
} else {
|
||||
return file.webpublicUrl || file.thumbnailUrl || file.url;
|
||||
}
|
||||
}
|
||||
|
||||
public async clacDriveUsageOf(user: User['id'] | User): Promise<number> {
|
||||
const id = typeof user === 'object' ? user.id : user;
|
||||
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userId = :id', { id: id })
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
}
|
||||
|
||||
public async clacDriveUsageOfHost(host: string): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost = :host', { host: host })
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
}
|
||||
|
||||
public async clacDriveUsageOfLocal(): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost IS NULL')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
}
|
||||
|
||||
public async clacDriveUsageOfRemote(): Promise<number> {
|
||||
const { sum } = await this
|
||||
.createQueryBuilder('file')
|
||||
.where('file.userHost IS NOT NULL')
|
||||
.select('SUM(file.size)', 'sum')
|
||||
.getRawOne();
|
||||
|
||||
return parseInt(sum, 10) || 0;
|
||||
}
|
||||
|
||||
public packMany(
|
||||
files: any[],
|
||||
options?: {
|
||||
detail?: boolean
|
||||
self?: boolean,
|
||||
withUser?: boolean,
|
||||
}
|
||||
) {
|
||||
return Promise.all(files.map(f => this.pack(f, options)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: DriveFile['id'] | DriveFile,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
self?: boolean,
|
||||
withUser?: boolean,
|
||||
}
|
||||
) {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
self: false
|
||||
}, options);
|
||||
|
||||
const file = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: file.id,
|
||||
createdAt: file.createdAt,
|
||||
name: file.name,
|
||||
type: file.type,
|
||||
md5: file.md5,
|
||||
size: file.size,
|
||||
isSensitive: file.isSensitive,
|
||||
properties: file.properties,
|
||||
url: opts.self ? file.url : this.getPublicUrl(file, false),
|
||||
thumbnailUrl: this.getPublicUrl(file, true),
|
||||
folderId: file.folderId,
|
||||
folder: opts.detail && file.folderId ? DriveFolders.pack(file.folderId, {
|
||||
detail: true
|
||||
}) : null,
|
||||
user: opts.withUser ? Users.pack(file.userId) : null
|
||||
});
|
||||
}
|
||||
}
|
49
src/models/repositories/drive-folder.ts
Normal file
49
src/models/repositories/drive-folder.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { DriveFolders, DriveFiles } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { DriveFolder } from '../entities/drive-folder';
|
||||
|
||||
@EntityRepository(DriveFolder)
|
||||
export class DriveFolderRepository extends Repository<DriveFolder> {
|
||||
public validateFolderName(name: string): boolean {
|
||||
return (
|
||||
(name.trim().length > 0) &&
|
||||
(name.length <= 200)
|
||||
);
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: DriveFolder['id'] | DriveFolder,
|
||||
options?: {
|
||||
detail: boolean
|
||||
}
|
||||
): Promise<Record<string, any>> {
|
||||
const opts = Object.assign({
|
||||
detail: false
|
||||
}, options);
|
||||
|
||||
const folder = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: folder.id,
|
||||
createdAt: folder.createdAt,
|
||||
name: folder.name,
|
||||
parentId: folder.parentId,
|
||||
|
||||
...(opts.detail ? {
|
||||
foldersCount: DriveFolders.count({
|
||||
parentId: folder.id
|
||||
}),
|
||||
filesCount: DriveFiles.count({
|
||||
folderId: folder.id
|
||||
}),
|
||||
|
||||
...(folder.parentId ? {
|
||||
parent: this.pack(folder.parentId, {
|
||||
detail: true
|
||||
})
|
||||
} : {})
|
||||
} : {})
|
||||
});
|
||||
}
|
||||
}
|
19
src/models/repositories/follow-request.ts
Normal file
19
src/models/repositories/follow-request.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { FollowRequest } from '../entities/follow-request';
|
||||
import { Users } from '..';
|
||||
|
||||
@EntityRepository(FollowRequest)
|
||||
export class FollowRequestRepository extends Repository<FollowRequest> {
|
||||
public async pack(
|
||||
src: FollowRequest['id'] | FollowRequest,
|
||||
me?: any
|
||||
) {
|
||||
const request = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: request.id,
|
||||
follower: await Users.pack(request.followerId, me),
|
||||
followee: await Users.pack(request.followeeId, me),
|
||||
};
|
||||
}
|
||||
}
|
44
src/models/repositories/following.ts
Normal file
44
src/models/repositories/following.ts
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { Following } from '../entities/following';
|
||||
|
||||
@EntityRepository(Following)
|
||||
export class FollowingRepository extends Repository<Following> {
|
||||
public packMany(
|
||||
followings: any[],
|
||||
me?: any,
|
||||
opts?: {
|
||||
populateFollowee?: boolean;
|
||||
populateFollower?: boolean;
|
||||
}
|
||||
) {
|
||||
return Promise.all(followings.map(x => this.pack(x, me, opts)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: Following['id'] | Following,
|
||||
me?: any,
|
||||
opts?: {
|
||||
populateFollowee?: boolean;
|
||||
populateFollower?: boolean;
|
||||
}
|
||||
) {
|
||||
const following = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
if (opts == null) opts = {};
|
||||
|
||||
return await rap({
|
||||
id: following.id,
|
||||
createdAt: following.createdAt,
|
||||
followeeId: following.followeeId,
|
||||
followerId: following.followerId,
|
||||
followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, {
|
||||
detail: true
|
||||
}) : null,
|
||||
follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, {
|
||||
detail: true
|
||||
}) : null,
|
||||
});
|
||||
}
|
||||
}
|
49
src/models/repositories/games/reversi/game.ts
Normal file
49
src/models/repositories/games/reversi/game.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users } from '../../..';
|
||||
import { ReversiGame } from '../../../entities/games/reversi/game';
|
||||
|
||||
@EntityRepository(ReversiGame)
|
||||
export class ReversiGameRepository extends Repository<ReversiGame> {
|
||||
public async pack(
|
||||
src: ReversiGame['id'] | ReversiGame,
|
||||
me?: any,
|
||||
options?: {
|
||||
detail?: boolean
|
||||
}
|
||||
) {
|
||||
const opts = Object.assign({
|
||||
detail: true
|
||||
}, options);
|
||||
|
||||
const game = typeof src === 'object' ? src : await this.findOne(src);
|
||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||
|
||||
return {
|
||||
id: game.id,
|
||||
createdAt: game.createdAt,
|
||||
startedAt: game.startedAt,
|
||||
isStarted: game.isStarted,
|
||||
isEnded: game.isEnded,
|
||||
form1: game.form1,
|
||||
form2: game.form2,
|
||||
user1Accepted: game.user1Accepted,
|
||||
user2Accepted: game.user2Accepted,
|
||||
user1Id: game.user1Id,
|
||||
user2Id: game.user2Id,
|
||||
user1: await Users.pack(game.user1Id, meId),
|
||||
user2: await Users.pack(game.user2Id, meId),
|
||||
winnerId: game.winnerId,
|
||||
winner: game.winnerId ? await Users.pack(game.winnerId, meId) : null,
|
||||
surrendered: game.surrendered,
|
||||
black: game.black,
|
||||
bw: game.bw,
|
||||
isLlotheo: game.isLlotheo,
|
||||
canPutEverywhere: game.canPutEverywhere,
|
||||
loopedBoard: game.loopedBoard,
|
||||
...(opts.detail ? {
|
||||
logs: game.logs,
|
||||
map: game.map,
|
||||
} : {})
|
||||
};
|
||||
}
|
||||
}
|
27
src/models/repositories/games/reversi/matching.ts
Normal file
27
src/models/repositories/games/reversi/matching.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { ReversiMatching } from '../../../entities/games/reversi/matching';
|
||||
import { Users } from '../../..';
|
||||
|
||||
@EntityRepository(ReversiMatching)
|
||||
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
|
||||
public async pack(
|
||||
src: ReversiMatching['id'] | ReversiMatching,
|
||||
me: any
|
||||
) {
|
||||
const matching = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: matching.id,
|
||||
createdAt: matching.createdAt,
|
||||
parentId: matching.parentId,
|
||||
parent: Users.pack(matching.parentId, me, {
|
||||
detail: true
|
||||
}),
|
||||
childId: matching.childId,
|
||||
child: Users.pack(matching.childId, me, {
|
||||
detail: true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
37
src/models/repositories/messaging-message.ts
Normal file
37
src/models/repositories/messaging-message.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { MessagingMessage } from '../entities/messaging-message';
|
||||
import { Users, DriveFiles } from '..';
|
||||
|
||||
@EntityRepository(MessagingMessage)
|
||||
export class MessagingMessageRepository extends Repository<MessagingMessage> {
|
||||
public isValidText(text: string): boolean {
|
||||
return text.trim().length <= 1000 && text.trim() != '';
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: MessagingMessage['id'] | MessagingMessage,
|
||||
me?: any,
|
||||
options?: {
|
||||
populateRecipient: boolean
|
||||
}
|
||||
) {
|
||||
const opts = options || {
|
||||
populateRecipient: true
|
||||
};
|
||||
|
||||
const message = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: message.id,
|
||||
createdAt: message.createdAt,
|
||||
text: message.text,
|
||||
userId: message.userId,
|
||||
user: await Users.pack(message.user || message.userId, me),
|
||||
recipientId: message.recipientId,
|
||||
recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : null,
|
||||
fileId: message.fileId,
|
||||
file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
|
||||
isRead: message.isRead
|
||||
};
|
||||
}
|
||||
}
|
28
src/models/repositories/muting.ts
Normal file
28
src/models/repositories/muting.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { Muting } from '../entities/muting';
|
||||
|
||||
@EntityRepository(Muting)
|
||||
export class MutingRepository extends Repository<Muting> {
|
||||
public packMany(
|
||||
mutings: any[],
|
||||
me: any
|
||||
) {
|
||||
return Promise.all(mutings.map(x => this.pack(x, me)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: Muting['id'] | Muting,
|
||||
me?: any
|
||||
) {
|
||||
const muting = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: muting.id,
|
||||
mutee: Users.pack(muting.muteeId, me, {
|
||||
detail: true
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
25
src/models/repositories/note-favorite.ts
Normal file
25
src/models/repositories/note-favorite.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { NoteFavorite } from '../entities/note-favorite';
|
||||
import { Notes } from '..';
|
||||
|
||||
@EntityRepository(NoteFavorite)
|
||||
export class NoteFavoriteRepository extends Repository<NoteFavorite> {
|
||||
public packMany(
|
||||
favorites: any[],
|
||||
me: any
|
||||
) {
|
||||
return Promise.all(favorites.map(x => this.pack(x, me)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: NoteFavorite['id'] | NoteFavorite,
|
||||
me?: any
|
||||
) {
|
||||
const favorite = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: favorite.id,
|
||||
note: await Notes.pack(favorite.note || favorite.noteId, me),
|
||||
};
|
||||
}
|
||||
}
|
18
src/models/repositories/note-reaction.ts
Normal file
18
src/models/repositories/note-reaction.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { NoteReaction } from '../entities/note-reaction';
|
||||
import { Users } from '..';
|
||||
|
||||
@EntityRepository(NoteReaction)
|
||||
export class NoteReactionRepository extends Repository<NoteReaction> {
|
||||
public async pack(
|
||||
src: NoteReaction['id'] | NoteReaction,
|
||||
me?: any
|
||||
) {
|
||||
const reaction = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: reaction.id,
|
||||
user: await Users.pack(reaction.userId, me),
|
||||
};
|
||||
}
|
||||
}
|
210
src/models/repositories/note.ts
Normal file
210
src/models/repositories/note.ts
Normal file
|
@ -0,0 +1,210 @@
|
|||
import { EntityRepository, Repository, In } from 'typeorm';
|
||||
import { Note } from '../entities/note';
|
||||
import { User } from '../entities/user';
|
||||
import { unique, concat } from '../../prelude/array';
|
||||
import { nyaize } from '../../misc/nyaize';
|
||||
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
|
||||
@EntityRepository(Note)
|
||||
export class NoteRepository extends Repository<Note> {
|
||||
public validateCw(x: string) {
|
||||
return x.trim().length <= 100;
|
||||
}
|
||||
|
||||
private async hideNote(packedNote: any, meId: User['id']) {
|
||||
let hide = false;
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (packedNote.visibility == 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds.some((id: any) => meId === id);
|
||||
|
||||
if (specified) {
|
||||
hide = false;
|
||||
} else {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (packedNote.visibility == 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId === id)) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
const following = await Followings.findOne({
|
||||
followeeId: packedNote.userId,
|
||||
followerId: meId
|
||||
});
|
||||
|
||||
if (following == null) {
|
||||
hide = true;
|
||||
} else {
|
||||
hide = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hide) {
|
||||
packedNote.visibleUserIds = null;
|
||||
packedNote.fileIds = [];
|
||||
packedNote.files = [];
|
||||
packedNote.text = null;
|
||||
packedNote.poll = null;
|
||||
packedNote.cw = null;
|
||||
packedNote.tags = [];
|
||||
packedNote.geo = null;
|
||||
packedNote.isHidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
public packMany(
|
||||
notes: (Note['id'] | Note)[],
|
||||
me?: User['id'] | User,
|
||||
options?: {
|
||||
detail?: boolean;
|
||||
skipHide?: boolean;
|
||||
}
|
||||
) {
|
||||
return Promise.all(notes.map(n => this.pack(n, me, options)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: Note['id'] | Note,
|
||||
me?: User['id'] | User,
|
||||
options?: {
|
||||
detail?: boolean;
|
||||
skipHide?: boolean;
|
||||
}
|
||||
): Promise<Record<string, any>> {
|
||||
const opts = Object.assign({
|
||||
detail: true,
|
||||
skipHide: false
|
||||
}, options);
|
||||
|
||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||
const note = typeof src === 'object' ? src : await this.findOne(src);
|
||||
const host = note.userHost;
|
||||
|
||||
async function populatePoll() {
|
||||
const poll = await Polls.findOne({ noteId: note.id });
|
||||
const choices = poll.choices.map(c => ({
|
||||
text: c,
|
||||
votes: poll.votes[poll.choices.indexOf(c)],
|
||||
isVoted: false
|
||||
}));
|
||||
|
||||
if (poll.multiple) {
|
||||
const votes = await PollVotes.find({
|
||||
userId: meId,
|
||||
noteId: note.id
|
||||
});
|
||||
|
||||
const myChoices = votes.map(v => v.choice);
|
||||
for (const myChoice of myChoices) {
|
||||
choices[myChoice].isVoted = true;
|
||||
}
|
||||
} else {
|
||||
const vote = await PollVotes.findOne({
|
||||
userId: meId,
|
||||
noteId: note.id
|
||||
});
|
||||
|
||||
if (vote) {
|
||||
choices[vote.choice].isVoted = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
multiple: poll.multiple,
|
||||
expiresAt: poll.expiresAt,
|
||||
choices
|
||||
};
|
||||
}
|
||||
|
||||
async function populateMyReaction() {
|
||||
const reaction = await NoteReactions.findOne({
|
||||
userId: meId,
|
||||
noteId: note.id,
|
||||
});
|
||||
|
||||
if (reaction) {
|
||||
return reaction.reaction;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
let text = note.text;
|
||||
|
||||
if (note.name) {
|
||||
text = `【${note.name}】\n${note.text}`;
|
||||
}
|
||||
|
||||
const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)]));
|
||||
|
||||
const packed = await rap({
|
||||
id: note.id,
|
||||
createdAt: note.createdAt,
|
||||
app: note.appId ? Apps.pack(note.appId) : null,
|
||||
userId: note.userId,
|
||||
user: Users.pack(note.user || note.userId, meId),
|
||||
text: text,
|
||||
cw: note.cw,
|
||||
visibility: note.visibility,
|
||||
visibleUserIds: note.visibleUserIds,
|
||||
viaMobile: note.viaMobile,
|
||||
reactions: note.reactions,
|
||||
emojis: reactionEmojis.length > 0 ? Emojis.find({
|
||||
name: In(reactionEmojis),
|
||||
host: host
|
||||
}) : [],
|
||||
tags: note.tags,
|
||||
fileIds: note.fileIds,
|
||||
files: DriveFiles.packMany(note.fileIds),
|
||||
replyId: note.replyId,
|
||||
renoteId: note.renoteId,
|
||||
|
||||
...(opts.detail ? {
|
||||
reply: note.replyId ? this.pack(note.replyId, meId, {
|
||||
detail: false
|
||||
}) : null,
|
||||
|
||||
renote: note.renoteId ? this.pack(note.renoteId, meId, {
|
||||
detail: false
|
||||
}) : null,
|
||||
|
||||
poll: note.hasPoll ? populatePoll() : null,
|
||||
|
||||
...(meId ? {
|
||||
myReaction: populateMyReaction()
|
||||
} : {})
|
||||
} : {})
|
||||
});
|
||||
|
||||
if (packed.user.isCat && packed.text) {
|
||||
packed.text = nyaize(packed.text);
|
||||
}
|
||||
|
||||
if (!opts.skipHide) {
|
||||
await this.hideNote(packed, meId);
|
||||
}
|
||||
|
||||
return packed;
|
||||
}
|
||||
}
|
47
src/models/repositories/notification.ts
Normal file
47
src/models/repositories/notification.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Users, Notes } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { Notification } from '../entities/notification';
|
||||
|
||||
@EntityRepository(Notification)
|
||||
export class NotificationRepository extends Repository<Notification> {
|
||||
public packMany(
|
||||
notifications: any[],
|
||||
) {
|
||||
return Promise.all(notifications.map(x => this.pack(x)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: Notification['id'] | Notification,
|
||||
) {
|
||||
const notification = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return await rap({
|
||||
id: notification.id,
|
||||
createdAt: notification.createdAt,
|
||||
type: notification.type,
|
||||
userId: notification.notifierId,
|
||||
user: Users.pack(notification.notifier || notification.notifierId),
|
||||
...(notification.type === 'mention' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
} : {}),
|
||||
...(notification.type === 'reply' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
} : {}),
|
||||
...(notification.type === 'renote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
} : {}),
|
||||
...(notification.type === 'quote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
} : {}),
|
||||
...(notification.type === 'reaction' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
reaction: notification.reaction
|
||||
} : {}),
|
||||
...(notification.type === 'pollVote' ? {
|
||||
note: Notes.pack(notification.note || notification.noteId),
|
||||
choice: notification.choice
|
||||
} : {})
|
||||
});
|
||||
}
|
||||
}
|
11
src/models/repositories/signin.ts
Normal file
11
src/models/repositories/signin.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { Signin } from '../entities/signin';
|
||||
|
||||
@EntityRepository(Signin)
|
||||
export class SigninRepository extends Repository<Signin> {
|
||||
public async pack(
|
||||
src: any,
|
||||
) {
|
||||
return src;
|
||||
}
|
||||
}
|
16
src/models/repositories/user-list.ts
Normal file
16
src/models/repositories/user-list.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { EntityRepository, Repository } from 'typeorm';
|
||||
import { UserList } from '../entities/user-list';
|
||||
|
||||
@EntityRepository(UserList)
|
||||
export class UserListRepository extends Repository<UserList> {
|
||||
public async pack(
|
||||
src: any,
|
||||
) {
|
||||
const userList = typeof src === 'object' ? src : await this.findOne(src);
|
||||
|
||||
return {
|
||||
id: userList.id,
|
||||
name: userList.name
|
||||
};
|
||||
}
|
||||
}
|
198
src/models/repositories/user.ts
Normal file
198
src/models/repositories/user.ts
Normal file
|
@ -0,0 +1,198 @@
|
|||
import { EntityRepository, Repository, In } from 'typeorm';
|
||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
|
||||
@EntityRepository(User)
|
||||
export class UserRepository extends Repository<User> {
|
||||
public async getRelation(me: User['id'], target: User['id']) {
|
||||
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
|
||||
Followings.findOne({
|
||||
followerId: me,
|
||||
followeeId: target
|
||||
}),
|
||||
Followings.findOne({
|
||||
followerId: target,
|
||||
followeeId: me
|
||||
}),
|
||||
FollowRequests.findOne({
|
||||
followerId: me,
|
||||
followeeId: target
|
||||
}),
|
||||
FollowRequests.findOne({
|
||||
followerId: target,
|
||||
followeeId: me
|
||||
}),
|
||||
Blockings.findOne({
|
||||
blockerId: me,
|
||||
blockeeId: target
|
||||
}),
|
||||
Blockings.findOne({
|
||||
blockerId: target,
|
||||
blockeeId: me
|
||||
}),
|
||||
Mutings.findOne({
|
||||
muterId: me,
|
||||
muteeId: target
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
id: target,
|
||||
isFollowing: following1 != null,
|
||||
hasPendingFollowRequestFromYou: followReq1 != null,
|
||||
hasPendingFollowRequestToYou: followReq2 != null,
|
||||
isFollowed: following2 != null,
|
||||
isBlocking: toBlocking != null,
|
||||
isBlocked: fromBlocked != null,
|
||||
isMuted: mute != null
|
||||
};
|
||||
}
|
||||
|
||||
public packMany(
|
||||
users: (User['id'] | User)[],
|
||||
me?: User['id'] | User,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
includeSecrets?: boolean,
|
||||
includeHasUnreadNotes?: boolean
|
||||
}
|
||||
) {
|
||||
return Promise.all(users.map(u => this.pack(u, me, options)));
|
||||
}
|
||||
|
||||
public async pack(
|
||||
src: User['id'] | User,
|
||||
me?: User['id'] | User,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
includeSecrets?: boolean,
|
||||
includeHasUnreadNotes?: boolean
|
||||
}
|
||||
): Promise<Record<string, any>> {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
includeSecrets: false
|
||||
}, options);
|
||||
|
||||
const user = typeof src === 'object' ? src : await this.findOne(src);
|
||||
const meId = me ? typeof me === 'string' ? me : me.id : null;
|
||||
|
||||
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.id) : null;
|
||||
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
||||
|
||||
return await rap({
|
||||
id: user.id,
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
host: user.host,
|
||||
avatarUrl: user.avatarUrl,
|
||||
bannerUrl: user.bannerUrl,
|
||||
avatarColor: user.avatarColor,
|
||||
bannerColor: user.bannerColor,
|
||||
isAdmin: user.isAdmin,
|
||||
|
||||
// カスタム絵文字添付
|
||||
emojis: user.emojis.length > 0 ? Emojis.find({
|
||||
where: {
|
||||
name: In(user.emojis),
|
||||
host: user.host
|
||||
},
|
||||
select: ['name', 'host', 'url', 'aliases']
|
||||
}) : [],
|
||||
|
||||
...(opts.includeHasUnreadNotes ? {
|
||||
hasUnreadSpecifiedNotes: NoteUnreads.count({
|
||||
where: { userId: user.id, isSpecified: true },
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
hasUnreadMentions: NoteUnreads.count({
|
||||
where: { userId: user.id },
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
} : {}),
|
||||
|
||||
...(opts.detail ? {
|
||||
description: user.description,
|
||||
location: user.location,
|
||||
birthday: user.birthday,
|
||||
followersCount: user.followersCount,
|
||||
followingCount: user.followingCount,
|
||||
notesCount: user.notesCount,
|
||||
pinnedNoteIds: pins.map(pin => pin.noteId),
|
||||
pinnedNotes: Notes.packMany(pins.map(pin => pin.noteId), meId, {
|
||||
detail: true
|
||||
}),
|
||||
} : {}),
|
||||
|
||||
...(opts.detail && meId === user.id ? {
|
||||
avatarId: user.avatarId,
|
||||
bannerId: user.bannerId,
|
||||
autoWatch: user.autoWatch,
|
||||
alwaysMarkNsfw: user.alwaysMarkNsfw,
|
||||
carefulBot: user.carefulBot,
|
||||
hasUnreadMessagingMessage: MessagingMessages.count({
|
||||
where: {
|
||||
recipientId: user.id,
|
||||
isRead: false
|
||||
},
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
hasUnreadNotification: Notifications.count({
|
||||
where: {
|
||||
userId: user.id,
|
||||
isRead: false
|
||||
},
|
||||
take: 1
|
||||
}).then(count => count > 0),
|
||||
pendingReceivedFollowRequestsCount: FollowRequests.count({
|
||||
followeeId: user.id
|
||||
}),
|
||||
} : {}),
|
||||
|
||||
...(relation ? {
|
||||
isFollowing: relation.isFollowing,
|
||||
isFollowed: relation.isFollowed,
|
||||
hasPendingFollowRequestFromYou: relation.hasPendingFollowRequestFromYou,
|
||||
hasPendingFollowRequestToYou: relation.hasPendingFollowRequestToYou,
|
||||
isBlocking: relation.isBlocking,
|
||||
isBlocked: relation.isBlocked,
|
||||
isMuted: relation.isMuted,
|
||||
} : {})
|
||||
});
|
||||
}
|
||||
|
||||
public isLocalUser(user: User): user is ILocalUser {
|
||||
return user.host === null;
|
||||
}
|
||||
|
||||
public isRemoteUser(user: User): user is IRemoteUser {
|
||||
return !this.isLocalUser(user);
|
||||
}
|
||||
|
||||
//#region Validators
|
||||
public validateUsername(username: string, remote = false): boolean {
|
||||
return typeof username == 'string' && (remote ? /^\w([\w-]*\w)?$/ : /^\w{1,20}$/).test(username);
|
||||
}
|
||||
|
||||
public validatePassword(password: string): boolean {
|
||||
return typeof password == 'string' && password != '';
|
||||
}
|
||||
|
||||
public isValidName(name?: string): boolean {
|
||||
return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != '');
|
||||
}
|
||||
|
||||
public isValidDescription(description: string): boolean {
|
||||
return typeof description == 'string' && description.length < 500 && description.trim() != '';
|
||||
}
|
||||
|
||||
public isValidLocation(location: string): boolean {
|
||||
return typeof location == 'string' && location.length < 50 && location.trim() != '';
|
||||
}
|
||||
|
||||
public isValidBirthday(birthday: string): boolean {
|
||||
return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday);
|
||||
}
|
||||
//#endregion
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const Signin = db.get<ISignin>('signin');
|
||||
export default Signin;
|
||||
|
||||
export interface ISignin {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
userId: mongo.ObjectID;
|
||||
ip: string;
|
||||
headers: any;
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a signin record for API response
|
||||
*
|
||||
* @param {any} record
|
||||
* @return {Promise<any>}
|
||||
*/
|
||||
export const pack = (
|
||||
record: any
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
|
||||
const _record = deepcopy(record);
|
||||
|
||||
// Rename _id to id
|
||||
_record.id = _record._id;
|
||||
delete _record._id;
|
||||
|
||||
resolve(_record);
|
||||
});
|
|
@ -1,13 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import db from '../db/mongodb';
|
||||
|
||||
const SwSubscription = db.get<ISwSubscription>('swSubscriptions');
|
||||
export default SwSubscription;
|
||||
|
||||
export interface ISwSubscription {
|
||||
_id: mongo.ObjectID;
|
||||
userId: mongo.ObjectID;
|
||||
endpoint: string;
|
||||
auth: string;
|
||||
publickey: string;
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
|
||||
const UserList = db.get<IUserList>('userList');
|
||||
export default UserList;
|
||||
|
||||
export interface IUserList {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
title: string;
|
||||
userId: mongo.ObjectID;
|
||||
userIds: mongo.ObjectID[];
|
||||
}
|
||||
|
||||
export const pack = (
|
||||
userList: string | mongo.ObjectID | IUserList
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
let _userList: any;
|
||||
|
||||
if (isObjectId(userList)) {
|
||||
_userList = await UserList.findOne({
|
||||
_id: userList
|
||||
});
|
||||
} else if (typeof userList === 'string') {
|
||||
_userList = await UserList.findOne({
|
||||
_id: new mongo.ObjectID(userList)
|
||||
});
|
||||
} else {
|
||||
_userList = deepcopy(userList);
|
||||
}
|
||||
|
||||
if (!_userList) throw `invalid userList arg ${userList}`;
|
||||
|
||||
// Rename _id to id
|
||||
_userList.id = _userList._id;
|
||||
delete _userList._id;
|
||||
|
||||
resolve(_userList);
|
||||
});
|
|
@ -1,438 +0,0 @@
|
|||
import * as mongo from 'mongodb';
|
||||
import * as deepcopy from 'deepcopy';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import db from '../db/mongodb';
|
||||
import isObjectId from '../misc/is-objectid';
|
||||
import { packMany as packNoteMany } from './note';
|
||||
import Following from './following';
|
||||
import Blocking from './blocking';
|
||||
import Mute from './mute';
|
||||
import { getFriendIds } from '../server/api/common/get-friends';
|
||||
import config from '../config';
|
||||
import FollowRequest from './follow-request';
|
||||
import fetchMeta from '../misc/fetch-meta';
|
||||
import Emoji from './emoji';
|
||||
import { dbLogger } from '../db/logger';
|
||||
|
||||
const User = db.get<IUser>('users');
|
||||
|
||||
User.createIndex('createdAt');
|
||||
User.createIndex('updatedAt');
|
||||
User.createIndex('followersCount');
|
||||
User.createIndex('tags');
|
||||
User.createIndex('isSuspended');
|
||||
User.createIndex('username');
|
||||
User.createIndex('usernameLower');
|
||||
User.createIndex('host');
|
||||
User.createIndex(['username', 'host'], { unique: true });
|
||||
User.createIndex(['usernameLower', 'host'], { unique: true });
|
||||
User.createIndex('token', { sparse: true, unique: true });
|
||||
User.createIndex('uri', { sparse: true, unique: true });
|
||||
|
||||
export default User;
|
||||
|
||||
type IUserBase = {
|
||||
_id: mongo.ObjectID;
|
||||
createdAt: Date;
|
||||
updatedAt?: Date;
|
||||
deletedAt?: Date;
|
||||
followersCount: number;
|
||||
followingCount: number;
|
||||
name?: string;
|
||||
notesCount: number;
|
||||
username: string;
|
||||
usernameLower: string;
|
||||
avatarId: mongo.ObjectID;
|
||||
bannerId: mongo.ObjectID;
|
||||
avatarUrl?: string;
|
||||
bannerUrl?: string;
|
||||
avatarColor?: any;
|
||||
bannerColor?: any;
|
||||
wallpaperId: mongo.ObjectID;
|
||||
wallpaperUrl?: string;
|
||||
data: any;
|
||||
description: string;
|
||||
lang?: string;
|
||||
pinnedNoteIds: mongo.ObjectID[];
|
||||
emojis?: string[];
|
||||
tags?: string[];
|
||||
|
||||
isDeleted: boolean;
|
||||
|
||||
/**
|
||||
* 凍結されているか否か
|
||||
*/
|
||||
isSuspended: boolean;
|
||||
|
||||
/**
|
||||
* サイレンスされているか否か
|
||||
*/
|
||||
isSilenced: boolean;
|
||||
|
||||
/**
|
||||
* 鍵アカウントか否か
|
||||
*/
|
||||
isLocked: boolean;
|
||||
|
||||
/**
|
||||
* Botか否か
|
||||
*/
|
||||
isBot: boolean;
|
||||
|
||||
/**
|
||||
* Botからのフォローを承認制にするか
|
||||
*/
|
||||
carefulBot: boolean;
|
||||
|
||||
/**
|
||||
* フォローしているユーザーからのフォローリクエストを自動承認するか
|
||||
*/
|
||||
autoAcceptFollowed: boolean;
|
||||
|
||||
/**
|
||||
* このアカウントに届いているフォローリクエストの数
|
||||
*/
|
||||
pendingReceivedFollowRequestsCount: number;
|
||||
|
||||
host: string;
|
||||
};
|
||||
|
||||
export interface ILocalUser extends IUserBase {
|
||||
host: null;
|
||||
keypair: string;
|
||||
email: string;
|
||||
emailVerified?: boolean;
|
||||
emailVerifyCode?: string;
|
||||
password: string;
|
||||
token: string;
|
||||
twitter: {
|
||||
accessToken: string;
|
||||
accessTokenSecret: string;
|
||||
userId: string;
|
||||
screenName: string;
|
||||
};
|
||||
github: {
|
||||
accessToken: string;
|
||||
id: string;
|
||||
login: string;
|
||||
};
|
||||
discord: {
|
||||
accessToken: string;
|
||||
refreshToken: string;
|
||||
expiresDate: number;
|
||||
id: string;
|
||||
username: string;
|
||||
discriminator: string;
|
||||
};
|
||||
profile: {
|
||||
location: string;
|
||||
birthday: string; // 'YYYY-MM-DD'
|
||||
tags: string[];
|
||||
};
|
||||
fields?: {
|
||||
name: string;
|
||||
value: string;
|
||||
}[];
|
||||
isCat: boolean;
|
||||
isAdmin?: boolean;
|
||||
isModerator?: boolean;
|
||||
isVerified?: boolean;
|
||||
twoFactorSecret: string;
|
||||
twoFactorEnabled: boolean;
|
||||
twoFactorTempSecret?: string;
|
||||
clientSettings: any;
|
||||
settings: {
|
||||
autoWatch: boolean;
|
||||
alwaysMarkNsfw?: boolean;
|
||||
};
|
||||
hasUnreadNotification: boolean;
|
||||
hasUnreadMessagingMessage: boolean;
|
||||
}
|
||||
|
||||
export interface IRemoteUser extends IUserBase {
|
||||
inbox: string;
|
||||
sharedInbox?: string;
|
||||
featured?: string;
|
||||
endpoints: string[];
|
||||
uri: string;
|
||||
url?: string;
|
||||
publicKey: {
|
||||
id: string;
|
||||
publicKeyPem: string;
|
||||
};
|
||||
lastFetchedAt: Date;
|
||||
isAdmin: false;
|
||||
isModerator: false;
|
||||
}
|
||||
|
||||
export type IUser = ILocalUser | IRemoteUser;
|
||||
|
||||
export const isLocalUser = (user: any): user is ILocalUser =>
|
||||
user.host === null;
|
||||
|
||||
export const isRemoteUser = (user: any): user is IRemoteUser =>
|
||||
!isLocalUser(user);
|
||||
|
||||
//#region Validators
|
||||
export function validateUsername(username: string, remote = false): boolean {
|
||||
return typeof username == 'string' && (remote ? /^\w([\w-]*\w)?$/ : /^\w{1,20}$/).test(username);
|
||||
}
|
||||
|
||||
export function validatePassword(password: string): boolean {
|
||||
return typeof password == 'string' && password != '';
|
||||
}
|
||||
|
||||
export function isValidName(name?: string): boolean {
|
||||
return name === null || (typeof name == 'string' && name.length < 50 && name.trim() != '');
|
||||
}
|
||||
|
||||
export function isValidDescription(description: string): boolean {
|
||||
return typeof description == 'string' && description.length < 500 && description.trim() != '';
|
||||
}
|
||||
|
||||
export function isValidLocation(location: string): boolean {
|
||||
return typeof location == 'string' && location.length < 50 && location.trim() != '';
|
||||
}
|
||||
|
||||
export function isValidBirthday(birthday: string): boolean {
|
||||
return typeof birthday == 'string' && /^([0-9]{4})\-([0-9]{2})-([0-9]{2})$/.test(birthday);
|
||||
}
|
||||
//#endregion
|
||||
|
||||
export async function getRelation(me: mongo.ObjectId, target: mongo.ObjectId) {
|
||||
const [following1, following2, followReq1, followReq2, toBlocking, fromBlocked, mute] = await Promise.all([
|
||||
Following.findOne({
|
||||
followerId: me,
|
||||
followeeId: target
|
||||
}),
|
||||
Following.findOne({
|
||||
followerId: target,
|
||||
followeeId: me
|
||||
}),
|
||||
FollowRequest.findOne({
|
||||
followerId: me,
|
||||
followeeId: target
|
||||
}),
|
||||
FollowRequest.findOne({
|
||||
followerId: target,
|
||||
followeeId: me
|
||||
}),
|
||||
Blocking.findOne({
|
||||
blockerId: me,
|
||||
blockeeId: target
|
||||
}),
|
||||
Blocking.findOne({
|
||||
blockerId: target,
|
||||
blockeeId: me
|
||||
}),
|
||||
Mute.findOne({
|
||||
muterId: me,
|
||||
muteeId: target
|
||||
})
|
||||
]);
|
||||
|
||||
return {
|
||||
id: target,
|
||||
isFollowing: following1 !== null,
|
||||
hasPendingFollowRequestFromYou: followReq1 !== null,
|
||||
hasPendingFollowRequestToYou: followReq2 !== null,
|
||||
isFollowed: following2 !== null,
|
||||
isBlocking: toBlocking !== null,
|
||||
isBlocked: fromBlocked !== null,
|
||||
isMuted: mute !== null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pack a user for API response
|
||||
*
|
||||
* @param user target
|
||||
* @param me? serializee
|
||||
* @param options? serialize options
|
||||
* @return Packed user
|
||||
*/
|
||||
export const pack = (
|
||||
user: string | mongo.ObjectID | IUser,
|
||||
me?: string | mongo.ObjectID | IUser,
|
||||
options?: {
|
||||
detail?: boolean,
|
||||
includeSecrets?: boolean,
|
||||
includeHasUnreadNotes?: boolean
|
||||
}
|
||||
) => new Promise<any>(async (resolve, reject) => {
|
||||
const opts = Object.assign({
|
||||
detail: false,
|
||||
includeSecrets: false
|
||||
}, options);
|
||||
|
||||
let _user: any;
|
||||
|
||||
const fields = opts.detail ? {} : {
|
||||
name: true,
|
||||
username: true,
|
||||
host: true,
|
||||
avatarColor: true,
|
||||
avatarUrl: true,
|
||||
emojis: true,
|
||||
isCat: true,
|
||||
isBot: true,
|
||||
isAdmin: true,
|
||||
isVerified: true
|
||||
};
|
||||
|
||||
// Populate the user if 'user' is ID
|
||||
if (isObjectId(user)) {
|
||||
_user = await User.findOne({
|
||||
_id: user
|
||||
}, { fields });
|
||||
} else if (typeof user === 'string') {
|
||||
_user = await User.findOne({
|
||||
_id: new mongo.ObjectID(user)
|
||||
}, { fields });
|
||||
} else {
|
||||
_user = deepcopy(user);
|
||||
}
|
||||
|
||||
// (データベースの欠損などで)ユーザーがデータベース上に見つからなかったとき
|
||||
if (_user == null) {
|
||||
dbLogger.warn(`user not found on database: ${user}`);
|
||||
return resolve(null);
|
||||
}
|
||||
|
||||
// Me
|
||||
const meId: mongo.ObjectID = me
|
||||
? isObjectId(me)
|
||||
? me as mongo.ObjectID
|
||||
: typeof me === 'string'
|
||||
? new mongo.ObjectID(me)
|
||||
: (me as IUser)._id
|
||||
: null;
|
||||
|
||||
// Rename _id to id
|
||||
_user.id = _user._id;
|
||||
delete _user._id;
|
||||
|
||||
delete _user.usernameLower;
|
||||
delete _user.emailVerifyCode;
|
||||
|
||||
if (_user.host == null) {
|
||||
// Remove private properties
|
||||
delete _user.keypair;
|
||||
delete _user.password;
|
||||
delete _user.token;
|
||||
delete _user.twoFactorTempSecret;
|
||||
delete _user.two_factor_temp_secret; // 後方互換性のため
|
||||
delete _user.twoFactorSecret;
|
||||
if (_user.twitter) {
|
||||
delete _user.twitter.accessToken;
|
||||
delete _user.twitter.accessTokenSecret;
|
||||
}
|
||||
if (_user.github) {
|
||||
delete _user.github.accessToken;
|
||||
}
|
||||
if (_user.discord) {
|
||||
delete _user.discord.accessToken;
|
||||
delete _user.discord.refreshToken;
|
||||
delete _user.discord.expiresDate;
|
||||
}
|
||||
|
||||
// Visible via only the official client
|
||||
if (!opts.includeSecrets) {
|
||||
delete _user.email;
|
||||
delete _user.emailVerified;
|
||||
delete _user.settings;
|
||||
delete _user.clientSettings;
|
||||
}
|
||||
|
||||
if (!opts.detail) {
|
||||
delete _user.twoFactorEnabled;
|
||||
}
|
||||
} else {
|
||||
delete _user.publicKey;
|
||||
}
|
||||
|
||||
if (_user.avatarUrl == null) {
|
||||
_user.avatarUrl = `${config.driveUrl}/default-avatar.jpg`;
|
||||
}
|
||||
|
||||
if (!meId || !meId.equals(_user.id) || !opts.detail) {
|
||||
delete _user.avatarId;
|
||||
delete _user.bannerId;
|
||||
delete _user.hasUnreadMessagingMessage;
|
||||
delete _user.hasUnreadNotification;
|
||||
}
|
||||
|
||||
if (meId && !meId.equals(_user.id) && opts.detail) {
|
||||
const relation = await getRelation(meId, _user.id);
|
||||
|
||||
_user.isFollowing = relation.isFollowing;
|
||||
_user.isFollowed = relation.isFollowed;
|
||||
_user.hasPendingFollowRequestFromYou = relation.hasPendingFollowRequestFromYou;
|
||||
_user.hasPendingFollowRequestToYou = relation.hasPendingFollowRequestToYou;
|
||||
_user.isBlocking = relation.isBlocking;
|
||||
_user.isBlocked = relation.isBlocked;
|
||||
_user.isMuted = relation.isMuted;
|
||||
}
|
||||
|
||||
if (opts.detail) {
|
||||
if (_user.pinnedNoteIds) {
|
||||
// Populate pinned notes
|
||||
_user.pinnedNotes = packNoteMany(_user.pinnedNoteIds, meId, {
|
||||
detail: true
|
||||
});
|
||||
}
|
||||
|
||||
if (meId && !meId.equals(_user.id)) {
|
||||
const myFollowingIds = await getFriendIds(meId);
|
||||
|
||||
// Get following you know count
|
||||
_user.followingYouKnowCount = Following.count({
|
||||
followeeId: { $in: myFollowingIds },
|
||||
followerId: _user.id
|
||||
});
|
||||
|
||||
// Get followers you know count
|
||||
_user.followersYouKnowCount = Following.count({
|
||||
followeeId: _user.id,
|
||||
followerId: { $in: myFollowingIds }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts.includeHasUnreadNotes) {
|
||||
delete _user.hasUnreadSpecifiedNotes;
|
||||
delete _user.hasUnreadMentions;
|
||||
}
|
||||
|
||||
// カスタム絵文字添付
|
||||
if (_user.emojis) {
|
||||
_user.emojis = Emoji.find({
|
||||
name: { $in: _user.emojis },
|
||||
host: _user.host
|
||||
}, {
|
||||
fields: { _id: false }
|
||||
});
|
||||
}
|
||||
|
||||
// resolve promises in _user object
|
||||
_user = await rap(_user);
|
||||
|
||||
resolve(_user);
|
||||
});
|
||||
|
||||
/*
|
||||
function img(url) {
|
||||
return {
|
||||
thumbnail: {
|
||||
large: `${url}`,
|
||||
medium: '',
|
||||
small: ''
|
||||
}
|
||||
};
|
||||
}
|
||||
*/
|
||||
|
||||
export async function fetchProxyAccount(): Promise<ILocalUser> {
|
||||
const meta = await fetchMeta();
|
||||
return await User.findOne({ username: meta.proxyAccount, host: null }) as ILocalUser;
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue