トランザクションを使うようにしたり

This commit is contained in:
syuilo 2019-04-12 01:52:25 +09:00
parent 4198246351
commit 2ff3069d23
No known key found for this signature in database
GPG Key ID: BDC4C49D06AB9D69
12 changed files with 117 additions and 67 deletions

View File

@ -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">

View File

@ -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"/>

View File

@ -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">

View File

@ -22,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;
}
}
} }

View File

@ -39,6 +39,12 @@ export class UserProfile {
value: string; value: string;
}[]; }[];
@Column('varchar', {
length: 512, nullable: true,
comment: 'Remote URL of the user.'
})
public url: string | null;
@Column('varchar', { @Column('varchar', {
length: 128, nullable: true, length: 128, nullable: true,
comment: 'The email address of the User.' comment: 'The email address of the User.'
@ -192,4 +198,12 @@ export class UserProfile {
}) })
public userHost: string | null; public userHost: string | null;
//#endregion //#endregion
constructor(data: Partial<UserProfile>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
} }

View File

@ -23,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;
}
}
} }

View File

@ -205,6 +205,14 @@ export class User {
comment: 'The native access token of the User. It will be null if the origin of the user is local.' comment: 'The native access token of the User. It will be null if the origin of the user is local.'
}) })
public token: string | null; public token: string | null;
constructor(data: Partial<User>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
} }
export interface ILocalUser extends User { export interface ILocalUser extends User {

View File

@ -87,7 +87,6 @@ export class UserRepository extends Repository<User> {
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,
@ -118,6 +117,7 @@ export class UserRepository extends Repository<User> {
} : {}), } : {}),
...(opts.detail ? { ...(opts.detail ? {
url: profile.url,
createdAt: user.createdAt, createdAt: user.createdAt,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
description: profile.description, description: profile.description,

View File

@ -25,6 +25,7 @@ import { isDuplicateKeyValueError } from '../../../misc/is-duplicate-key-value-e
import { toPuny } from '../../../misc/convert-host'; import { toPuny } from '../../../misc/convert-host';
import { UserProfile } from '../../../models/entities/user-profile'; import { UserProfile } from '../../../models/entities/user-profile';
import { validActor } from '../../../remote/activitypub/type'; import { validActor } from '../../../remote/activitypub/type';
import { getConnection } from 'typeorm';
const logger = apLogger; const logger = apLogger;
/** /**
@ -136,11 +137,13 @@ 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(person.published) || new Date(),
lastFetchedAt: new Date(), lastFetchedAt: new Date(),
name: person.name, name: person.name,
isLocked: person.manuallyApprovesFollowers, isLocked: person.manuallyApprovesFollowers,
@ -150,13 +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,
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)) {
@ -167,19 +183,6 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
throw e; throw e;
} }
await UserProfiles.save({
userId: user.id,
description: fromHtml(person.summary),
fields,
userHost: host
} as Partial<UserProfile>);
await UserPublickeys.save({
userId: user.id,
keyId: person.publicKey.id,
keyPem: person.publicKey.publicKeyPem
} as UserPublickey);
// 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);

View File

@ -12,6 +12,7 @@ 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 { 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,7 +100,11 @@ 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,
@ -107,20 +112,21 @@ export default async (ctx: Koa.BaseContext) => {
host: toPuny(host), host: toPuny(host),
token: secret, token: secret,
isAdmin: config.autoAdmin && usersCount === 0, isAdmin: config.autoAdmin && usersCount === 0,
} as User); }));
await UserKeypairs.save({ await transactionalEntityManager.save(new UserKeypair({
publicKey: keyPair[0], publicKey: keyPair[0],
privateKey: keyPair[1], privateKey: keyPair[1],
userId: account.id userId: account.id
} as UserKeypair); }));
await UserProfiles.save({ await transactionalEntityManager.save(new UserProfile({
userId: account.id, userId: account.id,
autoAcceptFollowed: true, autoAcceptFollowed: true,
autoWatch: false, autoWatch: false,
password: hash, password: hash,
} as Partial<UserProfile>); }));
});
usersChart.update(account, true); usersChart.update(account, true);

View File

@ -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');

View File

@ -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')