server: properly handle logical deletion
All checks were successful
ci/woodpecker/push/lint-client Pipeline was successful
ci/woodpecker/push/lint-foundkey-js Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint-backend Pipeline was successful
ci/woodpecker/push/lint-sw Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
All checks were successful
ci/woodpecker/push/lint-client Pipeline was successful
ci/woodpecker/push/lint-foundkey-js Pipeline was successful
ci/woodpecker/push/build Pipeline was successful
ci/woodpecker/push/lint-backend Pipeline was successful
ci/woodpecker/push/lint-sw Pipeline was successful
ci/woodpecker/push/test Pipeline was successful
closes #329
This commit is contained in:
parent
71dfd229b0
commit
b14f3e8cdc
6 changed files with 56 additions and 29 deletions
|
@ -1,3 +1,4 @@
|
|||
import { IsNull, Not } from 'typeorm';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { User } from '@/models/entities/user.js';
|
||||
import { Note } from '@/models/entities/note.js';
|
||||
|
@ -28,8 +29,12 @@ export async function getNote(noteId: Note['id'], me: { id: User['id'] } | null)
|
|||
/**
|
||||
* Get user for API processing
|
||||
*/
|
||||
export async function getUser(userId: User['id']) {
|
||||
const user = await Users.findOneBy({ id: userId });
|
||||
export async function getUser(userId: User['id'], includeSuspended = false) {
|
||||
const user = await Users.findOneBy(
|
||||
id: userId,
|
||||
isDeleted: false,
|
||||
isSuspended: !includeSuspended,
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
throw new ApiError('NO_SUCH_USER');
|
||||
|
@ -41,10 +46,15 @@ export async function getUser(userId: User['id']) {
|
|||
/**
|
||||
* Get remote user for API processing
|
||||
*/
|
||||
export async function getRemoteUser(userId: User['id']) {
|
||||
const user = await getUser(userId);
|
||||
export async function getRemoteUser(userId: User['id'], includeSuspended = false) {
|
||||
const user = await Users.findOneBy(
|
||||
id: userId,
|
||||
host: Not(IsNull()),
|
||||
isDeleted: false,
|
||||
isSuspended: !includedSuspended,
|
||||
});
|
||||
|
||||
if (!Users.isRemoteUser(user)) {
|
||||
if (user == null) {
|
||||
throw new ApiError('NO_SUCH_USER');
|
||||
}
|
||||
|
||||
|
@ -54,10 +64,15 @@ export async function getRemoteUser(userId: User['id']) {
|
|||
/**
|
||||
* Get local user for API processing
|
||||
*/
|
||||
export async function getLocalUser(userId: User['id']) {
|
||||
const user = await getUser(userId);
|
||||
export async function getLocalUser(userId: User['id'], includeSuspended = false) {
|
||||
const user = await Users.findOneBy(
|
||||
id: userId,
|
||||
host: IsNull(),
|
||||
isDeleted: false,
|
||||
isSuspended: !includeSuspended,
|
||||
});
|
||||
|
||||
if (!Users.isLocalUser(user)) {
|
||||
if (user == null) {
|
||||
throw new ApiError('NO_SUCH_USER');
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Users } from '@/models/index.js';
|
|||
import { ApiError } from '@/server/api/error.js';
|
||||
import { deleteAccount } from '@/services/delete-account.js';
|
||||
import define from '@/server/api/define.js';
|
||||
import { getUser } from '@/server/api/common/getters.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
@ -22,14 +23,9 @@ export const paramDef = {
|
|||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default define(meta, paramDef, async (ps) => {
|
||||
const user = await Users.findOneBy({
|
||||
id: ps.userId,
|
||||
isDeleted: false,
|
||||
});
|
||||
const user = await getUser(ps.userId, true);
|
||||
|
||||
if (user == null) {
|
||||
throw new ApiError('NO_SUCH_USER');
|
||||
} else if (user.isAdmin) {
|
||||
if (user.isAdmin) {
|
||||
throw new ApiError('IS_ADMIN');
|
||||
} else if (user.isModerator) {
|
||||
throw new ApiError('IS_MODERATOR');
|
||||
|
|
|
@ -47,6 +47,7 @@ export default async (ctx: Koa.Context) => {
|
|||
const user = await Users.findOneBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: IsNull(),
|
||||
isDeleted: false,
|
||||
}) as ILocalUser;
|
||||
|
||||
if (user == null) {
|
||||
|
|
|
@ -223,6 +223,7 @@ const getFeed = async (acct: string) => {
|
|||
usernameLower: username.toLowerCase(),
|
||||
host: host ?? IsNull(),
|
||||
isSuspended: false,
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
return user && await packFeed(user);
|
||||
|
@ -272,6 +273,7 @@ router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => {
|
|||
usernameLower: username.toLowerCase(),
|
||||
host: host ?? IsNull(),
|
||||
isSuspended: false,
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
if (user != null) {
|
||||
|
@ -304,6 +306,7 @@ router.get('/users/:user', async ctx => {
|
|||
id: ctx.params.user,
|
||||
host: IsNull(),
|
||||
isSuspended: false,
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
if (user == null) {
|
||||
|
@ -419,6 +422,8 @@ router.get('/@:user/pages/:page', async (ctx, next) => {
|
|||
const user = await Users.findOneBy({
|
||||
usernameLower: username.toLowerCase(),
|
||||
host: host ?? IsNull(),
|
||||
isSuspended: false,
|
||||
isDeleted: false,
|
||||
});
|
||||
|
||||
if (user == null) return;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Users } from '@/models/index.js';
|
||||
import { AccessTokens, Users } from '@/models/index.js';
|
||||
import { createDeleteAccountJob } from '@/queue/index.js';
|
||||
import { publishUserEvent } from './stream.js';
|
||||
import { doPostSuspend } from './suspend-user.js';
|
||||
|
@ -7,9 +7,15 @@ export async function deleteAccount(user: {
|
|||
id: string;
|
||||
host: string | null;
|
||||
}): Promise<void> {
|
||||
await Users.update(user.id, {
|
||||
isDeleted: true,
|
||||
});
|
||||
await Promise.all([
|
||||
Users.update(user.id, {
|
||||
isDeleted: true,
|
||||
}),
|
||||
// revoke all of the users access tokens to block API access
|
||||
AccessTokens.delete({
|
||||
userId: user.id,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (Users.isLocalUser(user)) {
|
||||
// Terminate streaming
|
||||
|
|
|
@ -6,15 +6,15 @@ import { subscriber } from '@/db/redis.js';
|
|||
|
||||
export const userByIdCache = new Cache<User>(
|
||||
Infinity,
|
||||
async (id) => await Users.findOneBy({ id }) ?? undefined,
|
||||
async (id) => await Users.findOneBy({ id, isDeleted: false }) ?? undefined,
|
||||
);
|
||||
export const localUserByNativeTokenCache = new Cache<CacheableLocalUser>(
|
||||
Infinity,
|
||||
async (token) => await Users.findOneBy({ token, host: IsNull() }) as ILocalUser | null ?? undefined,
|
||||
async (token) => await Users.findOneBy({ token, host: IsNull(), isDeleted: false }) as ILocalUser | null ?? undefined,
|
||||
);
|
||||
export const uriPersonCache = new Cache<User>(
|
||||
Infinity,
|
||||
async (uri) => await Users.findOneBy({ uri }) ?? undefined,
|
||||
async (uri) => await Users.findOneBy({ uri, isDeleted: false }) ?? undefined,
|
||||
);
|
||||
|
||||
subscriber.on('message', async (_, data) => {
|
||||
|
@ -28,14 +28,18 @@ subscriber.on('message', async (_, data) => {
|
|||
case 'userChangeModeratorState':
|
||||
case 'remoteUserUpdated': {
|
||||
const user = await Users.findOneByOrFail({ id: body.id });
|
||||
userByIdCache.set(user.id, user);
|
||||
for (const [k, v] of uriPersonCache.cache.entries()) {
|
||||
if (v.value.id === user.id) {
|
||||
uriPersonCache.set(k, user);
|
||||
if (user.isDeleted) {
|
||||
userByIdCache.delete(user.id);
|
||||
uriPersonCache.delete(user.uri);
|
||||
if (Users.isLocalUser(user)) {
|
||||
localUserByNativeTokenCache.delete(user.token);
|
||||
}
|
||||
} else {
|
||||
userByIdCache.set(user.id, user);
|
||||
uriPersonCache.set(user.uri, user);
|
||||
if (Users.isLocalUser(user)) {
|
||||
localUserByNativeTokenCache.set(user.token, user);
|
||||
}
|
||||
}
|
||||
if (Users.isLocalUser(user)) {
|
||||
localUserByNativeTokenCache.set(user.token, user);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue