Compare commits
40 Commits
11.0.0-alp
...
11.0.0-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
64e10e9619 | ||
![]() |
b89cffe98d | ||
![]() |
bd76ba702f | ||
![]() |
5d52e9ce6b | ||
![]() |
3c29027ca3 | ||
![]() |
2ff3069d23 | ||
![]() |
4198246351 | ||
![]() |
2b6389b4dc | ||
![]() |
d7df75ae6c | ||
![]() |
11c30eccb3 | ||
![]() |
ab8c6515b8 | ||
![]() |
d4ad36fa41 | ||
![]() |
4d688be3df | ||
![]() |
d2b75f3501 | ||
![]() |
46b78cb4ff | ||
![]() |
5d6e0d0f37 | ||
![]() |
e19d0a37bb | ||
![]() |
dea3e2132e | ||
![]() |
91c1ceefbd | ||
![]() |
5c50989970 | ||
![]() |
2a7e3b9c51 | ||
![]() |
ab302df0ae | ||
![]() |
21e5809993 | ||
![]() |
c58afc67e8 | ||
![]() |
8e344f2deb | ||
![]() |
c28f4ffb3f | ||
![]() |
2a40240310 | ||
![]() |
0e9fba9287 | ||
![]() |
2e3dd2a30a | ||
![]() |
dda5f6559d | ||
![]() |
4152e59638 | ||
![]() |
9d5a92bce6 | ||
![]() |
30172b92e6 | ||
![]() |
8468a9d4c7 | ||
![]() |
626cfb61ac | ||
![]() |
9603f3fa4f | ||
![]() |
619a1b9e53 | ||
![]() |
62e76ad588 | ||
![]() |
236d72685d | ||
![]() |
72a5f7b1e2 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -8,11 +8,27 @@ If you encounter any problems with updating, please try the following:
|
|||||||
11.0.0
|
11.0.0
|
||||||
----------
|
----------
|
||||||
* データベースがMongoDBからPostgreSQLに変更されました
|
* データベースがMongoDBからPostgreSQLに変更されました
|
||||||
|
* アカウントを完全に削除できるように
|
||||||
|
* ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように
|
||||||
|
* フォロー申請数が実際より1すくなくなる問題を修正
|
||||||
|
* リストからアカウント削除したユーザーを削除できない問題を修正
|
||||||
|
* リストTLでフォローしていないユーザーの非公開投稿が流れる問題を修正
|
||||||
|
* リストTLでダイレクト投稿が流れない問題を修正
|
||||||
|
* ミュートしているユーザーの投稿がタイムラインに流れてくることがある問題を修正
|
||||||
|
|
||||||
### APIの破壊的変更
|
### APIの破壊的変更
|
||||||
* v10時点で deprecated だったパラメータなどを削除
|
* v10時点で deprecated だったパラメータなどを削除
|
||||||
* ユーザーリストの title が name に
|
* ユーザーリストの title が name に
|
||||||
|
|
||||||
|
10.100.0
|
||||||
|
----------
|
||||||
|
* ユーザーリストでフォローボタンを表示するように
|
||||||
|
* ドライブのファイルのサムネイルを修正
|
||||||
|
* 投稿ウィジットでローカルのみの公開範囲で投稿できない問題を修正
|
||||||
|
* TLを遡った時に抜けがある時がある問題を修正
|
||||||
|
* ユーザータイムラインが投稿日時順ではなくなっているのを修正
|
||||||
|
* 10.99.0 でチャートのレンダリングがおかしい問題を修正
|
||||||
|
|
||||||
10.99.0
|
10.99.0
|
||||||
----------
|
----------
|
||||||
* manifest.json にインスタンス名を反映させるように
|
* manifest.json にインスタンス名を反映させるように
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "11.0.0-alpha.7",
|
"version": "11.0.0-beta.4",
|
||||||
"codename": "daybreak",
|
"codename": "daybreak",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -258,7 +258,6 @@
|
|||||||
"vuex": "3.1.0",
|
"vuex": "3.1.0",
|
||||||
"vuex-persistedstate": "2.5.4",
|
"vuex-persistedstate": "2.5.4",
|
||||||
"web-push": "3.3.3",
|
"web-push": "3.3.3",
|
||||||
"webfinger.js": "2.7.0",
|
|
||||||
"webpack": "4.28.4",
|
"webpack": "4.28.4",
|
||||||
"webpack-cli": "3.2.3",
|
"webpack-cli": "3.2.3",
|
||||||
"websocket": "1.0.28",
|
"websocket": "1.0.28",
|
||||||
|
65
src/@types/webfinger.js.d.ts
vendored
65
src/@types/webfinger.js.d.ts
vendored
@@ -1,65 +0,0 @@
|
|||||||
declare module 'webfinger.js' {
|
|
||||||
interface IWebFingerConstructorConfig {
|
|
||||||
tls_only?: boolean;
|
|
||||||
webfist_fallback?: boolean;
|
|
||||||
uri_fallback?: boolean;
|
|
||||||
request_timeout?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
type JRDProperties = { [type: string]: string };
|
|
||||||
|
|
||||||
interface IJRDLink {
|
|
||||||
rel: string;
|
|
||||||
type?: string;
|
|
||||||
href?: string;
|
|
||||||
template?: string;
|
|
||||||
titles?: { [lang: string]: string };
|
|
||||||
properties?: JRDProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IJRD {
|
|
||||||
subject?: string;
|
|
||||||
expires?: Date;
|
|
||||||
aliases?: string[];
|
|
||||||
properties?: JRDProperties;
|
|
||||||
links?: IJRDLink[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IIDXLinks {
|
|
||||||
'avatar': IJRDLink[];
|
|
||||||
'remotestorage': IJRDLink[];
|
|
||||||
'blog': IJRDLink[];
|
|
||||||
'vcard': IJRDLink[];
|
|
||||||
'updates': IJRDLink[];
|
|
||||||
'share': IJRDLink[];
|
|
||||||
'profile': IJRDLink[];
|
|
||||||
'webfist': IJRDLink[];
|
|
||||||
'camlistore': IJRDLink[];
|
|
||||||
[type: string]: IJRDLink[];
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IIDXProperties {
|
|
||||||
'name': string;
|
|
||||||
[type: string]: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface IIDX {
|
|
||||||
links: IIDXLinks;
|
|
||||||
properties: IIDXProperties;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ILookupCallbackResult {
|
|
||||||
object: IJRD;
|
|
||||||
json: string;
|
|
||||||
idx: IIDX;
|
|
||||||
}
|
|
||||||
|
|
||||||
type LookupCallback = (err: Error | string, result?: ILookupCallbackResult) => void;
|
|
||||||
|
|
||||||
export class WebFinger {
|
|
||||||
constructor(config?: IWebFingerConstructorConfig);
|
|
||||||
|
|
||||||
public lookup(address: string, cb: LookupCallback): NodeJS.Timeout;
|
|
||||||
public lookupLink(address: string, rel: string, cb: IJRDLink): void;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -8,7 +8,7 @@
|
|||||||
<div class="is-remote" v-if="user.host != null">
|
<div class="is-remote" v-if="user.host != null">
|
||||||
<details>
|
<details>
|
||||||
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
|
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
|
||||||
<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
<header :style="bannerStyle">
|
<header :style="bannerStyle">
|
||||||
|
@@ -4,7 +4,7 @@
|
|||||||
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
|
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
|
||||||
<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="main">
|
<div class="main">
|
||||||
<x-header class="header" :user="user"/>
|
<x-header class="header" :user="user"/>
|
||||||
|
@@ -5,7 +5,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<div class="wwtwuxyh" v-if="!fetching">
|
<div class="wwtwuxyh" v-if="!fetching">
|
||||||
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
|
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
|
||||||
<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
|
<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
|
||||||
<header>
|
<header>
|
||||||
<div class="banner" :style="style"></div>
|
<div class="banner" :style="style"></div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
kind: 'light',
|
kind: 'light',
|
||||||
|
|
||||||
vars: {
|
vars: {
|
||||||
primary: '#fb4e4e',
|
primary: '#f18570',
|
||||||
secondary: '#fff',
|
secondary: '#fff',
|
||||||
text: '#666',
|
text: '#666',
|
||||||
},
|
},
|
||||||
|
@@ -7,7 +7,6 @@ import { URL } from 'url';
|
|||||||
import * as yaml from 'js-yaml';
|
import * as yaml from 'js-yaml';
|
||||||
import { Source, Mixin } from './types';
|
import { Source, Mixin } from './types';
|
||||||
import * as pkg from '../../package.json';
|
import * as pkg from '../../package.json';
|
||||||
import { toPuny } from '../misc/convert-host';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path of configuration directory
|
* Path of configuration directory
|
||||||
@@ -26,14 +25,14 @@ export default function load() {
|
|||||||
|
|
||||||
const mixin = {} as Mixin;
|
const mixin = {} as Mixin;
|
||||||
|
|
||||||
const url = validateUrl(config.url);
|
const url = tryCreateUrl(config.url);
|
||||||
|
|
||||||
config.url = toPuny(normalizeUrl(config.url));
|
config.url = url.origin;
|
||||||
|
|
||||||
config.port = config.port || parseInt(process.env.PORT, 10);
|
config.port = config.port || parseInt(process.env.PORT, 10);
|
||||||
|
|
||||||
mixin.host = toPuny(url.host);
|
mixin.host = url.host;
|
||||||
mixin.hostname = toPuny(url.hostname);
|
mixin.hostname = url.hostname;
|
||||||
mixin.scheme = url.protocol.replace(/:$/, '');
|
mixin.scheme = url.protocol.replace(/:$/, '');
|
||||||
mixin.wsScheme = mixin.scheme.replace('http', 'ws');
|
mixin.wsScheme = mixin.scheme.replace('http', 'ws');
|
||||||
mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`;
|
mixin.wsUrl = `${mixin.wsScheme}://${mixin.host}`;
|
||||||
@@ -54,14 +53,3 @@ function tryCreateUrl(url: string) {
|
|||||||
throw `url="${url}" is not a valid URL.`;
|
throw `url="${url}" is not a valid URL.`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateUrl(url: string) {
|
|
||||||
const result = tryCreateUrl(url);
|
|
||||||
if (result.pathname.replace('/', '').length) throw `url="${url}" is not a valid URL, has a pathname.`;
|
|
||||||
if (!url.includes(result.host)) throw `url="${url}" is not a valid URL, has an invalid hostname.`;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeUrl(url: string) {
|
|
||||||
return url.endsWith('/') ? url.substr(0, url.length - 1) : url;
|
|
||||||
}
|
|
||||||
|
@@ -36,10 +36,10 @@ import { Emoji } from '../models/entities/emoji';
|
|||||||
import { ReversiGame } from '../models/entities/games/reversi/game';
|
import { ReversiGame } from '../models/entities/games/reversi/game';
|
||||||
import { ReversiMatching } from '../models/entities/games/reversi/matching';
|
import { ReversiMatching } from '../models/entities/games/reversi/matching';
|
||||||
import { UserNotePining } from '../models/entities/user-note-pinings';
|
import { UserNotePining } from '../models/entities/user-note-pinings';
|
||||||
import { UserServiceLinking } from '../models/entities/user-service-linking';
|
|
||||||
import { Poll } from '../models/entities/poll';
|
import { Poll } from '../models/entities/poll';
|
||||||
import { UserKeypair } from '../models/entities/user-keypair';
|
import { UserKeypair } from '../models/entities/user-keypair';
|
||||||
import { UserPublickey } from '../models/entities/user-publickey';
|
import { UserPublickey } from '../models/entities/user-publickey';
|
||||||
|
import { UserProfile } from '../models/entities/user-profile';
|
||||||
|
|
||||||
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
|
||||||
|
|
||||||
@@ -101,12 +101,12 @@ export function initDb(justBorrow = false, sync = false, log = false) {
|
|||||||
AuthSession,
|
AuthSession,
|
||||||
AccessToken,
|
AccessToken,
|
||||||
User,
|
User,
|
||||||
|
UserProfile,
|
||||||
UserKeypair,
|
UserKeypair,
|
||||||
UserPublickey,
|
UserPublickey,
|
||||||
UserList,
|
UserList,
|
||||||
UserListJoining,
|
UserListJoining,
|
||||||
UserNotePining,
|
UserNotePining,
|
||||||
UserServiceLinking,
|
|
||||||
Following,
|
Following,
|
||||||
FollowRequest,
|
FollowRequest,
|
||||||
Muting,
|
Muting,
|
||||||
|
103
src/migrate.ts
103
src/migrate.ts
@@ -16,7 +16,6 @@ import { InternalStorage } from './services/drive/internal-storage';
|
|||||||
import { createTemp } from './misc/create-temp';
|
import { createTemp } from './misc/create-temp';
|
||||||
import { Note } from './models/entities/note';
|
import { Note } from './models/entities/note';
|
||||||
import { Following } from './models/entities/following';
|
import { Following } from './models/entities/following';
|
||||||
import { genId } from './misc/gen-id';
|
|
||||||
import { Poll } from './models/entities/poll';
|
import { Poll } from './models/entities/poll';
|
||||||
import { PollVote } from './models/entities/poll-vote';
|
import { PollVote } from './models/entities/poll-vote';
|
||||||
import { NoteFavorite } from './models/entities/note-favorite';
|
import { NoteFavorite } from './models/entities/note-favorite';
|
||||||
@@ -26,6 +25,7 @@ import { UserKeypair } from './models/entities/user-keypair';
|
|||||||
import { extractPublic } from './crypto_key';
|
import { extractPublic } from './crypto_key';
|
||||||
import { Emoji } from './models/entities/emoji';
|
import { Emoji } from './models/entities/emoji';
|
||||||
import { toPuny } from './misc/convert-host';
|
import { toPuny } from './misc/convert-host';
|
||||||
|
import { UserProfile } from './models/entities/user-profile';
|
||||||
|
|
||||||
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
|
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
|
||||||
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;
|
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;
|
||||||
@@ -35,6 +35,9 @@ const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${(config as any).mongodb.ho
|
|||||||
const db = monk(uri);
|
const db = monk(uri);
|
||||||
let mdb: mongo.Db;
|
let mdb: mongo.Db;
|
||||||
|
|
||||||
|
const test = false;
|
||||||
|
const limit = 500;
|
||||||
|
|
||||||
const nativeDbConn = async (): Promise<mongo.Db> => {
|
const nativeDbConn = async (): Promise<mongo.Db> => {
|
||||||
if (mdb) return mdb;
|
if (mdb) return mdb;
|
||||||
|
|
||||||
@@ -70,6 +73,7 @@ const getDriveFileBucket = async (): Promise<mongo.GridFSBucket> => {
|
|||||||
async function main() {
|
async function main() {
|
||||||
await initDb();
|
await initDb();
|
||||||
const Users = getRepository(User);
|
const Users = getRepository(User);
|
||||||
|
const UserProfiles = getRepository(UserProfile);
|
||||||
const DriveFiles = getRepository(DriveFile);
|
const DriveFiles = getRepository(DriveFile);
|
||||||
const DriveFolders = getRepository(DriveFolder);
|
const DriveFolders = getRepository(DriveFolder);
|
||||||
const Notes = getRepository(Note);
|
const Notes = getRepository(Note);
|
||||||
@@ -90,27 +94,30 @@ async function main() {
|
|||||||
usernameLower: user.username.toLowerCase(),
|
usernameLower: user.username.toLowerCase(),
|
||||||
host: toPuny(user.host),
|
host: toPuny(user.host),
|
||||||
token: generateUserToken(),
|
token: generateUserToken(),
|
||||||
password: user.password,
|
isAdmin: user.isAdmin || false,
|
||||||
isAdmin: user.isAdmin,
|
|
||||||
autoAcceptFollowed: true,
|
|
||||||
autoWatch: false,
|
|
||||||
name: user.name,
|
name: user.name,
|
||||||
location: user.profile ? user.profile.location : null,
|
followersCount: user.followersCount || 0,
|
||||||
birthday: user.profile ? user.profile.birthday : null,
|
followingCount: user.followingCount || 0,
|
||||||
followersCount: user.followersCount,
|
notesCount: user.notesCount || 0,
|
||||||
followingCount: user.followingCount,
|
isBot: user.isBot || false,
|
||||||
notesCount: user.notesCount,
|
isCat: user.isCat || false,
|
||||||
description: user.description,
|
isVerified: user.isVerified || false,
|
||||||
isBot: user.isBot,
|
|
||||||
isCat: user.isCat,
|
|
||||||
isVerified: user.isVerified,
|
|
||||||
inbox: user.inbox,
|
inbox: user.inbox,
|
||||||
sharedInbox: user.sharedInbox,
|
sharedInbox: user.sharedInbox,
|
||||||
uri: user.uri,
|
uri: user.uri,
|
||||||
});
|
});
|
||||||
|
await UserProfiles.save({
|
||||||
|
userId: user._id.toHexString(),
|
||||||
|
description: user.description,
|
||||||
|
userHost: toPuny(user.host),
|
||||||
|
autoAcceptFollowed: true,
|
||||||
|
autoWatch: false,
|
||||||
|
password: user.password,
|
||||||
|
location: user.profile ? user.profile.location : null,
|
||||||
|
birthday: user.profile ? user.profile.birthday : null,
|
||||||
|
});
|
||||||
if (user.publicKey) {
|
if (user.publicKey) {
|
||||||
await UserPublickeys.save({
|
await UserPublickeys.save({
|
||||||
id: genId(),
|
|
||||||
userId: user._id.toHexString(),
|
userId: user._id.toHexString(),
|
||||||
keyId: user.publicKey.id,
|
keyId: user.publicKey.id,
|
||||||
keyPem: user.publicKey.publicKeyPem
|
keyPem: user.publicKey.publicKeyPem
|
||||||
@@ -118,7 +125,6 @@ async function main() {
|
|||||||
}
|
}
|
||||||
if (user.keypair) {
|
if (user.keypair) {
|
||||||
await UserKeypairs.save({
|
await UserKeypairs.save({
|
||||||
id: genId(),
|
|
||||||
userId: user._id.toHexString(),
|
userId: user._id.toHexString(),
|
||||||
publicKey: extractPublic(user.keypair),
|
publicKey: extractPublic(user.keypair),
|
||||||
privateKey: user.keypair,
|
privateKey: user.keypair,
|
||||||
@@ -129,7 +135,7 @@ async function main() {
|
|||||||
async function migrateFollowing(following: any) {
|
async function migrateFollowing(following: any) {
|
||||||
await Followings.save({
|
await Followings.save({
|
||||||
id: following._id.toHexString(),
|
id: following._id.toHexString(),
|
||||||
createdAt: following.createdAt || new Date(),
|
createdAt: new Date(),
|
||||||
followerId: following.followerId.toHexString(),
|
followerId: following.followerId.toHexString(),
|
||||||
followeeId: following.followeeId.toHexString(),
|
followeeId: following.followeeId.toHexString(),
|
||||||
|
|
||||||
@@ -156,6 +162,7 @@ async function main() {
|
|||||||
const user = await _User.findOne({
|
const user = await _User.findOne({
|
||||||
_id: file.metadata.userId
|
_id: file.metadata.userId
|
||||||
});
|
});
|
||||||
|
if (user == null) return;
|
||||||
if (file.metadata.storage && file.metadata.storage.key) { // when object storage
|
if (file.metadata.storage && file.metadata.storage.key) { // when object storage
|
||||||
await DriveFiles.save({
|
await DriveFiles.save({
|
||||||
id: file._id.toHexString(),
|
id: file._id.toHexString(),
|
||||||
@@ -165,7 +172,7 @@ async function main() {
|
|||||||
md5: file.md5,
|
md5: file.md5,
|
||||||
name: file.filename,
|
name: file.filename,
|
||||||
type: file.contentType,
|
type: file.contentType,
|
||||||
properties: file.metadata.properties,
|
properties: file.metadata.properties || {},
|
||||||
size: file.length,
|
size: file.length,
|
||||||
url: file.metadata.url,
|
url: file.metadata.url,
|
||||||
uri: file.metadata.uri,
|
uri: file.metadata.uri,
|
||||||
@@ -251,7 +258,6 @@ async function main() {
|
|||||||
|
|
||||||
if (note.poll) {
|
if (note.poll) {
|
||||||
await Polls.save({
|
await Polls.save({
|
||||||
id: genId(),
|
|
||||||
noteId: note._id.toHexString(),
|
noteId: note._id.toHexString(),
|
||||||
choices: note.poll.choices.map((x: any) => x.text),
|
choices: note.poll.choices.map((x: any) => x.text),
|
||||||
expiresAt: note.poll.expiresAt,
|
expiresAt: note.poll.expiresAt,
|
||||||
@@ -297,13 +303,13 @@ async function main() {
|
|||||||
const u = await _User.findOne({
|
const u = await _User.findOne({
|
||||||
_id: new mongo.ObjectId(user.id)
|
_id: new mongo.ObjectId(user.id)
|
||||||
});
|
});
|
||||||
const avatar = await DriveFiles.findOne(u.avatarId.toHexString());
|
const avatar = u.avatarId ? await DriveFiles.findOne(u.avatarId.toHexString()) : null;
|
||||||
const banner = await DriveFiles.findOne(u.bannerId.toHexString());
|
const banner = u.bannerId ? await DriveFiles.findOne(u.bannerId.toHexString()) : null;
|
||||||
await Users.update(user.id, {
|
await Users.update(user.id, {
|
||||||
avatarId: avatar.id,
|
avatarId: avatar ? avatar.id : null,
|
||||||
bannerId: banner.id,
|
bannerId: banner ? banner.id : null,
|
||||||
avatarUrl: avatar.url,
|
avatarUrl: avatar ? avatar.url : null,
|
||||||
bannerUrl: banner.url
|
bannerUrl: banner ? banner.url : null
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,9 +325,14 @@ async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const allUsersCount = await _User.count();
|
let allUsersCount = await _User.count({
|
||||||
|
deletedAt: { $exists: false }
|
||||||
|
});
|
||||||
|
if (test && allUsersCount > limit) allUsersCount = limit;
|
||||||
for (let i = 0; i < allUsersCount; i++) {
|
for (let i = 0; i < allUsersCount; i++) {
|
||||||
const user = await _User.findOne({}, {
|
const user = await _User.findOne({
|
||||||
|
deletedAt: { $exists: false }
|
||||||
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@@ -333,7 +344,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allFollowingsCount = await _Following.count();
|
let allFollowingsCount = await _Following.count();
|
||||||
|
if (test && allFollowingsCount > limit) allFollowingsCount = limit;
|
||||||
for (let i = 0; i < allFollowingsCount; i++) {
|
for (let i = 0; i < allFollowingsCount; i++) {
|
||||||
const following = await _Following.findOne({}, {
|
const following = await _Following.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@@ -347,7 +359,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDriveFoldersCount = await _DriveFolder.count();
|
let allDriveFoldersCount = await _DriveFolder.count();
|
||||||
|
if (test && allDriveFoldersCount > limit) allDriveFoldersCount = limit;
|
||||||
for (let i = 0; i < allDriveFoldersCount; i++) {
|
for (let i = 0; i < allDriveFoldersCount; i++) {
|
||||||
const folder = await _DriveFolder.findOne({}, {
|
const folder = await _DriveFolder.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@@ -361,9 +374,16 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allDriveFilesCount = await _DriveFile.count();
|
let allDriveFilesCount = await _DriveFile.count({
|
||||||
|
'metadata._user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
});
|
||||||
|
if (test && allDriveFilesCount > limit) allDriveFilesCount = limit;
|
||||||
for (let i = 0; i < allDriveFilesCount; i++) {
|
for (let i = 0; i < allDriveFilesCount; i++) {
|
||||||
const file = await _DriveFile.findOne({}, {
|
const file = await _DriveFile.findOne({
|
||||||
|
'metadata._user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
try {
|
try {
|
||||||
@@ -375,12 +395,15 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNotesCount = await _Note.count({
|
let allNotesCount = await _Note.count({
|
||||||
'_user.host': null
|
'_user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
});
|
});
|
||||||
|
if (test && allNotesCount > limit) allNotesCount = limit;
|
||||||
for (let i = 0; i < allNotesCount; i++) {
|
for (let i = 0; i < allNotesCount; i++) {
|
||||||
const note = await _Note.findOne({
|
const note = await _Note.findOne({
|
||||||
'_user.host': null
|
'_user.host': null,
|
||||||
|
'metadata.deletedAt': { $exists: false }
|
||||||
}, {
|
}, {
|
||||||
skip: i
|
skip: i
|
||||||
});
|
});
|
||||||
@@ -393,7 +416,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allPollVotesCount = await _PollVote.count();
|
let allPollVotesCount = await _PollVote.count();
|
||||||
|
if (test && allPollVotesCount > limit) allPollVotesCount = limit;
|
||||||
for (let i = 0; i < allPollVotesCount; i++) {
|
for (let i = 0; i < allPollVotesCount; i++) {
|
||||||
const vote = await _PollVote.findOne({}, {
|
const vote = await _PollVote.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@@ -407,7 +431,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNoteFavoritesCount = await _Favorite.count();
|
let allNoteFavoritesCount = await _Favorite.count();
|
||||||
|
if (test && allNoteFavoritesCount > limit) allNoteFavoritesCount = limit;
|
||||||
for (let i = 0; i < allNoteFavoritesCount; i++) {
|
for (let i = 0; i < allNoteFavoritesCount; i++) {
|
||||||
const favorite = await _Favorite.findOne({}, {
|
const favorite = await _Favorite.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@@ -421,7 +446,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allNoteReactionsCount = await _NoteReaction.count();
|
let allNoteReactionsCount = await _NoteReaction.count();
|
||||||
|
if (test && allNoteReactionsCount > limit) allNoteReactionsCount = limit;
|
||||||
for (let i = 0; i < allNoteReactionsCount; i++) {
|
for (let i = 0; i < allNoteReactionsCount; i++) {
|
||||||
const reaction = await _NoteReaction.findOne({}, {
|
const reaction = await _NoteReaction.findOne({}, {
|
||||||
skip: i
|
skip: i
|
||||||
@@ -435,7 +461,8 @@ async function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const allActualUsersCount = await Users.count();
|
let allActualUsersCount = await Users.count();
|
||||||
|
if (test && allActualUsersCount > limit) allActualUsersCount = limit;
|
||||||
for (let i = 0; i < allActualUsersCount; i++) {
|
for (let i = 0; i < allActualUsersCount; i++) {
|
||||||
const [user] = await Users.find({
|
const [user] = await Users.find({
|
||||||
take: 1,
|
take: 1,
|
||||||
|
@@ -1,19 +0,0 @@
|
|||||||
import getAcct from './acct/render';
|
|
||||||
import getUserName from './get-user-name';
|
|
||||||
import { User } from '../models/entities/user';
|
|
||||||
import { Users } from '../models';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ユーザーを表す文字列を取得します。
|
|
||||||
* @param user ユーザー
|
|
||||||
*/
|
|
||||||
export default function(user: User): string {
|
|
||||||
let string = `${getUserName(user)} (@${getAcct(user)})\n` +
|
|
||||||
`${user.notesCount}投稿、${user.followingCount}フォロー、${user.followersCount}フォロワー\n`;
|
|
||||||
|
|
||||||
if (Users.isLocalUser(user)) {
|
|
||||||
string += `場所: ${user.location}、誕生日: ${user.birthday}\n`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return string + `「${user.description}」`;
|
|
||||||
}
|
|
@@ -53,7 +53,7 @@ export class App {
|
|||||||
public permission: string[];
|
public permission: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The callbackUrl of the App.'
|
comment: 'The callbackUrl of the App.'
|
||||||
})
|
})
|
||||||
public callbackUrl: string | null;
|
public callbackUrl: string | null;
|
||||||
|
@@ -25,12 +25,12 @@ export class Emoji {
|
|||||||
public host: string | null;
|
public host: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
})
|
})
|
||||||
public url: string;
|
public url: string;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true
|
length: 512, nullable: true
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
|
|
||||||
|
@@ -53,13 +53,13 @@ export class FollowRequest {
|
|||||||
public followerHost: string | null;
|
public followerHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerInbox: string | null;
|
public followerInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerSharedInbox: string | null;
|
public followerSharedInbox: string | null;
|
||||||
@@ -71,13 +71,13 @@ export class FollowRequest {
|
|||||||
public followeeHost: string | null;
|
public followeeHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeInbox: string | null;
|
public followeeInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeSharedInbox: string | null;
|
public followeeSharedInbox: string | null;
|
||||||
|
@@ -48,13 +48,13 @@ export class Following {
|
|||||||
public followerHost: string | null;
|
public followerHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerInbox: string | null;
|
public followerInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followerSharedInbox: string | null;
|
public followerSharedInbox: string | null;
|
||||||
@@ -66,13 +66,13 @@ export class Following {
|
|||||||
public followeeHost: string | null;
|
public followeeHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeInbox: string | null;
|
public followeeInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: '[Denormalized]'
|
comment: '[Denormalized]'
|
||||||
})
|
})
|
||||||
public followeeSharedInbox: string | null;
|
public followeeSharedInbox: string | null;
|
||||||
|
@@ -78,27 +78,27 @@ export class Meta {
|
|||||||
public blockedHosts: string[];
|
public blockedHosts: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: '/assets/ai.png'
|
default: '/assets/ai.png'
|
||||||
})
|
})
|
||||||
public mascotImageUrl: string | null;
|
public mascotImageUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
})
|
||||||
public bannerUrl: string | null;
|
public bannerUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true,
|
nullable: true,
|
||||||
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
|
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
|
||||||
})
|
})
|
||||||
public errorImageUrl: string | null;
|
public errorImageUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
nullable: true
|
nullable: true
|
||||||
})
|
})
|
||||||
public iconUrl: string | null;
|
public iconUrl: string | null;
|
||||||
|
@@ -15,13 +15,6 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public createdAt: Date;
|
public createdAt: Date;
|
||||||
|
|
||||||
@Index()
|
|
||||||
@Column('timestamp with time zone', {
|
|
||||||
nullable: true,
|
|
||||||
comment: 'The updated date of the Note.'
|
|
||||||
})
|
|
||||||
public updatedAt: Date | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
@@ -126,7 +119,7 @@ export class Note {
|
|||||||
|
|
||||||
@Index({ unique: true })
|
@Index({ unique: true })
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The URI of a note. it will be null when the note is local.'
|
comment: 'The URI of a note. it will be null when the note is local.'
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
public uri: string | null;
|
||||||
@@ -195,12 +188,6 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public userHost: string | null;
|
public userHost: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
|
||||||
length: 128, nullable: true,
|
|
||||||
comment: '[Denormalized]'
|
|
||||||
})
|
|
||||||
public userInbox: string | null;
|
|
||||||
|
|
||||||
@Column({
|
@Column({
|
||||||
...id(),
|
...id(),
|
||||||
nullable: true,
|
nullable: true,
|
||||||
@@ -227,6 +214,14 @@ export class Note {
|
|||||||
})
|
})
|
||||||
public renoteUserHost: string | null;
|
public renoteUserHost: string | null;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<Note>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IMentionedRemoteUsers = {
|
export type IMentionedRemoteUsers = {
|
||||||
|
@@ -6,10 +6,6 @@ import { User } from './user';
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class Poll {
|
export class Poll {
|
||||||
@PrimaryColumn(id())
|
@PrimaryColumn(id())
|
||||||
public id: string;
|
|
||||||
|
|
||||||
@Index({ unique: true })
|
|
||||||
@Column(id())
|
|
||||||
public noteId: Note['id'];
|
public noteId: Note['id'];
|
||||||
|
|
||||||
@OneToOne(type => Note, {
|
@OneToOne(type => Note, {
|
||||||
@@ -57,6 +53,14 @@ export class Poll {
|
|||||||
})
|
})
|
||||||
public userHost: string | null;
|
public userHost: string | null;
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<Poll>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type IPoll = {
|
export type IPoll = {
|
||||||
|
@@ -21,7 +21,7 @@ export class SwSubscription {
|
|||||||
public user: User | null;
|
public user: User | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256,
|
length: 512,
|
||||||
})
|
})
|
||||||
public endpoint: string;
|
public endpoint: string;
|
||||||
|
|
||||||
|
@@ -1,14 +1,10 @@
|
|||||||
import { PrimaryColumn, Entity, Index, JoinColumn, Column, OneToOne } from 'typeorm';
|
import { PrimaryColumn, Entity, JoinColumn, Column, OneToOne } from 'typeorm';
|
||||||
import { User } from './user';
|
import { User } from './user';
|
||||||
import { id } from '../id';
|
import { id } from '../id';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class UserKeypair {
|
export class UserKeypair {
|
||||||
@PrimaryColumn(id())
|
@PrimaryColumn(id())
|
||||||
public id: string;
|
|
||||||
|
|
||||||
@Index({ unique: true })
|
|
||||||
@Column(id())
|
|
||||||
public userId: User['id'];
|
public userId: User['id'];
|
||||||
|
|
||||||
@OneToOne(type => User, {
|
@OneToOne(type => User, {
|
||||||
@@ -26,4 +22,12 @@ export class UserKeypair {
|
|||||||
length: 4096,
|
length: 4096,
|
||||||
})
|
})
|
||||||
public privateKey: string;
|
public privateKey: string;
|
||||||
|
|
||||||
|
constructor(data: Partial<UserKeypair>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
209
src/models/entities/user-profile.ts
Normal file
209
src/models/entities/user-profile.ts
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
import { Entity, Column, Index, OneToOne, JoinColumn, PrimaryColumn } from 'typeorm';
|
||||||
|
import { id } from '../id';
|
||||||
|
import { User } from './user';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class UserProfile {
|
||||||
|
@PrimaryColumn(id())
|
||||||
|
public userId: User['id'];
|
||||||
|
|
||||||
|
@OneToOne(type => User, {
|
||||||
|
onDelete: 'CASCADE'
|
||||||
|
})
|
||||||
|
@JoinColumn()
|
||||||
|
public user: User | 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('varchar', {
|
||||||
|
length: 1024, nullable: true,
|
||||||
|
comment: 'The description (bio) of the User.'
|
||||||
|
})
|
||||||
|
public description: string | null;
|
||||||
|
|
||||||
|
@Column('jsonb', {
|
||||||
|
default: [],
|
||||||
|
})
|
||||||
|
public fields: {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 512, nullable: true,
|
||||||
|
comment: 'Remote URL of the user.'
|
||||||
|
})
|
||||||
|
public url: string | null;
|
||||||
|
|
||||||
|
@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('boolean', {
|
||||||
|
default: false,
|
||||||
|
})
|
||||||
|
public twoFactorEnabled: boolean;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
|
//#region Linking
|
||||||
|
@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;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
//#region Denormalized fields
|
||||||
|
@Index()
|
||||||
|
@Column('varchar', {
|
||||||
|
length: 128, nullable: true,
|
||||||
|
comment: '[Denormalized]'
|
||||||
|
})
|
||||||
|
public userHost: string | null;
|
||||||
|
//#endregion
|
||||||
|
|
||||||
|
constructor(data: Partial<UserProfile>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -5,10 +5,6 @@ import { id } from '../id';
|
|||||||
@Entity()
|
@Entity()
|
||||||
export class UserPublickey {
|
export class UserPublickey {
|
||||||
@PrimaryColumn(id())
|
@PrimaryColumn(id())
|
||||||
public id: string;
|
|
||||||
|
|
||||||
@Index({ unique: true })
|
|
||||||
@Column(id())
|
|
||||||
public userId: User['id'];
|
public userId: User['id'];
|
||||||
|
|
||||||
@OneToOne(type => User, {
|
@OneToOne(type => User, {
|
||||||
@@ -27,4 +23,12 @@ export class UserPublickey {
|
|||||||
length: 4096,
|
length: 4096,
|
||||||
})
|
})
|
||||||
public keyPem: string;
|
public keyPem: string;
|
||||||
|
|
||||||
|
constructor(data: Partial<UserPublickey>) {
|
||||||
|
if (data == null) return;
|
||||||
|
|
||||||
|
for (const [k, v] of Object.entries(data)) {
|
||||||
|
(this as any)[k] = v;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,108 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
@@ -45,18 +45,6 @@ export class User {
|
|||||||
})
|
})
|
||||||
public name: string | null;
|
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', {
|
@Column('integer', {
|
||||||
default: 0,
|
default: 0,
|
||||||
comment: 'The count of followers.'
|
comment: 'The count of followers.'
|
||||||
@@ -101,12 +89,6 @@ export class User {
|
|||||||
@JoinColumn()
|
@JoinColumn()
|
||||||
public banner: DriveFile | null;
|
public banner: DriveFile | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
|
||||||
length: 1024, nullable: true,
|
|
||||||
comment: 'The description (bio) of the User.'
|
|
||||||
})
|
|
||||||
public description: string | null;
|
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, array: true, default: '{}'
|
length: 128, array: true, default: '{}'
|
||||||
@@ -114,38 +96,12 @@ export class User {
|
|||||||
public tags: string[];
|
public tags: string[];
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, nullable: true,
|
length: 512, 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;
|
public avatarUrl: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
})
|
})
|
||||||
public bannerUrl: string | null;
|
public bannerUrl: string | null;
|
||||||
|
|
||||||
@@ -206,11 +162,6 @@ export class User {
|
|||||||
})
|
})
|
||||||
public isVerified: boolean;
|
public isVerified: boolean;
|
||||||
|
|
||||||
@Column('boolean', {
|
|
||||||
default: false,
|
|
||||||
})
|
|
||||||
public twoFactorEnabled: boolean;
|
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 128, array: true, default: '{}'
|
length: 128, array: true, default: '{}'
|
||||||
})
|
})
|
||||||
@@ -224,36 +175,30 @@ export class User {
|
|||||||
public host: string | null;
|
public host: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The inbox of the User. It will be null if the origin of the user is local.'
|
comment: 'The inbox URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public inbox: string | null;
|
public inbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The sharedInbox of the User. It will be null if the origin of the user is local.'
|
comment: 'The sharedInbox URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public sharedInbox: string | null;
|
public sharedInbox: string | null;
|
||||||
|
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The featured of the User. It will be null if the origin of the user is local.'
|
comment: 'The featured URL of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public featured: string | null;
|
public featured: string | null;
|
||||||
|
|
||||||
@Index()
|
@Index()
|
||||||
@Column('varchar', {
|
@Column('varchar', {
|
||||||
length: 256, nullable: true,
|
length: 512, nullable: true,
|
||||||
comment: 'The URI of the User. It will be null if the origin of the user is local.'
|
comment: 'The URI of the User. It will be null if the origin of the user is local.'
|
||||||
})
|
})
|
||||||
public uri: string | null;
|
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 })
|
@Index({ unique: true })
|
||||||
@Column('char', {
|
@Column('char', {
|
||||||
length: 16, nullable: true, unique: true,
|
length: 16, nullable: true, unique: true,
|
||||||
@@ -261,31 +206,13 @@ export class User {
|
|||||||
})
|
})
|
||||||
public token: string | null;
|
public token: string | null;
|
||||||
|
|
||||||
@Column('jsonb', {
|
constructor(data: Partial<User>) {
|
||||||
default: {},
|
if (data == null) return;
|
||||||
comment: 'The client-specific data of the User.'
|
|
||||||
})
|
|
||||||
public clientData: Record<string, any>;
|
|
||||||
|
|
||||||
@Column('boolean', {
|
for (const [k, v] of Object.entries(data)) {
|
||||||
default: false,
|
(this as any)[k] = v;
|
||||||
})
|
}
|
||||||
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 {
|
export interface ILocalUser extends User {
|
||||||
|
@@ -25,7 +25,6 @@ import { FollowRequestRepository } from './repositories/follow-request';
|
|||||||
import { MutingRepository } from './repositories/muting';
|
import { MutingRepository } from './repositories/muting';
|
||||||
import { BlockingRepository } from './repositories/blocking';
|
import { BlockingRepository } from './repositories/blocking';
|
||||||
import { NoteReactionRepository } from './repositories/note-reaction';
|
import { NoteReactionRepository } from './repositories/note-reaction';
|
||||||
import { UserServiceLinking } from './entities/user-service-linking';
|
|
||||||
import { NotificationRepository } from './repositories/notification';
|
import { NotificationRepository } from './repositories/notification';
|
||||||
import { NoteFavoriteRepository } from './repositories/note-favorite';
|
import { NoteFavoriteRepository } from './repositories/note-favorite';
|
||||||
import { ReversiMatchingRepository } from './repositories/games/reversi/matching';
|
import { ReversiMatchingRepository } from './repositories/games/reversi/matching';
|
||||||
@@ -35,6 +34,7 @@ import { AppRepository } from './repositories/app';
|
|||||||
import { FollowingRepository } from './repositories/following';
|
import { FollowingRepository } from './repositories/following';
|
||||||
import { AbuseUserReportRepository } from './repositories/abuse-user-report';
|
import { AbuseUserReportRepository } from './repositories/abuse-user-report';
|
||||||
import { AuthSessionRepository } from './repositories/auth-session';
|
import { AuthSessionRepository } from './repositories/auth-session';
|
||||||
|
import { UserProfile } from './entities/user-profile';
|
||||||
|
|
||||||
export const Apps = getCustomRepository(AppRepository);
|
export const Apps = getCustomRepository(AppRepository);
|
||||||
export const Notes = getCustomRepository(NoteRepository);
|
export const Notes = getCustomRepository(NoteRepository);
|
||||||
@@ -45,12 +45,12 @@ export const NoteUnreads = getRepository(NoteUnread);
|
|||||||
export const Polls = getRepository(Poll);
|
export const Polls = getRepository(Poll);
|
||||||
export const PollVotes = getRepository(PollVote);
|
export const PollVotes = getRepository(PollVote);
|
||||||
export const Users = getCustomRepository(UserRepository);
|
export const Users = getCustomRepository(UserRepository);
|
||||||
|
export const UserProfiles = getRepository(UserProfile);
|
||||||
export const UserKeypairs = getRepository(UserKeypair);
|
export const UserKeypairs = getRepository(UserKeypair);
|
||||||
export const UserPublickeys = getRepository(UserPublickey);
|
export const UserPublickeys = getRepository(UserPublickey);
|
||||||
export const UserLists = getCustomRepository(UserListRepository);
|
export const UserLists = getCustomRepository(UserListRepository);
|
||||||
export const UserListJoinings = getRepository(UserListJoining);
|
export const UserListJoinings = getRepository(UserListJoining);
|
||||||
export const UserNotePinings = getRepository(UserNotePining);
|
export const UserNotePinings = getRepository(UserNotePining);
|
||||||
export const UserServiceLinkings = getRepository(UserServiceLinking);
|
|
||||||
export const Followings = getCustomRepository(FollowingRepository);
|
export const Followings = getCustomRepository(FollowingRepository);
|
||||||
export const FollowRequests = getCustomRepository(FollowRequestRepository);
|
export const FollowRequests = getCustomRepository(FollowRequestRepository);
|
||||||
export const Instances = getRepository(Instance);
|
export const Instances = getRepository(Instance);
|
||||||
|
@@ -3,6 +3,7 @@ import { DriveFile } from '../entities/drive-file';
|
|||||||
import { Users, DriveFolders } from '..';
|
import { Users, DriveFolders } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
import { User } from '../entities/user';
|
import { User } from '../entities/user';
|
||||||
|
import { toPuny } from '../../misc/convert-host';
|
||||||
|
|
||||||
@EntityRepository(DriveFile)
|
@EntityRepository(DriveFile)
|
||||||
export class DriveFileRepository extends Repository<DriveFile> {
|
export class DriveFileRepository extends Repository<DriveFile> {
|
||||||
@@ -39,7 +40,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
|
|||||||
public async clacDriveUsageOfHost(host: string): Promise<number> {
|
public async clacDriveUsageOfHost(host: string): Promise<number> {
|
||||||
const { sum } = await this
|
const { sum } = await this
|
||||||
.createQueryBuilder('file')
|
.createQueryBuilder('file')
|
||||||
.where('file.userHost = :host', { host: host })
|
.where('file.userHost = :host', { host: toPuny(host) })
|
||||||
.select('SUM(file.size)', 'sum')
|
.select('SUM(file.size)', 'sum')
|
||||||
.getRawOne();
|
.getRawOne();
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
import { EntityRepository, Repository, In } from 'typeorm';
|
import { EntityRepository, Repository, In } from 'typeorm';
|
||||||
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings } from '..';
|
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
|
||||||
import rap from '@prezzemolo/rap';
|
import rap from '@prezzemolo/rap';
|
||||||
|
|
||||||
@EntityRepository(User)
|
@EntityRepository(User)
|
||||||
@@ -80,13 +80,13 @@ export class UserRepository extends Repository<User> {
|
|||||||
|
|
||||||
const relation = meId && (meId !== user.id) && opts.detail ? await this.getRelation(meId, user.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 }) : [];
|
const pins = opts.detail ? await UserNotePinings.find({ userId: user.id }) : [];
|
||||||
|
const profile = opts.detail ? await UserProfiles.findOne({ userId: user.id }) : null;
|
||||||
|
|
||||||
return await rap({
|
return await rap({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
host: user.host,
|
host: user.host,
|
||||||
uri: user.uri,
|
|
||||||
avatarUrl: user.avatarUrl,
|
avatarUrl: user.avatarUrl,
|
||||||
bannerUrl: user.bannerUrl,
|
bannerUrl: user.bannerUrl,
|
||||||
avatarColor: user.avatarColor,
|
avatarColor: user.avatarColor,
|
||||||
@@ -94,6 +94,7 @@ export class UserRepository extends Repository<User> {
|
|||||||
isAdmin: user.isAdmin,
|
isAdmin: user.isAdmin,
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
isCat: user.isCat,
|
isCat: user.isCat,
|
||||||
|
isVerified: user.isVerified,
|
||||||
|
|
||||||
// カスタム絵文字添付
|
// カスタム絵文字添付
|
||||||
emojis: user.emojis.length > 0 ? Emojis.find({
|
emojis: user.emojis.length > 0 ? Emojis.find({
|
||||||
@@ -116,9 +117,12 @@ export class UserRepository extends Repository<User> {
|
|||||||
} : {}),
|
} : {}),
|
||||||
|
|
||||||
...(opts.detail ? {
|
...(opts.detail ? {
|
||||||
description: user.description,
|
url: profile.url,
|
||||||
location: user.location,
|
createdAt: user.createdAt,
|
||||||
birthday: user.birthday,
|
updatedAt: user.updatedAt,
|
||||||
|
description: profile.description,
|
||||||
|
location: profile.location,
|
||||||
|
birthday: profile.birthday,
|
||||||
followersCount: user.followersCount,
|
followersCount: user.followersCount,
|
||||||
followingCount: user.followingCount,
|
followingCount: user.followingCount,
|
||||||
notesCount: user.notesCount,
|
notesCount: user.notesCount,
|
||||||
@@ -131,9 +135,9 @@ export class UserRepository extends Repository<User> {
|
|||||||
...(opts.detail && meId === user.id ? {
|
...(opts.detail && meId === user.id ? {
|
||||||
avatarId: user.avatarId,
|
avatarId: user.avatarId,
|
||||||
bannerId: user.bannerId,
|
bannerId: user.bannerId,
|
||||||
autoWatch: user.autoWatch,
|
autoWatch: profile.autoWatch,
|
||||||
alwaysMarkNsfw: user.alwaysMarkNsfw,
|
alwaysMarkNsfw: profile.alwaysMarkNsfw,
|
||||||
carefulBot: user.carefulBot,
|
carefulBot: profile.carefulBot,
|
||||||
hasUnreadMessagingMessage: MessagingMessages.count({
|
hasUnreadMessagingMessage: MessagingMessages.count({
|
||||||
where: {
|
where: {
|
||||||
recipientId: user.id,
|
recipientId: user.id,
|
||||||
@@ -153,6 +157,12 @@ export class UserRepository extends Repository<User> {
|
|||||||
}),
|
}),
|
||||||
} : {}),
|
} : {}),
|
||||||
|
|
||||||
|
...(opts.includeSecrets ? {
|
||||||
|
clientData: profile.clientData,
|
||||||
|
email: profile.email,
|
||||||
|
emailVerified: profile.emailVerified,
|
||||||
|
} : {}),
|
||||||
|
|
||||||
...(relation ? {
|
...(relation ? {
|
||||||
isFollowing: relation.isFollowing,
|
isFollowing: relation.isFollowing,
|
||||||
isFollowed: relation.isFollowed,
|
isFollowed: relation.isFollowed,
|
||||||
|
@@ -4,7 +4,6 @@ import parseAcct from '../../misc/acct/parse';
|
|||||||
import { IRemoteUser } from '../../models/entities/user';
|
import { IRemoteUser } from '../../models/entities/user';
|
||||||
import perform from '../../remote/activitypub/perform';
|
import perform from '../../remote/activitypub/perform';
|
||||||
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
|
import { resolvePerson, updatePerson } from '../../remote/activitypub/models/person';
|
||||||
import { toUnicode } from 'punycode';
|
|
||||||
import { URL } from 'url';
|
import { URL } from 'url';
|
||||||
import { publishApLogStream } from '../../services/stream';
|
import { publishApLogStream } from '../../services/stream';
|
||||||
import Logger from '../../services/logger';
|
import Logger from '../../services/logger';
|
||||||
@@ -13,6 +12,8 @@ import { Instances, Users, UserPublickeys } from '../../models';
|
|||||||
import { instanceChart } from '../../services/chart';
|
import { instanceChart } from '../../services/chart';
|
||||||
import { UserPublickey } from '../../models/entities/user-publickey';
|
import { UserPublickey } from '../../models/entities/user-publickey';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
|
import { toPuny } from '../../misc/convert-host';
|
||||||
|
import { validActor } from '../../remote/activitypub/type';
|
||||||
|
|
||||||
const logger = new Logger('inbox');
|
const logger = new Logger('inbox');
|
||||||
|
|
||||||
@@ -33,7 +34,10 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
let key: UserPublickey;
|
let key: UserPublickey;
|
||||||
|
|
||||||
if (keyIdLower.startsWith('acct:')) {
|
if (keyIdLower.startsWith('acct:')) {
|
||||||
const { username, host } = parseAcct(keyIdLower.slice('acct:'.length));
|
const acct = parseAcct(keyIdLower.slice('acct:'.length));
|
||||||
|
const host = toPuny(acct.host);
|
||||||
|
const username = toPuny(acct.username);
|
||||||
|
|
||||||
if (host === null) {
|
if (host === null) {
|
||||||
logger.warn(`request was made by local user: @${username}`);
|
logger.warn(`request was made by local user: @${username}`);
|
||||||
return;
|
return;
|
||||||
@@ -50,19 +54,22 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.blockedHosts.includes(host.toLowerCase())) {
|
if (meta.blockedHosts.includes(host)) {
|
||||||
logger.info(`Blocked request: ${host}`);
|
logger.info(`Blocked request: ${host}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
user = await Users.findOne({ usernameLower: username, host: host.toLowerCase() }) as IRemoteUser;
|
user = await Users.findOne({
|
||||||
|
usernameLower: username.toLowerCase(),
|
||||||
|
host: host
|
||||||
|
}) as IRemoteUser;
|
||||||
|
|
||||||
key = await UserPublickeys.findOne({
|
key = await UserPublickeys.findOne({
|
||||||
userId: user.id
|
userId: user.id
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// アクティビティ内のホストの検証
|
// アクティビティ内のホストの検証
|
||||||
const host = toUnicode(new URL(signature.keyId).hostname.toLowerCase());
|
const host = toPuny(new URL(signature.keyId).hostname);
|
||||||
try {
|
try {
|
||||||
ValidateActivity(activity, host);
|
ValidateActivity(activity, host);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -73,7 +80,7 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.blockedHosts.includes(host.toLowerCase())) {
|
if (meta.blockedHosts.includes(host)) {
|
||||||
logger.info(`Blocked request: ${host}`);
|
logger.info(`Blocked request: ${host}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -87,7 +94,7 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
|
|
||||||
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
|
||||||
if (activity.type === 'Update') {
|
if (activity.type === 'Update') {
|
||||||
if (activity.object && activity.object.type === 'Person') {
|
if (activity.object && validActor.includes(activity.object.type)) {
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
logger.warn('Update activity received, but user not registed.');
|
logger.warn('Update activity received, but user not registed.');
|
||||||
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
} else if (!httpSignature.verifySignature(signature, key.keyPem)) {
|
||||||
@@ -145,7 +152,7 @@ export default async (job: Bull.Job): Promise<void> => {
|
|||||||
function ValidateActivity(activity: any, host: string) {
|
function ValidateActivity(activity: any, host: string) {
|
||||||
// id (if exists)
|
// id (if exists)
|
||||||
if (typeof activity.id === 'string') {
|
if (typeof activity.id === 'string') {
|
||||||
const uriHost = toUnicode(new URL(activity.id).hostname.toLowerCase());
|
const uriHost = toPuny(new URL(activity.id).hostname);
|
||||||
if (host !== uriHost) {
|
if (host !== uriHost) {
|
||||||
const diag = activity.signature ? '. Has LD-Signature. Forwarded?' : '';
|
const diag = activity.signature ? '. Has LD-Signature. Forwarded?' : '';
|
||||||
throw new Error(`activity.id(${activity.id}) has different host(${host})${diag}`);
|
throw new Error(`activity.id(${activity.id}) has different host(${host})${diag}`);
|
||||||
@@ -154,7 +161,7 @@ function ValidateActivity(activity: any, host: string) {
|
|||||||
|
|
||||||
// actor (if exists)
|
// actor (if exists)
|
||||||
if (typeof activity.actor === 'string') {
|
if (typeof activity.actor === 'string') {
|
||||||
const uriHost = toUnicode(new URL(activity.actor).hostname.toLowerCase());
|
const uriHost = toPuny(new URL(activity.actor).hostname);
|
||||||
if (host !== uriHost) throw new Error('activity.actor has different host');
|
if (host !== uriHost) throw new Error('activity.actor has different host');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,13 +169,13 @@ function ValidateActivity(activity: any, host: string) {
|
|||||||
if (activity.type === 'Create' && activity.object) {
|
if (activity.type === 'Create' && activity.object) {
|
||||||
// object.id (if exists)
|
// object.id (if exists)
|
||||||
if (typeof activity.object.id === 'string') {
|
if (typeof activity.object.id === 'string') {
|
||||||
const uriHost = toUnicode(new URL(activity.object.id).hostname.toLowerCase());
|
const uriHost = toPuny(new URL(activity.object.id).hostname);
|
||||||
if (host !== uriHost) throw new Error('activity.object.id has different host');
|
if (host !== uriHost) throw new Error('activity.object.id has different host');
|
||||||
}
|
}
|
||||||
|
|
||||||
// object.attributedTo (if exists)
|
// object.attributedTo (if exists)
|
||||||
if (typeof activity.object.attributedTo === 'string') {
|
if (typeof activity.object.attributedTo === 'string') {
|
||||||
const uriHost = toUnicode(new URL(activity.object.attributedTo).hostname.toLowerCase());
|
const uriHost = toPuny(new URL(activity.object.attributedTo).hostname);
|
||||||
if (host !== uriHost) throw new Error('activity.object.attributedTo has different host');
|
if (host !== uriHost) throw new Error('activity.object.attributedTo has different host');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,14 +8,13 @@ import { resolveImage } from './image';
|
|||||||
import { IRemoteUser, User } from '../../../models/entities/user';
|
import { IRemoteUser, User } from '../../../models/entities/user';
|
||||||
import { fromHtml } from '../../../mfm/fromHtml';
|
import { fromHtml } from '../../../mfm/fromHtml';
|
||||||
import { ITag, extractHashtags } from './tag';
|
import { ITag, extractHashtags } from './tag';
|
||||||
import { toUnicode } from 'punycode';
|
|
||||||
import { unique, concat, difference } from '../../../prelude/array';
|
import { unique, concat, difference } from '../../../prelude/array';
|
||||||
import { extractPollFromQuestion } from './question';
|
import { extractPollFromQuestion } from './question';
|
||||||
import vote from '../../../services/note/polls/vote';
|
import vote from '../../../services/note/polls/vote';
|
||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
import { DriveFile } from '../../../models/entities/drive-file';
|
import { DriveFile } from '../../../models/entities/drive-file';
|
||||||
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
|
||||||
import { extractDbHost } from '../../../misc/convert-host';
|
import { extractDbHost, toPuny } from '../../../misc/convert-host';
|
||||||
import { Notes, Emojis, Polls } from '../../../models';
|
import { Notes, Emojis, Polls } from '../../../models';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { IObject, INote } from '../type';
|
import { IObject, INote } from '../type';
|
||||||
@@ -246,8 +245,8 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
|
|||||||
return await createNote(uri, resolver);
|
return await createNote(uri, resolver);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function extractEmojis(tags: ITag[], host_: string) {
|
export async function extractEmojis(tags: ITag[], host: string) {
|
||||||
const host = toUnicode(host_.toLowerCase());
|
host = toPuny(host);
|
||||||
|
|
||||||
if (!tags) return [];
|
if (!tags) return [];
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import * as promiseLimit from 'promise-limit';
|
import * as promiseLimit from 'promise-limit';
|
||||||
import { toUnicode } from 'punycode';
|
|
||||||
|
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import Resolver from '../resolver';
|
import Resolver from '../resolver';
|
||||||
@@ -15,16 +14,18 @@ import { IIdentifier } from './identifier';
|
|||||||
import { apLogger } from '../logger';
|
import { apLogger } from '../logger';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { updateHashtag } from '../../../services/update-hashtag';
|
import { updateHashtag } from '../../../services/update-hashtag';
|
||||||
import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserServiceLinkings, UserPublickeys } from '../../../models';
|
import { Users, UserNotePinings, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '../../../models';
|
||||||
import { User, IRemoteUser } from '../../../models/entities/user';
|
import { User, IRemoteUser } from '../../../models/entities/user';
|
||||||
import { Emoji } from '../../../models/entities/emoji';
|
import { Emoji } from '../../../models/entities/emoji';
|
||||||
import { UserNotePining } from '../../../models/entities/user-note-pinings';
|
import { UserNotePining } from '../../../models/entities/user-note-pinings';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import { UserServiceLinking } from '../../../models/entities/user-service-linking';
|
|
||||||
import { instanceChart, usersChart } from '../../../services/chart';
|
import { instanceChart, usersChart } from '../../../services/chart';
|
||||||
import { UserPublickey } from '../../../models/entities/user-publickey';
|
import { UserPublickey } from '../../../models/entities/user-publickey';
|
||||||
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
|
import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-error';
|
||||||
import { toPuny } from '../../../misc/convert-host';
|
import { toPuny } from '../../../misc/convert-host';
|
||||||
|
import { UserProfile } from '../../../models/entities/user-profile';
|
||||||
|
import { validActor } from '../../../remote/activitypub/type';
|
||||||
|
import { getConnection } from 'typeorm';
|
||||||
const logger = apLogger;
|
const logger = apLogger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,13 +34,13 @@ const logger = apLogger;
|
|||||||
* @param uri Fetch target URI
|
* @param uri Fetch target URI
|
||||||
*/
|
*/
|
||||||
function validatePerson(x: any, uri: string) {
|
function validatePerson(x: any, uri: string) {
|
||||||
const expectHost = toUnicode(new URL(uri).hostname.toLowerCase());
|
const expectHost = toPuny(new URL(uri).hostname);
|
||||||
|
|
||||||
if (x == null) {
|
if (x == null) {
|
||||||
return new Error('invalid person: object is null');
|
return new Error('invalid person: object is null');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (x.type != 'Person' && x.type != 'Service') {
|
if (!validActor.includes(x.type)) {
|
||||||
return new Error(`invalid person: object is not a person or service '${x.type}'`);
|
return new Error(`invalid person: object is not a person or service '${x.type}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +64,7 @@ function validatePerson(x: any, uri: string) {
|
|||||||
return new Error('invalid person: id is not a string');
|
return new Error('invalid person: id is not a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
const idHost = toUnicode(new URL(x.id).hostname.toLowerCase());
|
const idHost = toPuny(new URL(x.id).hostname);
|
||||||
if (idHost !== expectHost) {
|
if (idHost !== expectHost) {
|
||||||
return new Error('invalid person: id has different host');
|
return new Error('invalid person: id has different host');
|
||||||
}
|
}
|
||||||
@@ -72,7 +73,7 @@ function validatePerson(x: any, uri: string) {
|
|||||||
return new Error('invalid person: publicKey.id is not a string');
|
return new Error('invalid person: publicKey.id is not a string');
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKeyIdHost = toUnicode(new URL(x.publicKey.id).hostname.toLowerCase());
|
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname);
|
||||||
if (publicKeyIdHost !== expectHost) {
|
if (publicKeyIdHost !== expectHost) {
|
||||||
return new Error('invalid person: publicKey.id has different host');
|
return new Error('invalid person: publicKey.id has different host');
|
||||||
}
|
}
|
||||||
@@ -127,7 +128,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
|
|
||||||
const host = toPuny(new URL(object.id).hostname);
|
const host = toPuny(new URL(object.id).hostname);
|
||||||
|
|
||||||
const { fields, services } = analyzeAttachments(person.attachment);
|
const { fields } = analyzeAttachments(person.attachment);
|
||||||
|
|
||||||
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase());
|
||||||
|
|
||||||
@@ -136,13 +137,14 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
// Create user
|
// Create user
|
||||||
let user: IRemoteUser;
|
let user: IRemoteUser;
|
||||||
try {
|
try {
|
||||||
user = await Users.save({
|
// Start transaction
|
||||||
|
await getConnection().transaction(async transactionalEntityManager => {
|
||||||
|
user = await transactionalEntityManager.save(new User({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
avatarId: null,
|
avatarId: null,
|
||||||
bannerId: null,
|
bannerId: null,
|
||||||
createdAt: Date.parse(person.published) || new Date(),
|
createdAt: new Date(),
|
||||||
lastFetchedAt: new Date(),
|
lastFetchedAt: new Date(),
|
||||||
description: fromHtml(person.summary),
|
|
||||||
name: person.name,
|
name: person.name,
|
||||||
isLocked: person.manuallyApprovesFollowers,
|
isLocked: person.manuallyApprovesFollowers,
|
||||||
username: person.preferredUsername,
|
username: person.preferredUsername,
|
||||||
@@ -151,15 +153,26 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
inbox: person.inbox,
|
inbox: person.inbox,
|
||||||
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
|
||||||
featured: person.featured,
|
featured: person.featured,
|
||||||
endpoints: person.endpoints,
|
|
||||||
uri: person.id,
|
uri: person.id,
|
||||||
url: person.url,
|
|
||||||
fields,
|
|
||||||
...services,
|
|
||||||
tags,
|
tags,
|
||||||
isBot,
|
isBot,
|
||||||
isCat: (person as any).isCat === true
|
isCat: (person as any).isCat === true
|
||||||
} as Partial<User>) as IRemoteUser;
|
})) as IRemoteUser;
|
||||||
|
|
||||||
|
await transactionalEntityManager.save(new UserProfile({
|
||||||
|
userId: user.id,
|
||||||
|
description: fromHtml(person.summary),
|
||||||
|
url: person.url,
|
||||||
|
fields,
|
||||||
|
userHost: host
|
||||||
|
}));
|
||||||
|
|
||||||
|
await transactionalEntityManager.save(new UserPublickey({
|
||||||
|
userId: user.id,
|
||||||
|
keyId: person.publicKey.id,
|
||||||
|
keyPem: person.publicKey.publicKeyPem
|
||||||
|
}));
|
||||||
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// duplicate key error
|
// duplicate key error
|
||||||
if (isDuplicateKeyValueError(e)) {
|
if (isDuplicateKeyValueError(e)) {
|
||||||
@@ -170,18 +183,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
await UserPublickeys.save({
|
|
||||||
id: genId(),
|
|
||||||
userId: user.id,
|
|
||||||
keyId: person.publicKey.id,
|
|
||||||
keyPem: person.publicKey.publicKeyPem
|
|
||||||
} as UserPublickey);
|
|
||||||
|
|
||||||
await UserServiceLinkings.save({
|
|
||||||
id: genId(),
|
|
||||||
userId: user.id
|
|
||||||
} as UserServiceLinking);
|
|
||||||
|
|
||||||
// Register host
|
// Register host
|
||||||
registerOrFetchInstanceDoc(host).then(i => {
|
registerOrFetchInstanceDoc(host).then(i => {
|
||||||
Instances.increment({ id: i.id }, 'usersCount', 1);
|
Instances.increment({ id: i.id }, 'usersCount', 1);
|
||||||
@@ -206,8 +207,8 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
|
|||||||
|
|
||||||
const avatarId = avatar ? avatar.id : null;
|
const avatarId = avatar ? avatar.id : null;
|
||||||
const bannerId = banner ? banner.id : null;
|
const bannerId = banner ? banner.id : null;
|
||||||
const avatarUrl = DriveFiles.getPublicUrl(avatar);
|
const avatarUrl = avatar ? DriveFiles.getPublicUrl(avatar) : null;
|
||||||
const bannerUrl = DriveFiles.getPublicUrl(banner);
|
const bannerUrl = banner ? DriveFiles.getPublicUrl(banner) : null;
|
||||||
const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null;
|
const avatarColor = avatar && avatar.properties.avgColor ? avatar.properties.avgColor : null;
|
||||||
const bannerColor = banner && avatar.properties.avgColor ? banner.properties.avgColor : null;
|
const bannerColor = banner && avatar.properties.avgColor ? banner.properties.avgColor : null;
|
||||||
|
|
||||||
@@ -348,7 +349,7 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
|
|||||||
keyPem: person.publicKey.publicKeyPem
|
keyPem: person.publicKey.publicKeyPem
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({ userId: exist.id }, {
|
await UserProfiles.update({ userId: exist.id }, {
|
||||||
twitterUserId: services.twitter.userId,
|
twitterUserId: services.twitter.userId,
|
||||||
twitterScreenName: services.twitter.screenName,
|
twitterScreenName: services.twitter.screenName,
|
||||||
githubId: services.github.id,
|
githubId: services.github.id,
|
||||||
|
@@ -68,11 +68,7 @@ export async function updateQuestion(value: any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Notes.update(note.id, {
|
await Polls.update({ noteId: note.id }, {
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
await Polls.update(poll.id, {
|
|
||||||
votes: poll.votes
|
votes: poll.votes
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -8,15 +8,15 @@ import { getEmojis } from './note';
|
|||||||
import renderEmoji from './emoji';
|
import renderEmoji from './emoji';
|
||||||
import { IIdentifier } from '../models/identifier';
|
import { IIdentifier } from '../models/identifier';
|
||||||
import renderHashtag from './hashtag';
|
import renderHashtag from './hashtag';
|
||||||
import { DriveFiles, UserServiceLinkings, UserKeypairs } from '../../../models';
|
import { DriveFiles, UserProfiles, UserKeypairs } from '../../../models';
|
||||||
|
|
||||||
export async function renderPerson(user: ILocalUser) {
|
export async function renderPerson(user: ILocalUser) {
|
||||||
const id = `${config.url}/users/${user.id}`;
|
const id = `${config.url}/users/${user.id}`;
|
||||||
|
|
||||||
const [avatar, banner, links] = await Promise.all([
|
const [avatar, banner, profile] = await Promise.all([
|
||||||
DriveFiles.findOne(user.avatarId),
|
DriveFiles.findOne(user.avatarId),
|
||||||
DriveFiles.findOne(user.bannerId),
|
DriveFiles.findOne(user.bannerId),
|
||||||
UserServiceLinkings.findOne({ userId: user.id })
|
UserProfiles.findOne({ userId: user.id })
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const attachment: {
|
const attachment: {
|
||||||
@@ -27,41 +27,41 @@ export async function renderPerson(user: ILocalUser) {
|
|||||||
identifier?: IIdentifier
|
identifier?: IIdentifier
|
||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
if (links.twitter) {
|
if (profile.twitter) {
|
||||||
attachment.push({
|
attachment.push({
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'Twitter',
|
name: 'Twitter',
|
||||||
value: `<a href="https://twitter.com/intent/user?user_id=${links.twitterUserId}" rel="me nofollow noopener" target="_blank"><span>@${links.twitterScreenName}</span></a>`,
|
value: `<a href="https://twitter.com/intent/user?user_id=${profile.twitterUserId}" rel="me nofollow noopener" target="_blank"><span>@${profile.twitterScreenName}</span></a>`,
|
||||||
identifier: {
|
identifier: {
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'misskey:authentication:twitter',
|
name: 'misskey:authentication:twitter',
|
||||||
value: `${links.twitterUserId}@${links.twitterScreenName}`
|
value: `${profile.twitterUserId}@${profile.twitterScreenName}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (links.github) {
|
if (profile.github) {
|
||||||
attachment.push({
|
attachment.push({
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'GitHub',
|
name: 'GitHub',
|
||||||
value: `<a href="https://github.com/${links.githubLogin}" rel="me nofollow noopener" target="_blank"><span>@${links.githubLogin}</span></a>`,
|
value: `<a href="https://github.com/${profile.githubLogin}" rel="me nofollow noopener" target="_blank"><span>@${profile.githubLogin}</span></a>`,
|
||||||
identifier: {
|
identifier: {
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'misskey:authentication:github',
|
name: 'misskey:authentication:github',
|
||||||
value: `${links.githubId}@${links.githubLogin}`
|
value: `${profile.githubId}@${profile.githubLogin}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (links.discord) {
|
if (profile.discord) {
|
||||||
attachment.push({
|
attachment.push({
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'Discord',
|
name: 'Discord',
|
||||||
value: `<a href="https://discordapp.com/users/${links.discordId}" rel="me nofollow noopener" target="_blank"><span>${links.discordUsername}#${links.discordDiscriminator}</span></a>`,
|
value: `<a href="https://discordapp.com/users/${profile.discordId}" rel="me nofollow noopener" target="_blank"><span>${profile.discordUsername}#${profile.discordDiscriminator}</span></a>`,
|
||||||
identifier: {
|
identifier: {
|
||||||
type: 'PropertyValue',
|
type: 'PropertyValue',
|
||||||
name: 'misskey:authentication:discord',
|
name: 'misskey:authentication:discord',
|
||||||
value: `${links.discordId}@${links.discordUsername}#${links.discordDiscriminator}`
|
value: `${profile.discordId}@${profile.discordUsername}#${profile.discordDiscriminator}`
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ export async function renderPerson(user: ILocalUser) {
|
|||||||
url: `${config.url}/@${user.username}`,
|
url: `${config.url}/@${user.username}`,
|
||||||
preferredUsername: user.username,
|
preferredUsername: user.username,
|
||||||
name: user.name,
|
name: user.name,
|
||||||
summary: toHtml(parse(user.description)),
|
summary: toHtml(parse(profile.description)),
|
||||||
icon: user.avatarId && renderImage(avatar),
|
icon: user.avatarId && renderImage(avatar),
|
||||||
image: user.bannerId && renderImage(banner),
|
image: user.bannerId && renderImage(banner),
|
||||||
tag,
|
tag,
|
||||||
|
@@ -4,7 +4,6 @@ import { URL } from 'url';
|
|||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import { lookup, IRunOptions } from 'lookup-dns-cache';
|
import { lookup, IRunOptions } from 'lookup-dns-cache';
|
||||||
import * as promiseAny from 'promise-any';
|
import * as promiseAny from 'promise-any';
|
||||||
import { toUnicode } from 'punycode';
|
|
||||||
|
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { ILocalUser } from '../../models/entities/user';
|
import { ILocalUser } from '../../models/entities/user';
|
||||||
@@ -12,6 +11,7 @@ import { publishApLogStream } from '../../services/stream';
|
|||||||
import { apLogger } from './logger';
|
import { apLogger } from './logger';
|
||||||
import { UserKeypairs } from '../../models';
|
import { UserKeypairs } from '../../models';
|
||||||
import fetchMeta from '../../misc/fetch-meta';
|
import fetchMeta from '../../misc/fetch-meta';
|
||||||
|
import { toPuny } from '../../misc/convert-host';
|
||||||
|
|
||||||
export const logger = apLogger.createSubLogger('deliver');
|
export const logger = apLogger.createSubLogger('deliver');
|
||||||
|
|
||||||
@@ -25,7 +25,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
|
|||||||
// ブロックしてたら中断
|
// ブロックしてたら中断
|
||||||
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
if (meta.blockedHosts.includes(toUnicode(host))) return;
|
if (meta.blockedHosts.includes(toPuny(host))) return;
|
||||||
|
|
||||||
const data = JSON.stringify(object);
|
const data = JSON.stringify(object);
|
||||||
|
|
||||||
|
@@ -65,6 +65,8 @@ interface IQuestionChoice {
|
|||||||
_misskey_votes?: number;
|
_misskey_votes?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const validActor = ['Person', 'Service'];
|
||||||
|
|
||||||
export interface IPerson extends IObject {
|
export interface IPerson extends IObject {
|
||||||
type: 'Person';
|
type: 'Person';
|
||||||
name: string;
|
name: string;
|
||||||
|
@@ -1,4 +1,3 @@
|
|||||||
import { toUnicode, toASCII } from 'punycode';
|
|
||||||
import webFinger from './webfinger';
|
import webFinger from './webfinger';
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import { createPerson, updatePerson } from './activitypub/models/person';
|
import { createPerson, updatePerson } from './activitypub/models/person';
|
||||||
@@ -7,31 +6,27 @@ import { remoteLogger } from './logger';
|
|||||||
import chalk from 'chalk';
|
import chalk from 'chalk';
|
||||||
import { User, IRemoteUser } from '../models/entities/user';
|
import { User, IRemoteUser } from '../models/entities/user';
|
||||||
import { Users } from '../models';
|
import { Users } from '../models';
|
||||||
|
import { toPuny } from '../misc/convert-host';
|
||||||
|
|
||||||
const logger = remoteLogger.createSubLogger('resolve-user');
|
const logger = remoteLogger.createSubLogger('resolve-user');
|
||||||
|
|
||||||
export async function resolveUser(username: string, _host: string, option?: any, resync = false): Promise<User> {
|
export async function resolveUser(username: string, host: string, option?: any, resync = false): Promise<User> {
|
||||||
const usernameLower = username.toLowerCase();
|
const usernameLower = username.toLowerCase();
|
||||||
|
host = toPuny(host);
|
||||||
|
|
||||||
if (_host == null) {
|
if (host == null) {
|
||||||
logger.info(`return local user: ${usernameLower}`);
|
logger.info(`return local user: ${usernameLower}`);
|
||||||
return await Users.findOne({ usernameLower, host: null });
|
return await Users.findOne({ usernameLower, host: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const configHostAscii = toASCII(config.host).toLowerCase();
|
if (config.host == host) {
|
||||||
const configHost = toUnicode(configHostAscii);
|
|
||||||
|
|
||||||
const hostAscii = toASCII(_host).toLowerCase();
|
|
||||||
const host = toUnicode(hostAscii);
|
|
||||||
|
|
||||||
if (configHost == host) {
|
|
||||||
logger.info(`return local user: ${usernameLower}`);
|
logger.info(`return local user: ${usernameLower}`);
|
||||||
return await Users.findOne({ usernameLower, host: null });
|
return await Users.findOne({ usernameLower, host: null });
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await Users.findOne({ usernameLower, host }, option);
|
const user = await Users.findOne({ usernameLower, host }, option);
|
||||||
|
|
||||||
const acctLower = `${usernameLower}@${hostAscii}`;
|
const acctLower = `${usernameLower}@${host}`;
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
const self = await resolveSelf(acctLower);
|
const self = await resolveSelf(acctLower);
|
||||||
@@ -51,7 +46,7 @@ export async function resolveUser(username: string, _host: string, option?: any,
|
|||||||
|
|
||||||
// validate uri
|
// validate uri
|
||||||
const uri = new URL(self.href);
|
const uri = new URL(self.href);
|
||||||
if (uri.hostname !== hostAscii) {
|
if (uri.hostname !== host) {
|
||||||
throw new Error(`Invalied uri`);
|
throw new Error(`Invalied uri`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +73,8 @@ export async function resolveUser(username: string, _host: string, option?: any,
|
|||||||
async function resolveSelf(acctLower: string) {
|
async function resolveSelf(acctLower: string) {
|
||||||
logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
|
||||||
const finger = await webFinger(acctLower).catch(e => {
|
const finger = await webFinger(acctLower).catch(e => {
|
||||||
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${e.message} (${e.status})`);
|
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`);
|
||||||
throw e;
|
throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`);
|
||||||
});
|
});
|
||||||
const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
|
const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
|
||||||
if (!self) {
|
if (!self) {
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import { WebFinger } from 'webfinger.js';
|
import config from '../config';
|
||||||
|
import * as request from 'request-promise-native';
|
||||||
const webFinger = new WebFinger({ });
|
import { URL } from 'url';
|
||||||
|
import { query as urlQuery } from '../prelude/url';
|
||||||
|
|
||||||
type ILink = {
|
type ILink = {
|
||||||
href: string;
|
href: string;
|
||||||
@@ -12,12 +13,33 @@ type IWebFinger = {
|
|||||||
subject: string;
|
subject: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function resolve(query: any): Promise<IWebFinger> {
|
export default async function(query: string): Promise<IWebFinger> {
|
||||||
return await new Promise((res, rej) => webFinger.lookup(query, (error: Error | string, result: any) => {
|
const url = genUrl(query);
|
||||||
if (error) {
|
|
||||||
return rej(error);
|
return await request({
|
||||||
|
url,
|
||||||
|
proxy: config.proxy,
|
||||||
|
timeout: 10 * 1000,
|
||||||
|
forever: true,
|
||||||
|
headers: {
|
||||||
|
'User-Agent': config.userAgent,
|
||||||
|
Accept: 'application/jrd+json, application/json'
|
||||||
|
},
|
||||||
|
json: true
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
res(result.object);
|
function genUrl(query: string) {
|
||||||
})) as IWebFinger;
|
if (query.match(/^https?:\/\//)) {
|
||||||
|
const u = new URL(query);
|
||||||
|
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
|
||||||
|
}
|
||||||
|
|
||||||
|
const m = query.match(/^([^@]+)@(.*)/);
|
||||||
|
if (m) {
|
||||||
|
const hostname = m[2];
|
||||||
|
return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Invalied query (${query})`);
|
||||||
}
|
}
|
||||||
|
@@ -80,7 +80,11 @@ export default async (endpoint: string, user: User, app: App, data: any, file?:
|
|||||||
apiLogger.error(`Internal error occurred in ${ep.name}`, {
|
apiLogger.error(`Internal error occurred in ${ep.name}`, {
|
||||||
ep: ep.name,
|
ep: ep.name,
|
||||||
ps: data,
|
ps: data,
|
||||||
e: e
|
e: {
|
||||||
|
message: e.message,
|
||||||
|
code: e.name,
|
||||||
|
stack: e.stack
|
||||||
|
}
|
||||||
});
|
});
|
||||||
throw new ApiError(null, {
|
throw new ApiError(null, {
|
||||||
e: {
|
e: {
|
||||||
|
@@ -1,6 +0,0 @@
|
|||||||
import { toUnicode } from 'punycode';
|
|
||||||
|
|
||||||
export default (host: string) => {
|
|
||||||
if (host == null) return null;
|
|
||||||
return toUnicode(host).toLowerCase();
|
|
||||||
};
|
|
@@ -1,6 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { Emojis } from '../../../../../models';
|
import { Emojis } from '../../../../../models';
|
||||||
|
import { toPuny } from '../../../../../misc/convert-host';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@@ -22,7 +23,7 @@ export const meta = {
|
|||||||
|
|
||||||
export default define(meta, async (ps) => {
|
export default define(meta, async (ps) => {
|
||||||
const emojis = await Emojis.find({
|
const emojis = await Emojis.find({
|
||||||
host: ps.host
|
host: toPuny(ps.host)
|
||||||
});
|
});
|
||||||
|
|
||||||
return emojis.map(e => ({
|
return emojis.map(e => ({
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { Instances } from '../../../../../models';
|
import { Instances } from '../../../../../models';
|
||||||
|
import { toPuny } from '../../../../../misc/convert-host';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['admin'],
|
tags: ['admin'],
|
||||||
@@ -20,13 +21,13 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, me) => {
|
export default define(meta, async (ps, me) => {
|
||||||
const instance = await Instances.findOne({ host: ps.host });
|
const instance = await Instances.findOne({ host: toPuny(ps.host) });
|
||||||
|
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
throw new Error('instance not found');
|
throw new Error('instance not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
Instances.update({ host: ps.host }, {
|
Instances.update({ host: toPuny(ps.host) }, {
|
||||||
isMarkedAsClosed: ps.isClosed
|
isMarkedAsClosed: ps.isClosed
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -3,7 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
|
|||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import rndstr from 'rndstr';
|
import rndstr from 'rndstr';
|
||||||
import { Users } from '../../../../models';
|
import { Users, UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@@ -42,7 +42,9 @@ export default define(meta, async (ps) => {
|
|||||||
// Generate hash of password
|
// Generate hash of password
|
||||||
const hash = bcrypt.hashSync(passwd);
|
const hash = bcrypt.hashSync(passwd);
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({
|
||||||
|
userId: user.id
|
||||||
|
}, {
|
||||||
password: hash
|
password: hash
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,6 +10,7 @@ import { Users, Notes } from '../../../../models';
|
|||||||
import { Note } from '../../../../models/entities/note';
|
import { Note } from '../../../../models/entities/note';
|
||||||
import { User } from '../../../../models/entities/user';
|
import { User } from '../../../../models/entities/user';
|
||||||
import fetchMeta from '../../../../misc/fetch-meta';
|
import fetchMeta from '../../../../misc/fetch-meta';
|
||||||
|
import { validActor } from '../../../../remote/activitypub/type';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
@@ -110,7 +111,7 @@ async function fetchAny(uri: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// それでもみつからなければ新規であるため登録
|
// それでもみつからなければ新規であるため登録
|
||||||
if (object.type === 'Person') {
|
if (validActor.includes(object.type)) {
|
||||||
const user = await createPerson(object.id);
|
const user = await createPerson(object.id);
|
||||||
return {
|
return {
|
||||||
type: 'User',
|
type: 'User',
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { Instances } from '../../../../models';
|
import { Instances } from '../../../../models';
|
||||||
|
import { toPuny } from '../../../../misc/convert-host';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
tags: ['federation'],
|
tags: ['federation'],
|
||||||
@@ -16,7 +17,7 @@ export const meta = {
|
|||||||
|
|
||||||
export default define(meta, async (ps, me) => {
|
export default define(meta, async (ps, me) => {
|
||||||
const instance = await Instances
|
const instance = await Instances
|
||||||
.findOne({ host: ps.host });
|
.findOne({ host: toPuny(ps.host) });
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import * as speakeasy from 'speakeasy';
|
import * as speakeasy from 'speakeasy';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { Users } from '../../../../../models';
|
import { UserProfiles } from '../../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -16,24 +16,26 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
const _token = ps.token.replace(/\s/g, '');
|
const token = ps.token.replace(/\s/g, '');
|
||||||
|
|
||||||
if (user.twoFactorTempSecret == null) {
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
|
if (profile.twoFactorTempSecret == null) {
|
||||||
throw new Error('二段階認証の設定が開始されていません');
|
throw new Error('二段階認証の設定が開始されていません');
|
||||||
}
|
}
|
||||||
|
|
||||||
const verified = (speakeasy as any).totp.verify({
|
const verified = (speakeasy as any).totp.verify({
|
||||||
secret: user.twoFactorTempSecret,
|
secret: profile.twoFactorTempSecret,
|
||||||
encoding: 'base32',
|
encoding: 'base32',
|
||||||
token: _token
|
token: token
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!verified) {
|
if (!verified) {
|
||||||
throw new Error('not verified');
|
throw new Error('not verified');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
twoFactorSecret: user.twoFactorTempSecret,
|
twoFactorSecret: profile.twoFactorTempSecret,
|
||||||
twoFactorEnabled: true
|
twoFactorEnabled: true
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy';
|
|||||||
import * as QRCode from 'qrcode';
|
import * as QRCode from 'qrcode';
|
||||||
import config from '../../../../../config';
|
import config from '../../../../../config';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { Users } from '../../../../../models';
|
import { UserProfiles } from '../../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -19,8 +19,10 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, user.password);
|
const same = await bcrypt.compare(ps.password, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
@@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => {
|
|||||||
length: 32
|
length: 32
|
||||||
});
|
});
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
twoFactorTempSecret: secret.base32
|
twoFactorTempSecret: secret.base32
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import define from '../../../define';
|
import define from '../../../define';
|
||||||
import { Users } from '../../../../../models';
|
import { UserProfiles } from '../../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -16,17 +16,17 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, user.password);
|
const same = await bcrypt.compare(ps.password, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
twoFactorSecret: null,
|
twoFactorSecret: null,
|
||||||
twoFactorEnabled: false
|
twoFactorEnabled: false
|
||||||
});
|
});
|
||||||
|
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { Users } from '../../../../models';
|
import { UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -20,8 +20,10 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.currentPassword, user.password);
|
const same = await bcrypt.compare(ps.currentPassword, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
@@ -31,7 +33,7 @@ export default define(meta, async (ps, user) => {
|
|||||||
const salt = await bcrypt.genSalt(8);
|
const salt = await bcrypt.genSalt(8);
|
||||||
const hash = await bcrypt.hash(ps.newPassword, salt);
|
const hash = await bcrypt.hash(ps.newPassword, salt);
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
password: hash
|
password: hash
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { Users } from '../../../../models';
|
import { Users, UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -16,8 +16,10 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, user.password);
|
const same = await bcrypt.compare(ps.password, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
|
@@ -3,7 +3,7 @@ import * as bcrypt from 'bcryptjs';
|
|||||||
import { publishMainStream } from '../../../../services/stream';
|
import { publishMainStream } from '../../../../services/stream';
|
||||||
import generateUserToken from '../../common/generate-native-user-token';
|
import generateUserToken from '../../common/generate-native-user-token';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { Users } from '../../../../models';
|
import { Users, UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -18,8 +18,10 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, user.password);
|
const same = await bcrypt.compare(ps.password, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import $ from 'cafy';
|
import $ from 'cafy';
|
||||||
import { publishMainStream } from '../../../../services/stream';
|
import { publishMainStream } from '../../../../services/stream';
|
||||||
import define from '../../define';
|
import define from '../../define';
|
||||||
import { Users } from '../../../../models';
|
import { UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -20,13 +20,13 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
await Users.createQueryBuilder().update()
|
await UserProfiles.createQueryBuilder().update()
|
||||||
.set({
|
.set({
|
||||||
clientData: {
|
clientData: {
|
||||||
[ps.name]: ps.value
|
[ps.name]: ps.value
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.where('id = :id', { id: user.id })
|
.where('userId = :id', { id: user.id })
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
// Publish event
|
// Publish event
|
||||||
|
@@ -8,7 +8,7 @@ import config from '../../../../config';
|
|||||||
import * as ms from 'ms';
|
import * as ms from 'ms';
|
||||||
import * as bcrypt from 'bcryptjs';
|
import * as bcrypt from 'bcryptjs';
|
||||||
import { apiLogger } from '../../logger';
|
import { apiLogger } from '../../logger';
|
||||||
import { Users } from '../../../../models';
|
import { Users, UserProfiles } from '../../../../models';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true,
|
requireCredential: true,
|
||||||
@@ -32,14 +32,16 @@ export const meta = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default define(meta, async (ps, user) => {
|
export default define(meta, async (ps, user) => {
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(ps.password, user.password);
|
const same = await bcrypt.compare(ps.password, profile.password);
|
||||||
|
|
||||||
if (!same) {
|
if (!same) {
|
||||||
throw new Error('incorrect password');
|
throw new Error('incorrect password');
|
||||||
}
|
}
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
email: ps.email,
|
email: ps.email,
|
||||||
emailVerified: false,
|
emailVerified: false,
|
||||||
emailVerifyCode: null
|
emailVerifyCode: null
|
||||||
@@ -56,7 +58,7 @@ export default define(meta, async (ps, user) => {
|
|||||||
if (ps.email != null) {
|
if (ps.email != null) {
|
||||||
const code = rndstr('a-z0-9', 16);
|
const code = rndstr('a-z0-9', 16);
|
||||||
|
|
||||||
await Users.update(user.id, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
emailVerifyCode: code
|
emailVerifyCode: code
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -10,7 +10,9 @@ import extractHashtags from '../../../../misc/extract-hashtags';
|
|||||||
import * as langmap from 'langmap';
|
import * as langmap from 'langmap';
|
||||||
import { updateHashtag } from '../../../../services/update-hashtag';
|
import { updateHashtag } from '../../../../services/update-hashtag';
|
||||||
import { ApiError } from '../../error';
|
import { ApiError } from '../../error';
|
||||||
import { Users, DriveFiles } from '../../../../models';
|
import { Users, DriveFiles, UserProfiles } from '../../../../models';
|
||||||
|
import { User } from '../../../../models/entities/user';
|
||||||
|
import { UserProfile } from '../../../../models/entities/user-profile';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@@ -154,22 +156,23 @@ export const meta = {
|
|||||||
export default define(meta, async (ps, user, app) => {
|
export default define(meta, async (ps, user, app) => {
|
||||||
const isSecure = user != null && app == null;
|
const isSecure = user != null && app == null;
|
||||||
|
|
||||||
const updates = {} as any;
|
const updates = {} as Partial<User>;
|
||||||
|
const profile = {} as Partial<UserProfile>;
|
||||||
|
|
||||||
if (ps.name !== undefined) updates.name = ps.name;
|
if (ps.name !== undefined) updates.name = ps.name;
|
||||||
if (ps.description !== undefined) updates.description = ps.description;
|
if (ps.description !== undefined) profile.description = ps.description;
|
||||||
if (ps.lang !== undefined) updates.lang = ps.lang;
|
//if (ps.lang !== undefined) updates.lang = ps.lang;
|
||||||
if (ps.location !== undefined) updates.location = ps.location;
|
if (ps.location !== undefined) profile.location = ps.location;
|
||||||
if (ps.birthday !== undefined) updates.birthday = ps.birthday;
|
if (ps.birthday !== undefined) profile.birthday = ps.birthday;
|
||||||
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
|
||||||
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
|
||||||
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
|
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
|
||||||
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
|
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
|
||||||
if (typeof ps.carefulBot == 'boolean') updates.carefulBot = ps.carefulBot;
|
if (typeof ps.carefulBot == 'boolean') profile.carefulBot = ps.carefulBot;
|
||||||
if (typeof ps.autoAcceptFollowed == 'boolean') updates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
if (typeof ps.autoAcceptFollowed == 'boolean') profile.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||||
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
|
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
|
||||||
if (typeof ps.autoWatch == 'boolean') updates.autoWatch = ps.autoWatch;
|
if (typeof ps.autoWatch == 'boolean') profile.autoWatch = ps.autoWatch;
|
||||||
if (typeof ps.alwaysMarkNsfw == 'boolean') updates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
if (typeof ps.alwaysMarkNsfw == 'boolean') profile.alwaysMarkNsfw = ps.alwaysMarkNsfw;
|
||||||
|
|
||||||
if (ps.avatarId) {
|
if (ps.avatarId) {
|
||||||
const avatar = await DriveFiles.findOne(ps.avatarId);
|
const avatar = await DriveFiles.findOne(ps.avatarId);
|
||||||
@@ -206,8 +209,8 @@ export default define(meta, async (ps, user, app) => {
|
|||||||
emojis = emojis.concat(extractEmojis(tokens));
|
emojis = emojis.concat(extractEmojis(tokens));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (updates.description != null) {
|
if (profile.description != null) {
|
||||||
const tokens = parse(updates.description);
|
const tokens = parse(profile.description);
|
||||||
emojis = emojis.concat(extractEmojis(tokens));
|
emojis = emojis.concat(extractEmojis(tokens));
|
||||||
tags = extractHashtags(tokens).map(tag => tag.toLowerCase());
|
tags = extractHashtags(tokens).map(tag => tag.toLowerCase());
|
||||||
}
|
}
|
||||||
@@ -220,7 +223,8 @@ export default define(meta, async (ps, user, app) => {
|
|||||||
for (const tag of user.tags.filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false);
|
for (const tag of user.tags.filter(x => !tags.includes(x))) updateHashtag(user, tag, true, false);
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
await Users.update(user.id, updates);
|
if (Object.keys(updates).length > 0) await Users.update(user.id, updates);
|
||||||
|
if (Object.keys(profile).length > 0) await UserProfiles.update({ userId: user.id }, profile);
|
||||||
|
|
||||||
const iObj = await Users.pack(user.id, user, {
|
const iObj = await Users.pack(user.id, user, {
|
||||||
detail: true,
|
detail: true,
|
||||||
|
@@ -10,7 +10,7 @@ import { deliver } from '../../../../../queue';
|
|||||||
import { renderActivity } from '../../../../../remote/activitypub/renderer';
|
import { renderActivity } from '../../../../../remote/activitypub/renderer';
|
||||||
import renderVote from '../../../../../remote/activitypub/renderer/vote';
|
import renderVote from '../../../../../remote/activitypub/renderer/vote';
|
||||||
import { deliverQuestionUpdate } from '../../../../../services/note/polls/update';
|
import { deliverQuestionUpdate } from '../../../../../services/note/polls/update';
|
||||||
import { PollVotes, NoteWatchings, Users, Polls } from '../../../../../models';
|
import { PollVotes, NoteWatchings, Users, Polls, UserProfiles } from '../../../../../models';
|
||||||
import { Not } from 'typeorm';
|
import { Not } from 'typeorm';
|
||||||
import { IRemoteUser } from '../../../../../models/entities/user';
|
import { IRemoteUser } from '../../../../../models/entities/user';
|
||||||
import { genId } from '../../../../../misc/gen-id';
|
import { genId } from '../../../../../misc/gen-id';
|
||||||
@@ -123,7 +123,7 @@ export default define(meta, async (ps, user) => {
|
|||||||
|
|
||||||
// Increment votes count
|
// Increment votes count
|
||||||
const index = ps.choice + 1; // In SQL, array index is 1 based
|
const index = ps.choice + 1; // In SQL, array index is 1 based
|
||||||
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`);
|
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`);
|
||||||
|
|
||||||
publishNoteStream(note.id, 'pollVoted', {
|
publishNoteStream(note.id, 'pollVoted', {
|
||||||
choice: ps.choice,
|
choice: ps.choice,
|
||||||
@@ -149,8 +149,10 @@ export default define(meta, async (ps, user) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// この投稿をWatchする
|
// この投稿をWatchする
|
||||||
if (user.autoWatch !== false) {
|
if (profile.autoWatch !== false) {
|
||||||
watch(user.id, note);
|
watch(user.id, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,6 +4,7 @@ import define from '../../define';
|
|||||||
import { ApiError } from '../../error';
|
import { ApiError } from '../../error';
|
||||||
import { Users, Followings } from '../../../../models';
|
import { Users, Followings } from '../../../../models';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||||
|
import { toPuny } from '../../../../misc/convert-host';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@@ -65,7 +66,7 @@ export const meta = {
|
|||||||
export default define(meta, async (ps, me) => {
|
export default define(meta, async (ps, me) => {
|
||||||
const user = await Users.findOne(ps.userId != null
|
const user = await Users.findOne(ps.userId != null
|
||||||
? { id: ps.userId }
|
? { id: ps.userId }
|
||||||
: { usernameLower: ps.username.toLowerCase(), host: ps.host });
|
: { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) });
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new ApiError(meta.errors.noSuchUser);
|
throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
@@ -4,6 +4,7 @@ import define from '../../define';
|
|||||||
import { ApiError } from '../../error';
|
import { ApiError } from '../../error';
|
||||||
import { Users, Followings } from '../../../../models';
|
import { Users, Followings } from '../../../../models';
|
||||||
import { makePaginationQuery } from '../../common/make-pagination-query';
|
import { makePaginationQuery } from '../../common/make-pagination-query';
|
||||||
|
import { toPuny } from '../../../../misc/convert-host';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
desc: {
|
desc: {
|
||||||
@@ -65,7 +66,7 @@ export const meta = {
|
|||||||
export default define(meta, async (ps, me) => {
|
export default define(meta, async (ps, me) => {
|
||||||
const user = await Users.findOne(ps.userId != null
|
const user = await Users.findOne(ps.userId != null
|
||||||
? { id: ps.userId }
|
? { id: ps.userId }
|
||||||
: { usernameLower: ps.username.toLowerCase(), host: ps.host });
|
: { usernameLower: ps.username.toLowerCase(), host: toPuny(ps.host) });
|
||||||
|
|
||||||
if (user == null) {
|
if (user == null) {
|
||||||
throw new ApiError(meta.errors.noSuchUser);
|
throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
@@ -15,7 +15,6 @@ import signin from './private/signin';
|
|||||||
import discord from './service/discord';
|
import discord from './service/discord';
|
||||||
import github from './service/github';
|
import github from './service/github';
|
||||||
import twitter from './service/twitter';
|
import twitter from './service/twitter';
|
||||||
import { toASCII } from 'punycode';
|
|
||||||
import { Instances } from '../../models';
|
import { Instances } from '../../models';
|
||||||
|
|
||||||
// Init app
|
// Init app
|
||||||
@@ -71,9 +70,7 @@ router.get('/v1/instance/peers', async ctx => {
|
|||||||
select: ['host']
|
select: ['host']
|
||||||
});
|
});
|
||||||
|
|
||||||
const punyCodes = instances.map(instance => toASCII(instance.host));
|
ctx.body = instances.map(instance => instance.host);
|
||||||
|
|
||||||
ctx.body = punyCodes;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Return 404 for unknown API
|
// Return 404 for unknown API
|
||||||
|
@@ -4,7 +4,7 @@ import * as speakeasy from 'speakeasy';
|
|||||||
import { publishMainStream } from '../../../services/stream';
|
import { publishMainStream } from '../../../services/stream';
|
||||||
import signin from '../common/signin';
|
import signin from '../common/signin';
|
||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import { Users, Signins } from '../../../models';
|
import { Users, Signins, UserProfiles } from '../../../models';
|
||||||
import { ILocalUser } from '../../../models/entities/user';
|
import { ILocalUser } from '../../../models/entities/user';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
|
|
||||||
@@ -45,13 +45,15 @@ export default async (ctx: Koa.BaseContext) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// Compare password
|
// Compare password
|
||||||
const same = await bcrypt.compare(password, user.password);
|
const same = await bcrypt.compare(password, profile.password);
|
||||||
|
|
||||||
if (same) {
|
if (same) {
|
||||||
if (user.twoFactorEnabled) {
|
if (profile.twoFactorEnabled) {
|
||||||
const verified = (speakeasy as any).totp.verify({
|
const verified = (speakeasy as any).totp.verify({
|
||||||
secret: user.twoFactorSecret,
|
secret: profile.twoFactorSecret,
|
||||||
encoding: 'base32',
|
encoding: 'base32',
|
||||||
token: token
|
token: token
|
||||||
});
|
});
|
||||||
|
@@ -5,13 +5,14 @@ import generateUserToken from '../common/generate-native-user-token';
|
|||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
import * as recaptcha from 'recaptcha-promise';
|
import * as recaptcha from 'recaptcha-promise';
|
||||||
import { Users, RegistrationTickets, UserServiceLinkings, UserKeypairs } from '../../../models';
|
import { Users, RegistrationTickets } from '../../../models';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import { usersChart } from '../../../services/chart';
|
import { usersChart } from '../../../services/chart';
|
||||||
import { UserServiceLinking } from '../../../models/entities/user-service-linking';
|
|
||||||
import { User } from '../../../models/entities/user';
|
import { User } from '../../../models/entities/user';
|
||||||
import { UserKeypair } from '../../../models/entities/user-keypair';
|
import { UserKeypair } from '../../../models/entities/user-keypair';
|
||||||
import { toPuny } from '../../../misc/convert-host';
|
import { toPuny } from '../../../misc/convert-host';
|
||||||
|
import { UserProfile } from '../../../models/entities/user-profile';
|
||||||
|
import { getConnection } from 'typeorm';
|
||||||
|
|
||||||
export default async (ctx: Koa.BaseContext) => {
|
export default async (ctx: Koa.BaseContext) => {
|
||||||
const body = ctx.request.body as any;
|
const body = ctx.request.body as any;
|
||||||
@@ -99,30 +100,33 @@ export default async (ctx: Koa.BaseContext) => {
|
|||||||
e ? j(e) : s([publicKey, privateKey])
|
e ? j(e) : s([publicKey, privateKey])
|
||||||
));
|
));
|
||||||
|
|
||||||
const account = await Users.save({
|
let account: User;
|
||||||
|
|
||||||
|
// Start transaction
|
||||||
|
await getConnection().transaction(async transactionalEntityManager => {
|
||||||
|
account = await transactionalEntityManager.save(new User({
|
||||||
id: genId(),
|
id: genId(),
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
username: username,
|
username: username,
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: toPuny(host),
|
host: toPuny(host),
|
||||||
token: secret,
|
token: secret,
|
||||||
password: hash,
|
|
||||||
isAdmin: config.autoAdmin && usersCount === 0,
|
isAdmin: config.autoAdmin && usersCount === 0,
|
||||||
autoAcceptFollowed: true,
|
}));
|
||||||
autoWatch: false
|
|
||||||
} as User);
|
|
||||||
|
|
||||||
await UserKeypairs.save({
|
await transactionalEntityManager.save(new UserKeypair({
|
||||||
id: genId(),
|
|
||||||
publicKey: keyPair[0],
|
publicKey: keyPair[0],
|
||||||
privateKey: keyPair[1],
|
privateKey: keyPair[1],
|
||||||
userId: account.id
|
userId: account.id
|
||||||
} as UserKeypair);
|
}));
|
||||||
|
|
||||||
await UserServiceLinkings.save({
|
await transactionalEntityManager.save(new UserProfile({
|
||||||
id: genId(),
|
userId: account.id,
|
||||||
userId: account.id
|
autoAcceptFollowed: true,
|
||||||
} as UserServiceLinking);
|
autoWatch: false,
|
||||||
|
password: hash,
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
usersChart.update(account, true);
|
usersChart.update(account, true);
|
||||||
|
|
||||||
|
@@ -8,7 +8,7 @@ import redis from '../../../db/redis';
|
|||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import signin from '../common/signin';
|
import signin from '../common/signin';
|
||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
import { Users, UserServiceLinkings } from '../../../models';
|
import { Users, UserProfiles } from '../../../models';
|
||||||
import { ILocalUser } from '../../../models/entities/user';
|
import { ILocalUser } from '../../../models/entities/user';
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext) {
|
function getUserToken(ctx: Koa.BaseContext) {
|
||||||
@@ -45,7 +45,7 @@ router.get('/disconnect/discord', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({
|
await UserProfiles.update({
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}, {
|
}, {
|
||||||
discord: false,
|
discord: false,
|
||||||
@@ -202,7 +202,7 @@ router.get('/dc/cb', async ctx => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = await UserServiceLinkings.createQueryBuilder()
|
const profile = await UserProfiles.createQueryBuilder()
|
||||||
.where('discord @> :discord', {
|
.where('discord @> :discord', {
|
||||||
discord: {
|
discord: {
|
||||||
id: id,
|
id: id,
|
||||||
@@ -211,12 +211,12 @@ router.get('/dc/cb', async ctx => {
|
|||||||
.andWhere('userHost IS NULL')
|
.andWhere('userHost IS NULL')
|
||||||
.getOne();
|
.getOne();
|
||||||
|
|
||||||
if (link == null) {
|
if (profile == null) {
|
||||||
ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
|
ctx.throw(404, `@${username}#${discriminator}と連携しているMisskeyアカウントはありませんでした...`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await UserServiceLinkings.update(link.id, {
|
await UserProfiles.update({ userId: profile.userId }, {
|
||||||
discord: true,
|
discord: true,
|
||||||
discordAccessToken: accessToken,
|
discordAccessToken: accessToken,
|
||||||
discordRefreshToken: refreshToken,
|
discordRefreshToken: refreshToken,
|
||||||
@@ -225,7 +225,7 @@ router.get('/dc/cb', async ctx => {
|
|||||||
discordDiscriminator: discriminator
|
discordDiscriminator: discriminator
|
||||||
});
|
});
|
||||||
|
|
||||||
signin(ctx, await Users.findOne(link.userId) as ILocalUser, true);
|
signin(ctx, await Users.findOne(profile.userId) as ILocalUser, true);
|
||||||
} else {
|
} else {
|
||||||
const code = ctx.query.code;
|
const code = ctx.query.code;
|
||||||
|
|
||||||
@@ -289,7 +289,7 @@ router.get('/dc/cb', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({ userId: user.id }, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
discord: true,
|
discord: true,
|
||||||
discordAccessToken: accessToken,
|
discordAccessToken: accessToken,
|
||||||
discordRefreshToken: refreshToken,
|
discordRefreshToken: refreshToken,
|
||||||
|
@@ -8,7 +8,7 @@ import redis from '../../../db/redis';
|
|||||||
import * as uuid from 'uuid';
|
import * as uuid from 'uuid';
|
||||||
import signin from '../common/signin';
|
import signin from '../common/signin';
|
||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
import { Users, UserServiceLinkings } from '../../../models';
|
import { Users, UserProfiles } from '../../../models';
|
||||||
import { ILocalUser } from '../../../models/entities/user';
|
import { ILocalUser } from '../../../models/entities/user';
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext) {
|
function getUserToken(ctx: Koa.BaseContext) {
|
||||||
@@ -45,7 +45,7 @@ router.get('/disconnect/github', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({
|
await UserProfiles.update({
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}, {
|
}, {
|
||||||
github: false,
|
github: false,
|
||||||
@@ -191,7 +191,7 @@ router.get('/gh/cb', async ctx => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const link = await UserServiceLinkings.createQueryBuilder()
|
const link = await UserProfiles.createQueryBuilder()
|
||||||
.where('github @> :github', {
|
.where('github @> :github', {
|
||||||
github: {
|
github: {
|
||||||
id: id,
|
id: id,
|
||||||
@@ -263,7 +263,7 @@ router.get('/gh/cb', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({ userId: user.id }, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
github: true,
|
github: true,
|
||||||
githubAccessToken: accessToken,
|
githubAccessToken: accessToken,
|
||||||
githubId: id,
|
githubId: id,
|
||||||
|
@@ -7,7 +7,7 @@ import { publishMainStream } from '../../../services/stream';
|
|||||||
import config from '../../../config';
|
import config from '../../../config';
|
||||||
import signin from '../common/signin';
|
import signin from '../common/signin';
|
||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
import { Users, UserServiceLinkings } from '../../../models';
|
import { Users, UserProfiles } from '../../../models';
|
||||||
import { ILocalUser } from '../../../models/entities/user';
|
import { ILocalUser } from '../../../models/entities/user';
|
||||||
|
|
||||||
function getUserToken(ctx: Koa.BaseContext) {
|
function getUserToken(ctx: Koa.BaseContext) {
|
||||||
@@ -44,7 +44,7 @@ router.get('/disconnect/twitter', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({
|
await UserProfiles.update({
|
||||||
userId: user.id
|
userId: user.id
|
||||||
}, {
|
}, {
|
||||||
twitter: false,
|
twitter: false,
|
||||||
@@ -139,7 +139,7 @@ router.get('/tw/cb', async ctx => {
|
|||||||
|
|
||||||
const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
|
const result = await twAuth.done(JSON.parse(twCtx), ctx.query.oauth_verifier);
|
||||||
|
|
||||||
const link = await UserServiceLinkings.createQueryBuilder()
|
const link = await UserProfiles.createQueryBuilder()
|
||||||
.where('twitter @> :twitter', {
|
.where('twitter @> :twitter', {
|
||||||
twitter: {
|
twitter: {
|
||||||
userId: result.userId,
|
userId: result.userId,
|
||||||
@@ -177,7 +177,7 @@ router.get('/tw/cb', async ctx => {
|
|||||||
token: userToken
|
token: userToken
|
||||||
});
|
});
|
||||||
|
|
||||||
await UserServiceLinkings.update({ userId: user.id }, {
|
await UserProfiles.update({ userId: user.id }, {
|
||||||
twitter: true,
|
twitter: true,
|
||||||
twitterAccessToken: result.accessToken,
|
twitterAccessToken: result.accessToken,
|
||||||
twitterAccessTokenSecret: result.accessTokenSecret,
|
twitterAccessTokenSecret: result.accessTokenSecret,
|
||||||
|
@@ -23,7 +23,7 @@ import apiServer from './api';
|
|||||||
import { sum } from '../prelude/array';
|
import { sum } from '../prelude/array';
|
||||||
import Logger from '../services/logger';
|
import Logger from '../services/logger';
|
||||||
import { program } from '../argv';
|
import { program } from '../argv';
|
||||||
import { Users } from '../models';
|
import { UserProfiles } from '../models';
|
||||||
import { networkChart } from '../services/chart';
|
import { networkChart } from '../services/chart';
|
||||||
|
|
||||||
export const serverLogger = new Logger('server', 'gray', false);
|
export const serverLogger = new Logger('server', 'gray', false);
|
||||||
@@ -73,15 +73,15 @@ router.use(nodeinfo.routes());
|
|||||||
router.use(wellKnown.routes());
|
router.use(wellKnown.routes());
|
||||||
|
|
||||||
router.get('/verify-email/:code', async ctx => {
|
router.get('/verify-email/:code', async ctx => {
|
||||||
const user = await Users.findOne({
|
const profile = await UserProfiles.findOne({
|
||||||
emailVerifyCode: ctx.params.code
|
emailVerifyCode: ctx.params.code
|
||||||
});
|
});
|
||||||
|
|
||||||
if (user != null) {
|
if (profile != null) {
|
||||||
ctx.body = 'Verify succeeded!';
|
ctx.body = 'Verify succeeded!';
|
||||||
ctx.status = 200;
|
ctx.status = 200;
|
||||||
|
|
||||||
Users.update(user.id, {
|
UserProfiles.update({ userId: profile.userId }, {
|
||||||
emailVerified: true,
|
emailVerified: true,
|
||||||
emailVerifyCode: null
|
emailVerifyCode: null
|
||||||
});
|
});
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
import { Feed } from 'feed';
|
import { Feed } from 'feed';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { User } from '../../models/entities/user';
|
import { User } from '../../models/entities/user';
|
||||||
import { Notes, DriveFiles } from '../../models';
|
import { Notes, DriveFiles, UserProfiles } from '../../models';
|
||||||
import { In } from 'typeorm';
|
import { In } from 'typeorm';
|
||||||
|
|
||||||
export default async function(user: User) {
|
export default async function(user: User) {
|
||||||
@@ -10,6 +10,8 @@ export default async function(user: User) {
|
|||||||
name: user.name || user.username
|
name: user.name || user.username
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
const notes = await Notes.find({
|
const notes = await Notes.find({
|
||||||
where: {
|
where: {
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
@@ -25,7 +27,7 @@ export default async function(user: User) {
|
|||||||
title: `${author.name} (@${user.username}@${config.host})`,
|
title: `${author.name} (@${user.username}@${config.host})`,
|
||||||
updated: notes[0].createdAt,
|
updated: notes[0].createdAt,
|
||||||
generator: 'Misskey',
|
generator: 'Misskey',
|
||||||
description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${user.description ? ` · ${user.description}` : ''}`,
|
description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${profile.description ? ` · ${profile.description}` : ''}`,
|
||||||
link: author.link,
|
link: author.link,
|
||||||
image: user.avatarUrl,
|
image: user.avatarUrl,
|
||||||
feedLinks: {
|
feedLinks: {
|
||||||
|
@@ -16,7 +16,7 @@ import fetchMeta from '../../misc/fetch-meta';
|
|||||||
import * as pkg from '../../../package.json';
|
import * as pkg from '../../../package.json';
|
||||||
import { genOpenapiSpec } from '../api/openapi/gen-spec';
|
import { genOpenapiSpec } from '../api/openapi/gen-spec';
|
||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { Users, Notes, Emojis } from '../../models';
|
import { Users, Notes, Emojis, UserProfiles } from '../../models';
|
||||||
import parseAcct from '../../misc/acct/parse';
|
import parseAcct from '../../misc/acct/parse';
|
||||||
import getNoteSummary from '../../misc/get-note-summary';
|
import getNoteSummary from '../../misc/get-note-summary';
|
||||||
|
|
||||||
@@ -149,11 +149,14 @@ router.get('/@:user', async (ctx, next) => {
|
|||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host
|
host
|
||||||
});
|
});
|
||||||
|
const profile = await UserProfiles.findOne({
|
||||||
|
userId: user.id
|
||||||
|
});
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
const meta = await fetchMeta();
|
const meta = await fetchMeta();
|
||||||
await ctx.render('user', {
|
await ctx.render('user', {
|
||||||
user,
|
user, profile,
|
||||||
instanceName: meta.name || 'Misskey'
|
instanceName: meta.name || 'Misskey'
|
||||||
});
|
});
|
||||||
ctx.set('Cache-Control', 'public, max-age=180');
|
ctx.set('Cache-Control', 'public, max-age=180');
|
||||||
|
@@ -9,12 +9,12 @@ block title
|
|||||||
= `${title} | ${instanceName}`
|
= `${title} | ${instanceName}`
|
||||||
|
|
||||||
block desc
|
block desc
|
||||||
meta(name='description' content= user.description)
|
meta(name='description' content= profile.description)
|
||||||
|
|
||||||
block og
|
block og
|
||||||
meta(property='og:type' content='blog')
|
meta(property='og:type' content='blog')
|
||||||
meta(property='og:title' content= title)
|
meta(property='og:title' content= title)
|
||||||
meta(property='og:description' content= user.description)
|
meta(property='og:description' content= profile.description)
|
||||||
meta(property='og:url' content= url)
|
meta(property='og:url' content= url)
|
||||||
meta(property='og:image' content= img)
|
meta(property='og:image' content= img)
|
||||||
|
|
||||||
@@ -24,12 +24,12 @@ block meta
|
|||||||
|
|
||||||
meta(name='twitter:card' content='summary')
|
meta(name='twitter:card' content='summary')
|
||||||
|
|
||||||
if user.twitter
|
if profile.twitter
|
||||||
meta(name='twitter:creator' content=`@${user.twitter.screenName}`)
|
meta(name='twitter:creator' content=`@${profile.twitter.screenName}`)
|
||||||
|
|
||||||
if !user.host
|
if !user.host
|
||||||
link(rel='alternate' href=`${config.url}/users/${user._id}` type='application/activity+json')
|
link(rel='alternate' href=`${config.url}/users/${user.id}` type='application/activity+json')
|
||||||
if user.uri
|
if user.uri
|
||||||
link(rel='alternate' href=user.uri type='application/activity+json')
|
link(rel='alternate' href=user.uri type='application/activity+json')
|
||||||
if user.url
|
if profile.url
|
||||||
link(rel='alternate' href=user.url type='text/html')
|
link(rel='alternate' href=profile.url type='text/html')
|
||||||
|
@@ -5,6 +5,7 @@ import { DriveFiles, Followings, Users, Notes } from '../../../../models';
|
|||||||
import { DriveFile } from '../../../../models/entities/drive-file';
|
import { DriveFile } from '../../../../models/entities/drive-file';
|
||||||
import { name, schema } from '../schemas/instance';
|
import { name, schema } from '../schemas/instance';
|
||||||
import { Note } from '../../../../models/entities/note';
|
import { Note } from '../../../../models/entities/note';
|
||||||
|
import { toPuny } from '../../../../misc/convert-host';
|
||||||
|
|
||||||
type InstanceLog = SchemaType<typeof schema>;
|
type InstanceLog = SchemaType<typeof schema>;
|
||||||
|
|
||||||
@@ -79,7 +80,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
requests: {
|
requests: {
|
||||||
received: 1
|
received: 1
|
||||||
}
|
}
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -94,7 +95,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
|
|
||||||
await this.inc({
|
await this.inc({
|
||||||
requests: update
|
requests: update
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -104,7 +105,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
total: 1,
|
total: 1,
|
||||||
inc: 1
|
inc: 1
|
||||||
}
|
}
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -126,7 +127,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
dec: isAdditional ? 0 : 1,
|
dec: isAdditional ? 0 : 1,
|
||||||
diffs: diffs
|
diffs: diffs
|
||||||
}
|
}
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -137,7 +138,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
inc: isAdditional ? 1 : 0,
|
inc: isAdditional ? 1 : 0,
|
||||||
dec: isAdditional ? 0 : 1,
|
dec: isAdditional ? 0 : 1,
|
||||||
}
|
}
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
@@ -148,7 +149,7 @@ export default class InstanceChart extends Chart<InstanceLog> {
|
|||||||
inc: isAdditional ? 1 : 0,
|
inc: isAdditional ? 1 : 0,
|
||||||
dec: isAdditional ? 0 : 1,
|
dec: isAdditional ? 0 : 1,
|
||||||
}
|
}
|
||||||
}, host);
|
}, toPuny(host));
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@@ -15,7 +15,7 @@ import { driveLogger } from './logger';
|
|||||||
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
import { IImage, ConvertToJpeg, ConvertToWebp, ConvertToPng } from './image-processor';
|
||||||
import { contentDisposition } from '../../misc/content-disposition';
|
import { contentDisposition } from '../../misc/content-disposition';
|
||||||
import { detectMine } from '../../misc/detect-mine';
|
import { detectMine } from '../../misc/detect-mine';
|
||||||
import { DriveFiles, DriveFolders, Users, Instances } from '../../models';
|
import { DriveFiles, DriveFolders, Users, Instances, UserProfiles } from '../../models';
|
||||||
import { InternalStorage } from './internal-storage';
|
import { InternalStorage } from './internal-storage';
|
||||||
import { DriveFile } from '../../models/entities/drive-file';
|
import { DriveFile } from '../../models/entities/drive-file';
|
||||||
import { IRemoteUser, User } from '../../models/entities/user';
|
import { IRemoteUser, User } from '../../models/entities/user';
|
||||||
@@ -365,6 +365,8 @@ export default async function(
|
|||||||
propPromises = [calcWh(), calcAvg()];
|
propPromises = [calcWh(), calcAvg()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]);
|
const [folder] = await Promise.all([fetchFolder(), Promise.all(propPromises)]);
|
||||||
|
|
||||||
let file = new DriveFile();
|
let file = new DriveFile();
|
||||||
@@ -376,7 +378,7 @@ export default async function(
|
|||||||
file.comment = comment;
|
file.comment = comment;
|
||||||
file.properties = properties;
|
file.properties = properties;
|
||||||
file.isLink = isLink;
|
file.isLink = isLink;
|
||||||
file.isSensitive = Users.isLocalUser(user) && user.alwaysMarkNsfw ? true :
|
file.isSensitive = Users.isLocalUser(user) && profile.alwaysMarkNsfw ? true :
|
||||||
(sensitive !== null && sensitive !== undefined)
|
(sensitive !== null && sensitive !== undefined)
|
||||||
? sensitive
|
? sensitive
|
||||||
: false;
|
: false;
|
||||||
|
@@ -9,7 +9,7 @@ import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc';
|
|||||||
import Logger from '../logger';
|
import Logger from '../logger';
|
||||||
import { IdentifiableError } from '../../misc/identifiable-error';
|
import { IdentifiableError } from '../../misc/identifiable-error';
|
||||||
import { User } from '../../models/entities/user';
|
import { User } from '../../models/entities/user';
|
||||||
import { Followings, Users, FollowRequests, Blockings, Instances } from '../../models';
|
import { Followings, Users, FollowRequests, Blockings, Instances, UserProfiles } from '../../models';
|
||||||
import { instanceChart, perUserFollowingChart } from '../chart';
|
import { instanceChart, perUserFollowingChart } from '../chart';
|
||||||
import { genId } from '../../misc/gen-id';
|
import { genId } from '../../misc/gen-id';
|
||||||
import { createNotification } from '../create-notification';
|
import { createNotification } from '../create-notification';
|
||||||
@@ -115,11 +115,13 @@ export default async function(follower: User, followee: User, requestId?: string
|
|||||||
if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
|
if (blocked != null) throw new IdentifiableError('3338392a-f764-498d-8855-db939dcf8c48', 'blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const followeeProfile = await UserProfiles.findOne({ userId: followee.id });
|
||||||
|
|
||||||
// フォロー対象が鍵アカウントである or
|
// フォロー対象が鍵アカウントである or
|
||||||
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
|
||||||
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
|
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである
|
||||||
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
|
||||||
if (followee.isLocked || (followee.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) {
|
if (followee.isLocked || (followeeProfile.carefulBot && follower.isBot) || (Users.isLocalUser(follower) && Users.isRemoteUser(followee))) {
|
||||||
let autoAccept = false;
|
let autoAccept = false;
|
||||||
|
|
||||||
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
// 鍵アカウントであっても、既にフォローされていた場合はスルー
|
||||||
@@ -132,7 +134,7 @@ export default async function(follower: User, followee: User, requestId?: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// フォローしているユーザーは自動承認オプション
|
// フォローしているユーザーは自動承認オプション
|
||||||
if (!autoAccept && (Users.isLocalUser(followee) && followee.autoAcceptFollowed)) {
|
if (!autoAccept && (Users.isLocalUser(followee) && followeeProfile.autoAcceptFollowed)) {
|
||||||
const followed = await Followings.findOne({
|
const followed = await Followings.findOne({
|
||||||
followerId: followee.id,
|
followerId: followee.id,
|
||||||
followeeId: follower.id
|
followeeId: follower.id
|
||||||
|
@@ -17,10 +17,10 @@ import extractMentions from '../../misc/extract-mentions';
|
|||||||
import extractEmojis from '../../misc/extract-emojis';
|
import extractEmojis from '../../misc/extract-emojis';
|
||||||
import extractHashtags from '../../misc/extract-hashtags';
|
import extractHashtags from '../../misc/extract-hashtags';
|
||||||
import { Note } from '../../models/entities/note';
|
import { Note } from '../../models/entities/note';
|
||||||
import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, Polls } from '../../models';
|
import { Mutings, Users, NoteWatchings, Followings, Notes, Instances, UserProfiles } from '../../models';
|
||||||
import { DriveFile } from '../../models/entities/drive-file';
|
import { DriveFile } from '../../models/entities/drive-file';
|
||||||
import { App } from '../../models/entities/app';
|
import { App } from '../../models/entities/app';
|
||||||
import { Not } from 'typeorm';
|
import { Not, getConnection } from 'typeorm';
|
||||||
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
|
import { User, ILocalUser, IRemoteUser } from '../../models/entities/user';
|
||||||
import { genId } from '../../misc/gen-id';
|
import { genId } from '../../misc/gen-id';
|
||||||
import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart';
|
import { notesChart, perUserNotesChart, activeUsersChart, instanceChart } from '../chart';
|
||||||
@@ -256,13 +256,15 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
|
|||||||
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
|
deliverNoteToMentionedRemoteUsers(mentionedUsers, user, noteActivity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// If has in reply to note
|
// If has in reply to note
|
||||||
if (data.reply) {
|
if (data.reply) {
|
||||||
// Fetch watchers
|
// Fetch watchers
|
||||||
nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm));
|
nmRelatedPromises.push(notifyToWatchersOfReplyee(data.reply, user, nm));
|
||||||
|
|
||||||
// この投稿をWatchする
|
// この投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && user.autoWatch !== false) {
|
if (Users.isLocalUser(user) && profile.autoWatch) {
|
||||||
watch(user.id, data.reply);
|
watch(user.id, data.reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -286,7 +288,7 @@ export default async (user: User, data: Option, silent = false) => new Promise<N
|
|||||||
nmRelatedPromises.push(notifyToWatchersOfRenotee(data.renote, user, nm, type));
|
nmRelatedPromises.push(notifyToWatchersOfRenotee(data.renote, user, nm, type));
|
||||||
|
|
||||||
// この投稿をWatchする
|
// この投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && user.autoWatch !== false) {
|
if (Users.isLocalUser(user) && profile.autoWatch) {
|
||||||
watch(user.id, data.renote);
|
watch(user.id, data.renote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -327,12 +329,16 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc
|
|||||||
if (Users.isLocalUser(user)) {
|
if (Users.isLocalUser(user)) {
|
||||||
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
|
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
|
||||||
if (reply && reply.userHost !== null) {
|
if (reply && reply.userHost !== null) {
|
||||||
deliver(user, noteActivity, reply.userInbox);
|
Users.findOne(reply.userId).then(u => {
|
||||||
|
deliver(user, noteActivity, u.inbox);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
|
// 投稿がRenoteかつ投稿者がローカルユーザーかつRenote元の投稿の投稿者がリモートユーザーなら配送
|
||||||
if (renote && renote.userHost !== null) {
|
if (renote && renote.userHost !== null) {
|
||||||
deliver(user, noteActivity, renote.userInbox);
|
Users.findOne(renote.userId).then(u => {
|
||||||
|
deliver(user, noteActivity, u.inbox);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -343,7 +349,7 @@ async function publish(user: User, note: Note, reply: Note, renote: Note, noteAc
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) {
|
async function insertNote(user: User, data: Option, tags: string[], emojis: string[], mentionedUsers: User[]) {
|
||||||
const insert: Partial<Note> = {
|
const insert = new Note({
|
||||||
id: genId(data.createdAt),
|
id: genId(data.createdAt),
|
||||||
createdAt: data.createdAt,
|
createdAt: data.createdAt,
|
||||||
fileIds: data.files ? data.files.map(file => file.id) : [],
|
fileIds: data.files ? data.files.map(file => file.id) : [],
|
||||||
@@ -375,8 +381,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
|
|||||||
renoteUserId: data.renote ? data.renote.userId : null,
|
renoteUserId: data.renote ? data.renote.userId : null,
|
||||||
renoteUserHost: data.renote ? data.renote.userHost : null,
|
renoteUserHost: data.renote ? data.renote.userHost : null,
|
||||||
userHost: user.host,
|
userHost: user.host,
|
||||||
userInbox: user.inbox,
|
});
|
||||||
};
|
|
||||||
|
|
||||||
if (data.uri != null) insert.uri = data.uri;
|
if (data.uri != null) insert.uri = data.uri;
|
||||||
|
|
||||||
@@ -392,11 +397,13 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
|
|||||||
|
|
||||||
// 投稿を作成
|
// 投稿を作成
|
||||||
try {
|
try {
|
||||||
const note = await Notes.save(insert);
|
let note: Note;
|
||||||
|
if (insert.hasPoll) {
|
||||||
|
// Start transaction
|
||||||
|
await getConnection().transaction(async transactionalEntityManager => {
|
||||||
|
note = await transactionalEntityManager.save(insert);
|
||||||
|
|
||||||
if (note.hasPoll) {
|
const poll = new Poll({
|
||||||
await Polls.save({
|
|
||||||
id: genId(),
|
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
choices: data.poll.choices,
|
choices: data.poll.choices,
|
||||||
expiresAt: data.poll.expiresAt,
|
expiresAt: data.poll.expiresAt,
|
||||||
@@ -405,7 +412,12 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
|
|||||||
noteVisibility: note.visibility,
|
noteVisibility: note.visibility,
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
userHost: user.host
|
userHost: user.host
|
||||||
} as Poll);
|
});
|
||||||
|
|
||||||
|
await transactionalEntityManager.save(poll);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
note = await Notes.save(insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
return note;
|
return note;
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
import { updateQuestion } from '../../../remote/activitypub/models/question';
|
|
||||||
import ms = require('ms');
|
|
||||||
import Logger from '../../logger';
|
|
||||||
import renderUpdate from '../../../remote/activitypub/renderer/update';
|
import renderUpdate from '../../../remote/activitypub/renderer/update';
|
||||||
import { renderActivity } from '../../../remote/activitypub/renderer';
|
import { renderActivity } from '../../../remote/activitypub/renderer';
|
||||||
import { deliver } from '../../../queue';
|
import { deliver } from '../../../queue';
|
||||||
@@ -8,21 +5,6 @@ import renderNote from '../../../remote/activitypub/renderer/note';
|
|||||||
import { Users, Notes, Followings } from '../../../models';
|
import { Users, Notes, Followings } from '../../../models';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
|
|
||||||
const logger = new Logger('pollsUpdate');
|
|
||||||
|
|
||||||
export async function triggerUpdate(note: Note) {
|
|
||||||
if (!note.updatedAt || Date.now() - new Date(note.updatedAt).getTime() > ms('1min')) {
|
|
||||||
logger.info(`Updating ${note.id}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const updated = await updateQuestion(note.uri);
|
|
||||||
logger.info(`Updated ${note.id} ${updated ? 'changed' : 'nochange'}`);
|
|
||||||
} catch (e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function deliverQuestionUpdate(noteId: Note['id']) {
|
export async function deliverQuestionUpdate(noteId: Note['id']) {
|
||||||
const note = await Notes.findOne(noteId);
|
const note = await Notes.findOne(noteId);
|
||||||
|
|
||||||
|
@@ -2,7 +2,7 @@ import watch from '../../../services/note/watch';
|
|||||||
import { publishNoteStream } from '../../stream';
|
import { publishNoteStream } from '../../stream';
|
||||||
import { User } from '../../../models/entities/user';
|
import { User } from '../../../models/entities/user';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { PollVotes, Users, NoteWatchings, Polls } from '../../../models';
|
import { PollVotes, Users, NoteWatchings, Polls, UserProfiles } from '../../../models';
|
||||||
import { Not } from 'typeorm';
|
import { Not } from 'typeorm';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
import { createNotification } from '../../create-notification';
|
import { createNotification } from '../../create-notification';
|
||||||
@@ -40,7 +40,7 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re
|
|||||||
|
|
||||||
// Increment votes count
|
// Increment votes count
|
||||||
const index = choice + 1; // In SQL, array index is 1 based
|
const index = choice + 1; // In SQL, array index is 1 based
|
||||||
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE id = '${poll.id}'`);
|
await Polls.query(`UPDATE poll SET votes[${index}] = votes[${index}] + 1 WHERE noteId = '${poll.noteId}'`);
|
||||||
|
|
||||||
publishNoteStream(note.id, 'pollVoted', {
|
publishNoteStream(note.id, 'pollVoted', {
|
||||||
choice: choice,
|
choice: choice,
|
||||||
@@ -67,8 +67,10 @@ export default (user: User, note: Note, choice: number) => new Promise(async (re
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// ローカルユーザーが投票した場合この投稿をWatchする
|
// ローカルユーザーが投票した場合この投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && user.autoWatch) {
|
if (Users.isLocalUser(user) && profile.autoWatch) {
|
||||||
watch(user.id, note);
|
watch(user.id, note);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@@ -8,7 +8,7 @@ import { toDbReaction } from '../../../misc/reaction-lib';
|
|||||||
import fetchMeta from '../../../misc/fetch-meta';
|
import fetchMeta from '../../../misc/fetch-meta';
|
||||||
import { User } from '../../../models/entities/user';
|
import { User } from '../../../models/entities/user';
|
||||||
import { Note } from '../../../models/entities/note';
|
import { Note } from '../../../models/entities/note';
|
||||||
import { NoteReactions, Users, NoteWatchings, Notes } from '../../../models';
|
import { NoteReactions, Users, NoteWatchings, Notes, UserProfiles } from '../../../models';
|
||||||
import { Not } from 'typeorm';
|
import { Not } from 'typeorm';
|
||||||
import { perUserReactionsChart } from '../../chart';
|
import { perUserReactionsChart } from '../../chart';
|
||||||
import { genId } from '../../../misc/gen-id';
|
import { genId } from '../../../misc/gen-id';
|
||||||
@@ -79,8 +79,10 @@ export default async (user: User, note: Note, reaction: string) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const profile = await UserProfiles.findOne({ userId: user.id });
|
||||||
|
|
||||||
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
|
// ユーザーがローカルユーザーかつ自動ウォッチ設定がオンならばこの投稿をWatchする
|
||||||
if (Users.isLocalUser(user) && user.autoWatch !== false) {
|
if (Users.isLocalUser(user) && profile.autoWatch) {
|
||||||
watch(user.id, note);
|
watch(user.id, note);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +90,9 @@ export default async (user: User, note: Note, reaction: string) => {
|
|||||||
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
||||||
if (Users.isLocalUser(user) && note.userHost !== null) {
|
if (Users.isLocalUser(user) && note.userHost !== null) {
|
||||||
const content = renderActivity(renderLike(user, note, reaction));
|
const content = renderActivity(renderLike(user, note, reaction));
|
||||||
deliver(user, content, note.userInbox);
|
Users.findOne(note.userId).then(u => {
|
||||||
|
deliver(user, content, u.inbox);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
};
|
};
|
||||||
|
@@ -41,7 +41,9 @@ export default async (user: User, note: Note) => {
|
|||||||
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
// リアクターがローカルユーザーかつリアクション対象がリモートユーザーの投稿なら配送
|
||||||
if (Users.isLocalUser(user) && (note.userHost !== null)) {
|
if (Users.isLocalUser(user) && (note.userHost !== null)) {
|
||||||
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
|
const content = renderActivity(renderUndo(renderLike(user, note, exist.reaction), user));
|
||||||
deliver(user, content, note.userInbox);
|
Users.findOne(note.userId).then(u => {
|
||||||
|
deliver(user, content, u.inbox);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
//#endregion
|
//#endregion
|
||||||
};
|
};
|
||||||
|
Reference in New Issue
Block a user