set alsoKnownAs via /i/update
This commit is contained in:
parent
2a0efffa3e
commit
55f9112eed
|
@ -102,32 +102,6 @@ export class AccountMoveService {
|
||||||
return iObj;
|
return iObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an alias of an old remote account.
|
|
||||||
*
|
|
||||||
* The user's new profile will be published to the followers.
|
|
||||||
*/
|
|
||||||
@bindThis
|
|
||||||
public async createAlias(me: LocalUser, updates: Partial<User>): Promise<unknown> {
|
|
||||||
await this.usersRepository.update(me.id, updates);
|
|
||||||
me = Object.assign(me, updates);
|
|
||||||
|
|
||||||
// Publish meUpdated event
|
|
||||||
const iObj = await this.userEntityService.pack<true, true>(me.id, me, {
|
|
||||||
detail: true,
|
|
||||||
includeSecrets: true,
|
|
||||||
});
|
|
||||||
this.globalEventService.publishMainStream(me.id, 'meUpdated', iObj);
|
|
||||||
|
|
||||||
if (me.isLocked === false) {
|
|
||||||
await this.userFollowingService.acceptAllFollowRequests(me);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.accountUpdateService.publishToFollowers(me.id);
|
|
||||||
|
|
||||||
return iObj;
|
|
||||||
}
|
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async move(src: User, dst: User): Promise<void> {
|
public async move(src: User, dst: User): Promise<void> {
|
||||||
// Copy blockings and mutings, and update lists
|
// Copy blockings and mutings, and update lists
|
||||||
|
@ -144,9 +118,9 @@ export class AccountMoveService {
|
||||||
// follow the new account and unfollow the old one
|
// follow the new account and unfollow the old one
|
||||||
const proxy = await this.proxyAccountService.fetch();
|
const proxy = await this.proxyAccountService.fetch();
|
||||||
const followings = await this.followingsRepository.findBy({
|
const followings = await this.followingsRepository.findBy({
|
||||||
followeeId: src.id,
|
followeeId: src.id,
|
||||||
followerHost: IsNull(), // follower is local
|
followerHost: IsNull(), // follower is local
|
||||||
followerId: proxy ? Not(proxy.id) : undefined,
|
followerId: proxy ? Not(proxy.id) : undefined,
|
||||||
});
|
});
|
||||||
const followJobs = followings.map(following => ({
|
const followJobs = followings.map(following => ({
|
||||||
from: { id: following.followerId },
|
from: { id: following.followerId },
|
||||||
|
|
|
@ -223,7 +223,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||||
import * as ep___i_update from './endpoints/i/update.js';
|
import * as ep___i_update from './endpoints/i/update.js';
|
||||||
import * as ep___i_move from './endpoints/i/move.js';
|
import * as ep___i_move from './endpoints/i/move.js';
|
||||||
import * as ep___i_knownAs from './endpoints/i/known-as.js';
|
|
||||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||||
|
@ -560,7 +559,6 @@ const $i_unpin: Provider = { provide: 'ep:i/unpin', useClass: ep___i_unpin.defau
|
||||||
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
|
const $i_updateEmail: Provider = { provide: 'ep:i/update-email', useClass: ep___i_updateEmail.default };
|
||||||
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
|
const $i_update: Provider = { provide: 'ep:i/update', useClass: ep___i_update.default };
|
||||||
const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
|
const $i_move: Provider = { provide: 'ep:i/move', useClass: ep___i_move.default };
|
||||||
const $i_knownAs: Provider = { provide: 'ep:i/known-as', useClass: ep___i_knownAs.default };
|
|
||||||
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
|
const $i_webhooks_create: Provider = { provide: 'ep:i/webhooks/create', useClass: ep___i_webhooks_create.default };
|
||||||
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
|
const $i_webhooks_list: Provider = { provide: 'ep:i/webhooks/list', useClass: ep___i_webhooks_list.default };
|
||||||
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
const $i_webhooks_show: Provider = { provide: 'ep:i/webhooks/show', useClass: ep___i_webhooks_show.default };
|
||||||
|
@ -901,7 +899,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$i_updateEmail,
|
$i_updateEmail,
|
||||||
$i_update,
|
$i_update,
|
||||||
$i_move,
|
$i_move,
|
||||||
$i_knownAs,
|
|
||||||
$i_webhooks_create,
|
$i_webhooks_create,
|
||||||
$i_webhooks_list,
|
$i_webhooks_list,
|
||||||
$i_webhooks_show,
|
$i_webhooks_show,
|
||||||
|
@ -1236,7 +1233,6 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
|
||||||
$i_updateEmail,
|
$i_updateEmail,
|
||||||
$i_update,
|
$i_update,
|
||||||
$i_move,
|
$i_move,
|
||||||
$i_knownAs,
|
|
||||||
$i_webhooks_create,
|
$i_webhooks_create,
|
||||||
$i_webhooks_list,
|
$i_webhooks_list,
|
||||||
$i_webhooks_show,
|
$i_webhooks_show,
|
||||||
|
|
|
@ -223,7 +223,6 @@ import * as ep___i_unpin from './endpoints/i/unpin.js';
|
||||||
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
import * as ep___i_updateEmail from './endpoints/i/update-email.js';
|
||||||
import * as ep___i_update from './endpoints/i/update.js';
|
import * as ep___i_update from './endpoints/i/update.js';
|
||||||
import * as ep___i_move from './endpoints/i/move.js';
|
import * as ep___i_move from './endpoints/i/move.js';
|
||||||
import * as ep___i_knownAs from './endpoints/i/known-as.js';
|
|
||||||
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
import * as ep___i_webhooks_create from './endpoints/i/webhooks/create.js';
|
||||||
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
import * as ep___i_webhooks_show from './endpoints/i/webhooks/show.js';
|
||||||
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
import * as ep___i_webhooks_list from './endpoints/i/webhooks/list.js';
|
||||||
|
@ -558,7 +557,6 @@ const eps = [
|
||||||
['i/update-email', ep___i_updateEmail],
|
['i/update-email', ep___i_updateEmail],
|
||||||
['i/update', ep___i_update],
|
['i/update', ep___i_update],
|
||||||
['i/move', ep___i_move],
|
['i/move', ep___i_move],
|
||||||
['i/known-as', ep___i_knownAs],
|
|
||||||
['i/webhooks/create', ep___i_webhooks_create],
|
['i/webhooks/create', ep___i_webhooks_create],
|
||||||
['i/webhooks/list', ep___i_webhooks_list],
|
['i/webhooks/list', ep___i_webhooks_list],
|
||||||
['i/webhooks/show', ep___i_webhooks_show],
|
['i/webhooks/show', ep___i_webhooks_show],
|
||||||
|
|
|
@ -1,87 +0,0 @@
|
||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import ms from 'ms';
|
|
||||||
|
|
||||||
import { User } from '@/models/entities/User.js';
|
|
||||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
|
||||||
import { ApiError } from '@/server/api/error.js';
|
|
||||||
|
|
||||||
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
|
||||||
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
|
||||||
import { ApiLoggerService } from '@/server/api/ApiLoggerService.js';
|
|
||||||
|
|
||||||
export const meta = {
|
|
||||||
tags: ['users'],
|
|
||||||
|
|
||||||
secure: true,
|
|
||||||
requireCredential: true,
|
|
||||||
prohibitMoved: true,
|
|
||||||
|
|
||||||
limit: {
|
|
||||||
duration: ms('1day'),
|
|
||||||
max: 30,
|
|
||||||
},
|
|
||||||
|
|
||||||
errors: {
|
|
||||||
noSuchUser: {
|
|
||||||
message: 'No such user.',
|
|
||||||
code: 'NO_SUCH_USER',
|
|
||||||
id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
|
|
||||||
},
|
|
||||||
uriNull: {
|
|
||||||
message: 'User ActivityPup URI is null.',
|
|
||||||
code: 'URI_NULL',
|
|
||||||
id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
|
|
||||||
},
|
|
||||||
forbiddenToSetYourself: {
|
|
||||||
message: 'You can\'t set yourself as your own alias.',
|
|
||||||
code: 'FORBIDDEN_TO_SET_YOURSELF',
|
|
||||||
id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
export const paramDef = {
|
|
||||||
type: 'object',
|
|
||||||
properties: {
|
|
||||||
alsoKnownAs: { type: 'string' },
|
|
||||||
},
|
|
||||||
required: ['alsoKnownAs'],
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export default class extends Endpoint<typeof meta, typeof paramDef> {
|
|
||||||
constructor(
|
|
||||||
private remoteUserResolveService: RemoteUserResolveService,
|
|
||||||
private apiLoggerService: ApiLoggerService,
|
|
||||||
private accountMoveService: AccountMoveService,
|
|
||||||
) {
|
|
||||||
super(meta, paramDef, async (ps, me) => {
|
|
||||||
let unfiltered = ps.alsoKnownAs;
|
|
||||||
const updates = {} as Partial<User>;
|
|
||||||
|
|
||||||
if (!unfiltered) {
|
|
||||||
updates.alsoKnownAs = null;
|
|
||||||
} else {
|
|
||||||
// Parse user's input into the old account
|
|
||||||
if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
|
|
||||||
if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
|
|
||||||
if (!unfiltered.includes('@')) throw new ApiError(meta.errors.noSuchUser);
|
|
||||||
|
|
||||||
const userAddress = unfiltered.split('@');
|
|
||||||
// Retrieve the old account
|
|
||||||
const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
|
|
||||||
this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`);
|
|
||||||
throw new ApiError(meta.errors.noSuchUser);
|
|
||||||
});
|
|
||||||
if (knownAs.id === me.id) throw new ApiError(meta.errors.forbiddenToSetYourself);
|
|
||||||
|
|
||||||
const toUrl = this.accountMoveService.getUserUri(knownAs);
|
|
||||||
if (!toUrl) throw new ApiError(meta.errors.uriNull);
|
|
||||||
|
|
||||||
updates.alsoKnownAs = me.alsoKnownAs?.includes(toUrl) ? me.alsoKnownAs : me.alsoKnownAs?.concat([toUrl]) ?? [toUrl];
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.accountMoveService.createAlias(me, updates);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -19,7 +19,10 @@ import { HashtagService } from '@/core/HashtagService.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import { RoleService } from '@/core/RoleService.js';
|
import { RoleService } from '@/core/RoleService.js';
|
||||||
import { CacheService } from '@/core/CacheService.js';
|
import { CacheService } from '@/core/CacheService.js';
|
||||||
|
import { AccountMoveService } from '@/core/AccountMoveService.js';
|
||||||
|
import { RemoteUserResolveService } from '@/core/RemoteUserResolveService.js';
|
||||||
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
import { DriveFileEntityService } from '@/core/entities/DriveFileEntityService.js';
|
||||||
|
import { ApiLoggerService } from '../../ApiLoggerService.js';
|
||||||
import { ApiError } from '../../error.js';
|
import { ApiError } from '../../error.js';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
|
@ -71,6 +74,24 @@ export const meta = {
|
||||||
code: 'TOO_MANY_MUTED_WORDS',
|
code: 'TOO_MANY_MUTED_WORDS',
|
||||||
id: '010665b1-a211-42d2-bc64-8f6609d79785',
|
id: '010665b1-a211-42d2-bc64-8f6609d79785',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
noSuchUser: {
|
||||||
|
message: 'No such user.',
|
||||||
|
code: 'NO_SUCH_USER',
|
||||||
|
id: 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5',
|
||||||
|
},
|
||||||
|
|
||||||
|
uriNull: {
|
||||||
|
message: 'User ActivityPup URI is null.',
|
||||||
|
code: 'URI_NULL',
|
||||||
|
id: 'bf326f31-d430-4f97-9933-5d61e4d48a23',
|
||||||
|
},
|
||||||
|
|
||||||
|
forbiddenToSetYourself: {
|
||||||
|
message: 'You can\'t set yourself as your own alias.',
|
||||||
|
code: 'FORBIDDEN_TO_SET_YOURSELF',
|
||||||
|
id: '25c90186-4ab0-49c8-9bba-a1fa6c202ba4',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
res: {
|
res: {
|
||||||
|
@ -129,6 +150,12 @@ export const paramDef = {
|
||||||
emailNotificationTypes: { type: 'array', items: {
|
emailNotificationTypes: { type: 'array', items: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
} },
|
} },
|
||||||
|
alsoKnownAs: {
|
||||||
|
type: 'array',
|
||||||
|
maxItems: 5,
|
||||||
|
uniqueItems: true,
|
||||||
|
items: { type: 'string' },
|
||||||
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
@ -153,6 +180,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private userFollowingService: UserFollowingService,
|
private userFollowingService: UserFollowingService,
|
||||||
private accountUpdateService: AccountUpdateService,
|
private accountUpdateService: AccountUpdateService,
|
||||||
|
private accountMoveService: AccountMoveService,
|
||||||
|
private remoteUserResolveService: RemoteUserResolveService,
|
||||||
|
private apiLoggerService: ApiLoggerService,
|
||||||
private hashtagService: HashtagService,
|
private hashtagService: HashtagService,
|
||||||
private roleService: RoleService,
|
private roleService: RoleService,
|
||||||
private cacheService: CacheService,
|
private cacheService: CacheService,
|
||||||
|
@ -260,6 +290,41 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ps.alsoKnownAs) {
|
||||||
|
if (_user.movedToUri) {
|
||||||
|
throw new ApiError({
|
||||||
|
message: 'You have moved your account.',
|
||||||
|
code: 'YOUR_ACCOUNT_MOVED',
|
||||||
|
id: '56f20ec9-fd06-4fa5-841b-edd6d7d4fa31',
|
||||||
|
httpStatusCode: 403,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse user's input into the old account
|
||||||
|
const newAlsoKnownAs: string[] = [];
|
||||||
|
for (const line of ps.alsoKnownAs) {
|
||||||
|
let unfiltered = line;
|
||||||
|
if (unfiltered.startsWith('acct:')) unfiltered = unfiltered.substring(5);
|
||||||
|
if (unfiltered.startsWith('@')) unfiltered = unfiltered.substring(1);
|
||||||
|
if (!unfiltered.includes('@')) throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
|
||||||
|
const userAddress = unfiltered.split('@');
|
||||||
|
// Retrieve the old account
|
||||||
|
const knownAs = await this.remoteUserResolveService.resolveUser(userAddress[0], userAddress[1]).catch((e) => {
|
||||||
|
this.apiLoggerService.logger.warn(`failed to resolve dstination user: ${e}`);
|
||||||
|
throw new ApiError(meta.errors.noSuchUser);
|
||||||
|
});
|
||||||
|
if (knownAs.id === _user.id) throw new ApiError(meta.errors.forbiddenToSetYourself);
|
||||||
|
|
||||||
|
const toUrl = this.accountMoveService.getUserUri(knownAs);
|
||||||
|
if (!toUrl) throw new ApiError(meta.errors.uriNull);
|
||||||
|
|
||||||
|
newAlsoKnownAs.push(toUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
updates.alsoKnownAs = newAlsoKnownAs.length > 0 ? newAlsoKnownAs : null;
|
||||||
|
}
|
||||||
|
|
||||||
//#region emojis/tags
|
//#region emojis/tags
|
||||||
|
|
||||||
let emojis = [] as string[];
|
let emojis = [] as string[];
|
||||||
|
|
|
@ -48,8 +48,8 @@ describe('Account Move', () => {
|
||||||
}, 1000 * 10);
|
}, 1000 * 10);
|
||||||
|
|
||||||
test('Able to create an alias', async () => {
|
test('Able to create an alias', async () => {
|
||||||
await api('/i/known-as', {
|
await api('/i/update', {
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
alsoKnownAs: [`@alice@${url.hostname}`],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
||||||
|
@ -58,8 +58,8 @@ describe('Account Move', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Able to set remote user (but may fail)', async () => {
|
test('Able to set remote user (but may fail)', async () => {
|
||||||
const res = await api('/i/known-as', {
|
const res = await api('/i/update', {
|
||||||
alsoKnownAs: '@syuilo@example.com',
|
alsoKnownAs: ['@syuilo@example.com'],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
|
@ -67,22 +67,19 @@ describe('Account Move', () => {
|
||||||
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(res.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Nothing happen when alias duplicated', async () => {
|
test('Unable to add duplicated aliases to alsoKnownAs', async () => {
|
||||||
await api('/i/known-as', {
|
const res = await api('/i/update', {
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
alsoKnownAs: [`@alice@${url.hostname}`, `@alice@${url.hostname}`],
|
||||||
}, bob);
|
|
||||||
await api('/i/known-as', {
|
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
assert.strictEqual(res.status, 400);
|
||||||
assert.strictEqual(newBob.alsoKnownAs?.length, 1);
|
assert.strictEqual(res.body.error.code, 'INVALID_PARAM');
|
||||||
assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${alice.id}`);
|
assert.strictEqual(res.body.error.id, '3d81ceae-475f-4600-b2a8-2bc116157532');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to add itself', async () => {
|
test('Unable to add itself', async () => {
|
||||||
const res = await api('/i/known-as', {
|
const res = await api('/i/update', {
|
||||||
alsoKnownAs: `@bob@${url.hostname}`,
|
alsoKnownAs: [`@bob@${url.hostname}`],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
|
@ -91,8 +88,8 @@ describe('Account Move', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
|
test('Unable to add a nonexisting local account to alsoKnownAs', async () => {
|
||||||
const res = await api('/i/known-as', {
|
const res = await api('/i/update', {
|
||||||
alsoKnownAs: `@nonexist@${url.hostname}`,
|
alsoKnownAs: [`@nonexist@${url.hostname}`],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res.status, 400);
|
assert.strictEqual(res.status, 400);
|
||||||
|
@ -101,11 +98,8 @@ describe('Account Move', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Able to add two existing local account to alsoKnownAs', async () => {
|
test('Able to add two existing local account to alsoKnownAs', async () => {
|
||||||
await api('/i/known-as', {
|
await api('/i/update', {
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
alsoKnownAs: [`@alice@${url.hostname}`, `@carol@${url.hostname}`],
|
||||||
}, bob);
|
|
||||||
await api('/i/known-as', {
|
|
||||||
alsoKnownAs: `@carol@${url.hostname}`,
|
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
||||||
|
@ -114,17 +108,31 @@ describe('Account Move', () => {
|
||||||
assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${carol.id}`);
|
assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${carol.id}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Able to properly overwrite alsoKnownAs', async () => {
|
||||||
|
await api('/i/update', {
|
||||||
|
alsoKnownAs: [`@alice@${url.hostname}`],
|
||||||
|
}, bob);
|
||||||
|
await api('/i/update', {
|
||||||
|
alsoKnownAs: [`@carol@${url.hostname}`, `@dave@${url.hostname}`],
|
||||||
|
}, bob);
|
||||||
|
|
||||||
|
const newBob = await Users.findOneByOrFail({ id: bob.id });
|
||||||
|
assert.strictEqual(newBob.alsoKnownAs?.length, 2);
|
||||||
|
assert.strictEqual(newBob.alsoKnownAs[0], `${url.origin}/users/${carol.id}`);
|
||||||
|
assert.strictEqual(newBob.alsoKnownAs[1], `${url.origin}/users/${dave.id}`);
|
||||||
|
});
|
||||||
|
|
||||||
test('Unable to create an alias without the second @', async () => {
|
test('Unable to create an alias without the second @', async () => {
|
||||||
const res1 = await api('/i/known-as', {
|
const res1 = await api('/i/update', {
|
||||||
alsoKnownAs: '@alice',
|
alsoKnownAs: ['@alice'],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res1.status, 400);
|
assert.strictEqual(res1.status, 400);
|
||||||
assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER');
|
assert.strictEqual(res1.body.error.code, 'NO_SUCH_USER');
|
||||||
assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
assert.strictEqual(res1.body.error.id, 'fcd2eef9-a9b2-4c4f-8624-038099e90aa5');
|
||||||
|
|
||||||
const res2 = await api('/i/known-as', {
|
const res2 = await api('/i/update', {
|
||||||
alsoKnownAs: 'alice',
|
alsoKnownAs: ['alice'],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
assert.strictEqual(res2.status, 400);
|
assert.strictEqual(res2.status, 400);
|
||||||
|
@ -137,8 +145,8 @@ describe('Account Move', () => {
|
||||||
let antennaId = '';
|
let antennaId = '';
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await api('/i/known-as', {
|
await api('/i/update', {
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
alsoKnownAs: [`@alice@${url.hostname}`],
|
||||||
}, root);
|
}, root);
|
||||||
const list = await api('/users/lists/create', {
|
const list = await api('/users/lists/create', {
|
||||||
name: rndstr('0-9a-z', 8),
|
name: rndstr('0-9a-z', 8),
|
||||||
|
@ -167,8 +175,8 @@ describe('Account Move', () => {
|
||||||
}, alice);
|
}, alice);
|
||||||
antennaId = antenna.body.id;
|
antennaId = antenna.body.id;
|
||||||
|
|
||||||
await api('/i/known-as', {
|
await api('/i/update', {
|
||||||
alsoKnownAs: `@alice@${url.hostname}`,
|
alsoKnownAs: [`@alice@${url.hostname}`],
|
||||||
}, bob);
|
}, bob);
|
||||||
|
|
||||||
await api('/following/create', {
|
await api('/following/create', {
|
||||||
|
@ -342,7 +350,7 @@ describe('Account Move', () => {
|
||||||
'/gallery/posts/like',
|
'/gallery/posts/like',
|
||||||
'/gallery/posts/unlike',
|
'/gallery/posts/unlike',
|
||||||
'/gallery/posts/update',
|
'/gallery/posts/update',
|
||||||
'/i/known-as',
|
'/i/update',
|
||||||
'/i/move',
|
'/i/move',
|
||||||
'/notes/create',
|
'/notes/create',
|
||||||
'/notes/polls/vote',
|
'/notes/polls/vote',
|
||||||
|
|
|
@ -2,35 +2,51 @@
|
||||||
<div class="_gaps_m">
|
<div class="_gaps_m">
|
||||||
<FormSection first>
|
<FormSection first>
|
||||||
<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
|
<template #label>{{ i18n.ts._accountMigration.moveTo }}</template>
|
||||||
<MkInput v-model="moveToAccount" manual-save>
|
<div class="_gaps_m">
|
||||||
<template #prefix><i class="ti ti-plane-departure"></i></template>
|
<div>
|
||||||
<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
|
<MkInput v-model="moveToAccount">
|
||||||
</MkInput>
|
<template #prefix><i class="ti ti-plane-departure"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MkButton inline primary :disabled="!moveToAccount" @click="move"><i class="ti ti-check"></i> {{ i18n.ts.ok }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormInfo warn>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo>
|
<FormInfo warn>{{ i18n.ts._accountMigration.moveAccountDescription }}</FormInfo>
|
||||||
|
|
||||||
<FormSection>
|
<FormSection>
|
||||||
<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
|
<template #label>{{ i18n.ts._accountMigration.moveFrom }}</template>
|
||||||
<MkInput v-model="accountAlias" manual-save>
|
<div class="_gaps_m">
|
||||||
<template #prefix><i class="ti ti-plane-arrival"></i></template>
|
<div v-for="(_, i) in accountAliases">
|
||||||
<template #label>{{ i18n.ts._accountMigration.moveFromLabel }}</template>
|
<MkInput v-model="accountAliases[i]">
|
||||||
</MkInput>
|
<template #prefix><i class="ti ti-plane-arrival"></i></template>
|
||||||
|
<template #label>{{ i18n.ts._accountMigration.moveToLabel }}</template>
|
||||||
|
</MkInput>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<MkButton :disabled="accountAliases.length >= 5" inline style="margin-right: 8px;" @click="add"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
|
||||||
|
<MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</FormSection>
|
</FormSection>
|
||||||
<FormInfo warn>{{ i18n.ts._accountMigration.moveFromDescription }}</FormInfo>
|
<FormInfo warn>{{ i18n.ts._accountMigration.moveFromDescription }}</FormInfo>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, watch } from 'vue';
|
import { ref } from 'vue';
|
||||||
import FormSection from '@/components/form/section.vue';
|
import FormSection from '@/components/form/section.vue';
|
||||||
import FormInfo from '@/components/MkInfo.vue';
|
import FormInfo from '@/components/MkInfo.vue';
|
||||||
import MkInput from '@/components/MkInput.vue';
|
import MkInput from '@/components/MkInput.vue';
|
||||||
|
import MkButton from '@/components/MkButton.vue';
|
||||||
import * as os from '@/os';
|
import * as os from '@/os';
|
||||||
import { i18n } from '@/i18n';
|
import { i18n } from '@/i18n';
|
||||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||||
|
|
||||||
const moveToAccount = ref('');
|
const moveToAccount = ref('');
|
||||||
const accountAlias = ref('');
|
const accountAliases = ref(['']);
|
||||||
|
|
||||||
async function move(): Promise<void> {
|
async function move(): Promise<void> {
|
||||||
const account = moveToAccount.value;
|
const account = moveToAccount.value;
|
||||||
|
@ -44,20 +60,16 @@ async function move(): Promise<void> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function save(): Promise<void> {
|
function add() {
|
||||||
const account = accountAlias.value;
|
accountAliases.value.push('');
|
||||||
os.apiWithDialog('i/known-as', {
|
|
||||||
alsoKnownAs: account,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(accountAlias, async () => {
|
async function save(): Promise<void> {
|
||||||
await save();
|
const alsoKnownAs = accountAliases.value.map(alias => alias.trim()).filter(alias => alias !== '');
|
||||||
});
|
os.apiWithDialog('i/update', {
|
||||||
|
alsoKnownAs,
|
||||||
watch(moveToAccount, async () => {
|
});
|
||||||
await move();
|
}
|
||||||
});
|
|
||||||
|
|
||||||
definePageMetadata({
|
definePageMetadata({
|
||||||
title: i18n.ts.accountMigration,
|
title: i18n.ts.accountMigration,
|
||||||
|
|
|
@ -1357,10 +1357,6 @@ export type Endpoints = {
|
||||||
req: TODO;
|
req: TODO;
|
||||||
res: TODO;
|
res: TODO;
|
||||||
};
|
};
|
||||||
'i/known-as': {
|
|
||||||
req: TODO;
|
|
||||||
res: TODO;
|
|
||||||
};
|
|
||||||
'i/notifications': {
|
'i/notifications': {
|
||||||
req: {
|
req: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
|
@ -1511,6 +1507,7 @@ export type Endpoints = {
|
||||||
mutedWords?: string[][];
|
mutedWords?: string[][];
|
||||||
mutingNotificationTypes?: Notification_2['type'][];
|
mutingNotificationTypes?: Notification_2['type'][];
|
||||||
emailNotificationTypes?: string[];
|
emailNotificationTypes?: string[];
|
||||||
|
alsoKnownAs?: string[];
|
||||||
};
|
};
|
||||||
res: MeDetailed;
|
res: MeDetailed;
|
||||||
};
|
};
|
||||||
|
@ -2348,6 +2345,7 @@ type LiteInstanceMetadata = {
|
||||||
imageUrl: string;
|
imageUrl: string;
|
||||||
}[];
|
}[];
|
||||||
translatorAvailable: boolean;
|
translatorAvailable: boolean;
|
||||||
|
serverRules: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// @public (undocumented)
|
// @public (undocumented)
|
||||||
|
@ -2663,6 +2661,7 @@ type UserDetailed = UserLite & {
|
||||||
lang: string | null;
|
lang: string | null;
|
||||||
lastFetchedAt?: DateString;
|
lastFetchedAt?: DateString;
|
||||||
location: string | null;
|
location: string | null;
|
||||||
|
movedToUri: string;
|
||||||
notesCount: number;
|
notesCount: number;
|
||||||
pinnedNoteIds: ID[];
|
pinnedNoteIds: ID[];
|
||||||
pinnedNotes: Note[];
|
pinnedNotes: Note[];
|
||||||
|
@ -2697,7 +2696,6 @@ type UserLite = {
|
||||||
avatarUrl: string;
|
avatarUrl: string;
|
||||||
avatarBlurhash: string;
|
avatarBlurhash: string;
|
||||||
alsoKnownAs: string[];
|
alsoKnownAs: string[];
|
||||||
movedToUri: any;
|
|
||||||
emojis: {
|
emojis: {
|
||||||
name: string;
|
name: string;
|
||||||
url: string;
|
url: string;
|
||||||
|
|
|
@ -363,7 +363,6 @@ export type Endpoints = {
|
||||||
'i/import-following': { req: TODO; res: TODO; };
|
'i/import-following': { req: TODO; res: TODO; };
|
||||||
'i/import-user-lists': { req: TODO; res: TODO; };
|
'i/import-user-lists': { req: TODO; res: TODO; };
|
||||||
'i/move': { req: TODO; res: TODO; };
|
'i/move': { req: TODO; res: TODO; };
|
||||||
'i/known-as': { req: TODO; res: TODO; };
|
|
||||||
'i/notifications': { req: {
|
'i/notifications': { req: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
sinceId?: Notification['id'];
|
sinceId?: Notification['id'];
|
||||||
|
@ -421,6 +420,7 @@ export type Endpoints = {
|
||||||
mutedWords?: string[][];
|
mutedWords?: string[][];
|
||||||
mutingNotificationTypes?: Notification['type'][];
|
mutingNotificationTypes?: Notification['type'][];
|
||||||
emailNotificationTypes?: string[];
|
emailNotificationTypes?: string[];
|
||||||
|
alsoKnownAs?: string[];
|
||||||
}; res: MeDetailed; };
|
}; res: MeDetailed; };
|
||||||
'i/user-group-invites': { req: TODO; res: TODO; };
|
'i/user-group-invites': { req: TODO; res: TODO; };
|
||||||
'i/2fa/done': { req: TODO; res: TODO; };
|
'i/2fa/done': { req: TODO; res: TODO; };
|
||||||
|
|
Loading…
Reference in a new issue