Files
fluxer/fluxer_api/src/test/TestHarnessController.ts
Hampus Kraft 2f557eda8c initial commit
2026-01-01 21:05:54 +00:00

2209 lines
64 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import type {Context} from 'hono';
import {Redis} from 'ioredis';
import type {HonoApp, HonoEnv} from '~/App';
import {AttachmentDecayRepository} from '~/attachment/AttachmentDecayRepository';
import {AttachmentDecayService} from '~/attachment/AttachmentDecayService';
import type {IpAuthorizationTicketCache} from '~/auth/services/AuthLoginService';
import {
createAttachmentID,
createChannelID,
createGuildID,
createMessageID,
createUserID,
type MessageID,
type UserID,
} from '~/BrandedTypes';
import {Config} from '~/Config';
import {ChannelTypes, SuspiciousActivityFlags, UserFlags} from '~/Constants';
import {mapMessageToResponse} from '~/channel/ChannelModel';
import {ChannelRepository} from '~/channel/ChannelRepository';
import {BatchBuilder, defineTable, deleteOneOrMany, fetchMany, fetchOne} from '~/database/Cassandra';
import type {
ChannelEmptyBucketRow,
ChannelMessageBucketRow,
ChannelRow,
ChannelStateRow,
MessageRow,
} from '~/database/CassandraTypes';
import {
CHANNEL_EMPTY_BUCKET_COLUMNS,
CHANNEL_MESSAGE_BUCKET_COLUMNS,
CHANNEL_STATE_COLUMNS,
MESSAGE_COLUMNS,
} from '~/database/CassandraTypes';
import {
AclsMustBeNonEmptyError,
DeletionFailedError,
EmailServiceNotTestableError,
InputValidationError,
InvalidAclsFormatError,
InvalidBotFlagError,
InvalidFlagsFormatError,
InvalidSuspiciousFlagsFormatError,
InvalidSystemFlagError,
InvalidTimestampError,
NoPendingDeletionError,
ProcessingFailedError,
TestHarnessDisabledError,
TestHarnessForbiddenError,
UnknownGuildError,
UnknownGuildMemberError,
UnknownHarvestError,
UnknownMessageError,
UnknownSuspiciousFlagError,
UnknownUserError,
UnknownUserFlagError,
UpdateFailedError,
} from '~/Errors';
import {GuildRepository} from '~/guild/repositories/GuildRepository';
import type {IEmailService, ITestEmailService, SentEmailRecord} from '~/infrastructure/IEmailService';
import {RedisAccountDeletionQueueService} from '~/infrastructure/RedisAccountDeletionQueueService';
import {RedisActivityTracker} from '~/infrastructure/RedisActivityTracker';
import {SnowflakeService} from '~/infrastructure/SnowflakeService';
import {StorageService} from '~/infrastructure/StorageService';
import {Logger} from '~/Logger';
import {OAuth2AccessTokensByUser} from '~/Tables';
import {AuthSessionRepository} from '~/user/repositories/auth/AuthSessionRepository';
import {IpAuthorizationTokens} from '~/user/repositories/auth/IpAuthorizationRepository';
import {ScheduledMessageRepository} from '~/user/repositories/ScheduledMessageRepository';
import {UserChannelRepository} from '~/user/repositories/UserChannelRepository';
import {processUserDeletion} from '~/user/services/UserDeletionService';
import {UserHarvestRepository} from '~/user/UserHarvestRepository';
import {UserRepository} from '~/user/UserRepository';
import {getExpiryBucket} from '~/utils/AttachmentDecay';
import * as BucketUtils from '~/utils/BucketUtils';
import * as SnowflakeUtils from '~/utils/SnowflakeUtils';
import {ScheduledMessageExecutor} from '~/worker/executors/ScheduledMessageExecutor';
import {processExpiredAttachments} from '~/worker/tasks/expireAttachments';
import {processInactivityDeletionsCore} from '~/worker/tasks/processInactivityDeletions';
import {setWorkerDependencies} from '~/worker/WorkerContext';
import {initializeWorkerDependencies} from '~/worker/WorkerDependencies';
const TEST_EMAIL_ENDPOINT = '/test/emails';
const TEST_AUTH_HEADER = 'x-test-token';
const MAX_TEST_PRIVATE_CHANNELS = 1000;
const Messages = defineTable<MessageRow, 'channel_id' | 'bucket' | 'message_id'>({
name: 'messages',
columns: MESSAGE_COLUMNS,
primaryKey: ['channel_id', 'bucket', 'message_id'],
partitionKey: ['channel_id', 'bucket'],
});
const ChannelState = defineTable<ChannelStateRow, 'channel_id'>({
name: 'channel_state',
columns: CHANNEL_STATE_COLUMNS,
primaryKey: ['channel_id'],
});
const ChannelMessageBuckets = defineTable<ChannelMessageBucketRow, 'channel_id' | 'bucket', 'channel_id'>({
name: 'channel_message_buckets',
columns: CHANNEL_MESSAGE_BUCKET_COLUMNS,
primaryKey: ['channel_id', 'bucket'],
partitionKey: ['channel_id'],
});
const ChannelEmptyBuckets = defineTable<ChannelEmptyBucketRow, 'channel_id' | 'bucket', 'channel_id'>({
name: 'channel_empty_buckets',
columns: CHANNEL_EMPTY_BUCKET_COLUMNS,
primaryKey: ['channel_id', 'bucket'],
partitionKey: ['channel_id'],
});
const FETCH_CHANNEL_STATE = ChannelState.select({
where: ChannelState.where.eq('channel_id'),
limit: 1,
});
const FETCH_CHANNEL_BUCKETS = ChannelMessageBuckets.select({
columns: ['bucket', 'updated_at'],
where: ChannelMessageBuckets.where.eq('channel_id'),
orderBy: {col: 'bucket', direction: 'DESC'},
});
const FETCH_CHANNEL_EMPTY_BUCKETS = ChannelEmptyBuckets.select({
columns: ['bucket', 'updated_at'],
where: ChannelEmptyBuckets.where.eq('channel_id'),
orderBy: {col: 'bucket', direction: 'DESC'},
});
const ensureHarnessAccess = (ctx: Context<HonoEnv>) => {
if (!Config.dev.testModeEnabled) {
throw new TestHarnessDisabledError();
}
if (Config.dev.testHarnessToken) {
const headerValue = ctx.req.header(TEST_AUTH_HEADER) || ctx.req.header('authorization') || '';
const bearer = headerValue.startsWith('Bearer ') ? headerValue.slice('Bearer '.length) : headerValue;
if (bearer !== Config.dev.testHarnessToken) {
throw new TestHarnessForbiddenError();
}
}
};
const isTestEmailService = (service: IEmailService): service is ITestEmailService => {
return typeof (service as ITestEmailService).listSentEmails === 'function';
};
const serializeEmails = (emails: Array<SentEmailRecord>) =>
emails.map((email) => ({
...email,
timestamp: email.timestamp.toISOString(),
}));
const initializeWorkerDepsWithHarnessEmail = async (ctx: Context<HonoEnv>, snowflakeService: SnowflakeService) => {
const workerDeps = await initializeWorkerDependencies(snowflakeService);
const harnessEmailService = ctx.get('emailService');
if (isTestEmailService(harnessEmailService) && harnessEmailService !== workerDeps.emailService) {
workerDeps.emailService = harnessEmailService;
}
return workerDeps;
};
const userFlagEntries = Object.entries(UserFlags);
const suspiciousFlagEntries = Object.entries(SuspiciousActivityFlags);
const parseUserFlagNames = (names: Array<string>): bigint | null => {
let mask = 0n;
for (const name of names) {
const entry = userFlagEntries.find(([flagName]) => flagName === name);
if (!entry) {
return null;
}
mask |= entry[1];
}
return mask;
};
const parseSuspiciousFlagNames = (names: Array<string>): number | null => {
let mask = 0;
for (const name of names) {
const entry = suspiciousFlagEntries.find(([flagName]) => flagName === name);
if (!entry) {
return null;
}
mask |= entry[1];
}
return mask;
};
export const TestHarnessController = (app: HonoApp) => {
app.get(TEST_EMAIL_ENDPOINT, (ctx) => {
ensureHarnessAccess(ctx);
const emailService = ctx.get('emailService');
if (!isTestEmailService(emailService)) {
throw new EmailServiceNotTestableError();
}
const emails = emailService.listSentEmails();
const filter = ctx.req.query('recipient');
let filtered = emails;
if (filter) {
filtered = emails.filter((email) => email.to === filter);
}
return ctx.json({emails: serializeEmails(filtered), count: filtered.length});
});
app.delete(TEST_EMAIL_ENDPOINT, (ctx) => {
ensureHarnessAccess(ctx);
const emailService = ctx.get('emailService');
if (!isTestEmailService(emailService)) {
throw new EmailServiceNotTestableError();
}
emailService.clearSentEmails();
return ctx.json({cleared: true});
});
app.patch('/test/users/:userId/flags', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {flags} = body;
if (typeof flags !== 'string' && typeof flags !== 'number') {
throw new InvalidFlagsFormatError();
}
const userRepository = ctx.get('userRepository');
await userRepository.patchUpsert(userId, {flags: BigInt(flags)});
return ctx.json({success: true});
});
app.post('/test/users/:userId/acls', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {acls} = body;
if (acls !== null && !Array.isArray(acls)) {
throw new InvalidAclsFormatError();
}
let normalized: Set<string> | null = null;
if (Array.isArray(acls)) {
normalized = new Set<string>();
for (const acl of acls) {
if (typeof acl !== 'string' || !acl.trim()) {
throw new AclsMustBeNonEmptyError();
}
normalized.add(acl);
}
}
const userRepository = ctx.get('userRepository');
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
await userRepository.patchUpsert(userId, {acls: normalized});
const userCacheService = ctx.get('userCacheService');
await userCacheService.invalidateUserCache(userId);
return ctx.json({success: true, count: normalized?.size ?? 0});
});
app.post('/test/users/:userId/security-flags', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {
set_flags: setFlags,
clear_flags: clearFlags,
suspicious_activity_flags: suspiciousFlagsValue,
suspicious_activity_flag_names: suspiciousFlagNames,
pending_manual_verification: pendingManualVerification,
} = body as {
set_flags?: Array<string>;
clear_flags?: Array<string>;
suspicious_activity_flags?: number | null;
suspicious_activity_flag_names?: Array<string>;
pending_manual_verification?: boolean | null;
};
const userRepository = ctx.get('userRepository');
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
let nextFlags = user.flags ?? 0n;
const pendingUpdates: Record<string, unknown> = {};
const applyFlagList = (names: Array<string> | undefined, op: 'set' | 'clear') => {
if (!names) return true;
const mask = parseUserFlagNames(names);
if (mask === null) {
return false;
}
nextFlags = op === 'set' ? nextFlags | mask : nextFlags & ~mask;
return true;
};
if (!applyFlagList(setFlags, 'set') || !applyFlagList(clearFlags, 'clear')) {
throw new UnknownUserFlagError();
}
if (typeof pendingManualVerification === 'boolean') {
nextFlags = pendingManualVerification
? nextFlags | UserFlags.PENDING_MANUAL_VERIFICATION
: nextFlags & ~UserFlags.PENDING_MANUAL_VERIFICATION;
}
if (setFlags || clearFlags || typeof pendingManualVerification === 'boolean') {
pendingUpdates.flags = nextFlags;
}
let suspiciousValue: number | null | undefined = suspiciousFlagsValue;
if (Array.isArray(suspiciousFlagNames)) {
const parsed = parseSuspiciousFlagNames(suspiciousFlagNames);
if (parsed === null) {
throw new UnknownSuspiciousFlagError();
}
suspiciousValue = parsed;
}
if (suspiciousValue !== undefined) {
if (suspiciousValue !== null && typeof suspiciousValue !== 'number') {
throw new InvalidSuspiciousFlagsFormatError();
}
pendingUpdates.suspicious_activity_flags = suspiciousValue;
}
if (Object.keys(pendingUpdates).length === 0) {
return ctx.json({success: true, updated: false});
}
await userRepository.patchUpsert(userId, pendingUpdates);
return ctx.json({
success: true,
updated: true,
flags: (pendingUpdates.flags as bigint | undefined)?.toString() ?? user.flags?.toString(),
suspicious_activity_flags: pendingUpdates.suspicious_activity_flags ?? user.suspiciousActivityFlags ?? null,
});
});
app.post('/test/users/:userId/unclaim', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const userRepository = ctx.get('userRepository');
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
await userRepository.patchUpsert(userId, {
email: null,
email_verified: false,
email_bounced: false,
password_hash: null,
password_last_changed_at: null,
authenticator_types: null,
totp_secret: null,
});
return ctx.json({success: true});
});
app.post('/test/users/:userId/set-pending-deletion', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {pending_deletion_at: pendingDeletionAt, set_self_deleted_flag: setSelfDeletedFlag} = body as {
pending_deletion_at?: string;
set_self_deleted_flag?: boolean;
};
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
let date: Date;
try {
date = pendingDeletionAt ? new Date(pendingDeletionAt) : new Date();
if (Number.isNaN(date.getTime())) {
throw new InvalidTimestampError();
}
} catch {
throw new InvalidTimestampError();
}
const updates: Record<string, unknown> = {pending_deletion_at: date};
const shouldSetSelfDeleted = setSelfDeletedFlag !== false;
if (shouldSetSelfDeleted) {
const nextFlags = (user.flags ?? 0n) | UserFlags.SELF_DELETED;
updates.flags = nextFlags;
}
const updated = await userRepository.patchUpsert(userId, updates);
const redis = new Redis(Config.redis.url);
const redisDeletionQueue = new RedisAccountDeletionQueueService(redis, userRepository);
try {
await redisDeletionQueue.scheduleDeletion(userId, date, user.deletionReasonCode ?? 0);
} finally {
await redis.quit();
}
return ctx.json(
{
success: true,
pending_deletion_at: date.toISOString(),
flags:
(updates.flags as bigint | undefined)?.toString() ?? updated?.flags?.toString() ?? user.flags?.toString(),
},
200,
);
});
app.post('/test/auth/ip-authorization', async (ctx) => {
ensureHarnessAccess(ctx);
const cacheService = ctx.get('cacheService');
const body = await ctx.req.json();
const {
ticket,
token,
user_id: userId,
email,
username,
client_ip: clientIp,
user_agent: userAgent,
client_location: clientLocation,
platform,
resend_used: resendUsed,
invite_code: inviteCode,
created_at: createdAtInput,
ttl_seconds: ttlSeconds,
} = body as Record<string, unknown>;
if (!ticket || !token || !userId || !email || !username || !clientIp || !userAgent || !clientLocation) {
return ctx.json({error: 'missing required fields'}, 400);
}
let createdAt = Date.now();
if (typeof createdAtInput === 'number') {
createdAt = createdAtInput;
} else if (typeof createdAtInput === 'string') {
const parsed = new Date(createdAtInput);
if (!Number.isNaN(parsed.getTime())) {
createdAt = parsed.getTime();
}
}
const payload: IpAuthorizationTicketCache = {
userId: String(userId),
email: String(email),
username: String(username),
clientIp: String(clientIp),
userAgent: String(userAgent),
platform: platform ? String(platform) : null,
authToken: String(token),
clientLocation: String(clientLocation),
inviteCode: inviteCode ? String(inviteCode) : null,
resendUsed: Boolean(resendUsed),
createdAt,
};
const ttl = typeof ttlSeconds === 'number' && ttlSeconds > 0 ? ttlSeconds : 60 * 15;
await cacheService.set(`ip-auth-ticket:${ticket}`, payload, ttl);
await cacheService.set(`ip-auth-token:${token}`, {ticket: String(ticket)}, ttl);
return ctx.json(
{
success: true,
ticket: String(ticket),
token: String(token),
ttl_seconds: ttl,
},
200,
);
});
app.post('/test/auth/ip-authorization/publish', async (ctx) => {
ensureHarnessAccess(ctx);
const cacheService = ctx.get('cacheService');
const body = await ctx.req.json();
const {ticket, token, user_id: userId} = body as {ticket?: string; token?: string; user_id?: string};
if (!ticket || !token || !userId) {
return ctx.json({error: 'ticket, token, and user_id are required'}, 400);
}
const payload = JSON.stringify({token, user_id: userId});
await cacheService.publish(`ip-auth:${ticket}`, payload);
return ctx.json({success: true});
});
app.post('/test/auth/ip-authorization/expire', async (ctx) => {
ensureHarnessAccess(ctx);
const cacheService = ctx.get('cacheService');
const body = await ctx.req.json();
const {ticket, token} = body as {ticket?: string; token?: string};
if (!ticket && !token) {
return ctx.json({error: 'ticket or token is required'}, 400);
}
if (ticket) {
await cacheService.delete(`ip-auth-ticket:${ticket}`);
await cacheService.delete(`ip-auth:${ticket}`);
}
if (token) {
await cacheService.delete(`ip-auth-token:${token}`);
await deleteOneOrMany(
IpAuthorizationTokens.deleteCql({
where: IpAuthorizationTokens.where.eq('token_'),
}),
{token_: token},
);
}
return ctx.json({success: true}, 200);
});
app.post('/test/auth/mfa-ticket', async (ctx) => {
ensureHarnessAccess(ctx);
const cacheService = ctx.get('cacheService');
const userRepository = new UserRepository();
const body = await ctx.req.json();
const {
ticket,
user_id: userIdInput,
ttl_seconds: ttlSeconds,
} = body as {
ticket?: string;
user_id?: string;
ttl_seconds?: number;
};
if (!ticket || !userIdInput) {
return ctx.json({error: 'ticket and user_id are required'}, 400);
}
const userId = createUserID(BigInt(userIdInput));
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
const ttl = typeof ttlSeconds === 'number' && ttlSeconds > 0 ? ttlSeconds : 60 * 5;
await cacheService.set(`mfa-ticket:${ticket}`, userId.toString(), ttl);
return ctx.json(
{
success: true,
ticket,
user_id: userId.toString(),
ttl_seconds: ttl,
},
200,
);
});
app.post('/test/users/:userId/premium', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {premium_type: premiumType, premium_until: premiumUntil} = body as {
premium_type?: number | null;
premium_until?: string | null;
};
const userRepository = ctx.get('userRepository');
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
const updates: Record<string, unknown> = {};
if (premiumType !== undefined) {
updates.premium_type = premiumType;
}
if (premiumUntil !== undefined) {
updates.premium_until = premiumUntil ? new Date(premiumUntil) : null;
}
if (premiumType !== null && premiumType !== undefined && premiumType > 0) {
if (!user.premiumSince) {
updates.premium_since = new Date();
}
}
if (Object.keys(updates).length === 0) {
return ctx.json({success: true, updated: false});
}
await userRepository.patchUpsert(userId, updates);
return ctx.json({
success: true,
updated: true,
premium_type: updates.premium_type ?? user.premiumType,
premium_until: updates.premium_until
? (updates.premium_until as Date).toISOString()
: (user.premiumUntil?.toISOString() ?? null),
});
});
app.post('/test/users/:userId/private-channels', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = (await ctx.req.json()) as Record<string, unknown>;
const parseCount = (value: unknown, name: string): number => {
if (value === undefined || value === null) {
return 0;
}
if (typeof value !== 'number' || !Number.isInteger(value) || value < 0 || value > MAX_TEST_PRIVATE_CHANNELS) {
throw InputValidationError.create(
name,
`The value for ${name} must be an integer between 0 and ${MAX_TEST_PRIVATE_CHANNELS}.`,
);
}
return value;
};
const dmCount = parseCount(body.dm_count, 'dm_count');
const groupDmCount = parseCount(body.group_dm_count, 'group_dm_count');
const recipientsInput = Array.isArray(body.recipients) ? body.recipients : [];
if ((dmCount > 0 || groupDmCount > 0) && recipientsInput.length === 0) {
throw InputValidationError.create('recipients', 'At least one recipient is required to seed private channels.');
}
const recipients = recipientsInput.map((raw) => {
if (typeof raw !== 'string') {
throw InputValidationError.create('recipients', 'Recipient IDs must be strings.');
}
const trimmed = raw.trim();
if (trimmed === '') {
throw InputValidationError.create('recipients', 'Recipient IDs cannot be empty.');
}
try {
return createUserID(BigInt(trimmed));
} catch {
throw InputValidationError.create('recipients', 'Recipient IDs must be valid snowflakes.');
}
});
const clearExisting = Boolean(body.clear_existing);
const channelRepository = new ChannelRepository();
const userRepository = ctx.get('userRepository');
const snowflakeService = ctx.get('snowflakeService');
if (clearExisting) {
await userRepository.deleteAllPrivateChannels(userId);
}
const dmChannels: Array<{channel_id: string; last_message_id: string}> = [];
for (let i = 0; i < dmCount; i++) {
const recipientId = recipients[i % recipients.length];
const channelId = createChannelID(snowflakeService.generate());
const lastMessageId = createMessageID(snowflakeService.generate());
const channelRow: ChannelRow = {
channel_id: channelId,
guild_id: null,
type: ChannelTypes.DM,
name: null,
topic: null,
icon_hash: null,
url: null,
parent_id: null,
position: 0,
owner_id: null,
recipient_ids: new Set([userId, recipientId]),
nsfw: null,
rate_limit_per_user: null,
bitrate: null,
user_limit: null,
rtc_region: null,
last_message_id: lastMessageId,
last_pin_timestamp: null,
permission_overwrites: null,
nicks: null,
soft_deleted: false,
indexed_at: null,
version: 1,
};
await channelRepository.upsert(channelRow);
await userRepository.openDmForUser(userId, channelId);
dmChannels.push({
channel_id: channelId.toString(),
last_message_id: lastMessageId.toString(),
});
}
const groupDmChannels: Array<{channel_id: string; last_message_id: string}> = [];
if (groupDmCount > 0) {
const recipientsSet = new Set<UserID>([userId, ...recipients]);
for (let i = 0; i < groupDmCount; i++) {
const channelId = createChannelID(snowflakeService.generate());
const lastMessageId = createMessageID(snowflakeService.generate());
const channelRow: ChannelRow = {
channel_id: channelId,
guild_id: null,
type: ChannelTypes.GROUP_DM,
name: null,
topic: null,
icon_hash: null,
url: null,
parent_id: null,
position: 0,
owner_id: userId,
recipient_ids: new Set(recipientsSet),
nsfw: false,
rate_limit_per_user: 0,
bitrate: null,
user_limit: null,
rtc_region: null,
last_message_id: lastMessageId,
last_pin_timestamp: null,
permission_overwrites: null,
nicks: null,
soft_deleted: false,
indexed_at: null,
version: 1,
};
await channelRepository.upsert(channelRow);
await userRepository.openDmForUser(userId, channelId);
groupDmChannels.push({
channel_id: channelId.toString(),
last_message_id: lastMessageId.toString(),
});
}
}
return ctx.json({
dms: dmChannels,
group_dms: groupDmChannels,
});
});
app.post('/test/guilds/:guildId/features', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const body = await ctx.req.json();
const {add_features: addFeatures, remove_features: removeFeatures} = body as {
add_features?: Array<string>;
remove_features?: Array<string>;
};
const guildRepository = new GuildRepository();
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
const newFeatures = new Set(guild.features);
if (Array.isArray(addFeatures)) {
for (const feature of addFeatures) {
if (typeof feature === 'string' && feature.trim()) {
newFeatures.add(feature);
}
}
}
if (Array.isArray(removeFeatures)) {
for (const feature of removeFeatures) {
if (typeof feature === 'string') {
newFeatures.delete(feature);
}
}
}
const guildRow = guild.toRow();
await guildRepository.upsert({
...guildRow,
features: newFeatures,
});
return ctx.json({
success: true,
features: Array.from(newFeatures),
});
});
app.get('/test/verify-asset/user/:userId/avatar', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
if (!user.avatarHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'User has no avatar set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = user.avatarHash.startsWith('a_') ? user.avatarHash.slice(2) : user.avatarHash;
const s3Key = `avatars/${userId}/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: user.avatarHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: user.avatarHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/user/:userId/banner', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
if (!user.bannerHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'User has no banner set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = user.bannerHash.startsWith('a_') ? user.bannerHash.slice(2) : user.bannerHash;
const s3Key = `banners/${userId}/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: user.bannerHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: user.bannerHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/guild/:guildId/icon', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const guildRepository = new GuildRepository();
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
if (!guild.iconHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'Guild has no icon set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = guild.iconHash.startsWith('a_') ? guild.iconHash.slice(2) : guild.iconHash;
const s3Key = `icons/${guildId}/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: guild.iconHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: guild.iconHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/guild/:guildId/banner', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const guildRepository = new GuildRepository();
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
if (!guild.bannerHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'Guild has no banner set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = guild.bannerHash.startsWith('a_') ? guild.bannerHash.slice(2) : guild.bannerHash;
const s3Key = `banners/${guildId}/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: guild.bannerHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: guild.bannerHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/guild/:guildId/splash', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const guildRepository = new GuildRepository();
const guild = await guildRepository.findUnique(guildId);
if (!guild) {
throw new UnknownGuildError();
}
if (!guild.splashHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'Guild has no splash set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = guild.splashHash.startsWith('a_') ? guild.splashHash.slice(2) : guild.splashHash;
const s3Key = `splashes/${guildId}/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: guild.splashHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: guild.splashHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/guild/:guildId/member/:userId/avatar', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const userId = createUserID(BigInt(ctx.req.param('userId')));
const guildRepository = new GuildRepository();
const member = await guildRepository.getMember(guildId, userId);
if (!member) {
throw new UnknownGuildMemberError();
}
if (!member.avatarHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'Member has no guild avatar set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = member.avatarHash.startsWith('a_') ? member.avatarHash.slice(2) : member.avatarHash;
const s3Key = `guilds/${guildId}/users/${userId}/avatars/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: member.avatarHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: member.avatarHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/verify-asset/guild/:guildId/member/:userId/banner', async (ctx) => {
ensureHarnessAccess(ctx);
const guildId = createGuildID(BigInt(ctx.req.param('guildId')));
const userId = createUserID(BigInt(ctx.req.param('userId')));
const guildRepository = new GuildRepository();
const member = await guildRepository.getMember(guildId, userId);
if (!member) {
throw new UnknownGuildMemberError();
}
if (!member.bannerHash) {
return ctx.json({
hash: null,
existsInS3: null,
message: 'Member has no guild banner set',
});
}
const storageService = new StorageService();
const hashWithoutPrefix = member.bannerHash.startsWith('a_') ? member.bannerHash.slice(2) : member.bannerHash;
const s3Key = `guilds/${guildId}/users/${userId}/banners/${hashWithoutPrefix}`;
try {
const metadata = await storageService.getObjectMetadata(Config.s3.buckets.cdn, s3Key);
return ctx.json({
hash: member.bannerHash,
s3Key,
existsInS3: metadata !== null,
metadata,
});
} catch (error) {
return ctx.json({
hash: member.bannerHash,
s3Key,
existsInS3: false,
error: error instanceof Error ? error.message : 'unknown error',
});
}
});
app.get('/test/users/:userId/data-exists', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
console.log('[test/users/:userId/data-exists] Request received', {userId: userId.toString()});
const userRepository = new UserRepository();
const userChannelRepository = new UserChannelRepository();
const authSessionRepository = new AuthSessionRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
console.log('[test/users/:userId/data-exists] User not found', {userId: userId.toString()});
return ctx.json({user_exists: false}, 200);
}
console.log('[test/users/:userId/data-exists] User found', {
userId: userId.toString(),
flags: user.flags?.toString() ?? 'null',
pendingDeletionAt: user.pendingDeletionAt?.toISOString() ?? 'null',
});
const [relationships, sessions, oauthAccessTokens, pinnedDms, savedMessages] = await Promise.all([
userRepository.listRelationships(userId),
authSessionRepository.listAuthSessions(userId),
fetchMany<{token_: string}>(
OAuth2AccessTokensByUser.selectCql({
columns: ['token_'],
where: OAuth2AccessTokensByUser.where.eq('user_id'),
}),
{user_id: userId},
),
userChannelRepository.getPinnedDms(userId),
userRepository.listSavedMessages(userId),
]);
const hasSelfDeletedFlag = user.flags ? (user.flags & UserFlags.SELF_DELETED) !== 0n : false;
const hasDeletedFlag = user.flags ? (user.flags & UserFlags.DELETED) !== 0n : false;
console.log('[test/users/:userId/data-exists] Flag check results', {
userId: userId.toString(),
rawFlags: user.flags?.toString() ?? 'null',
SELF_DELETED_FLAG_VALUE: UserFlags.SELF_DELETED.toString(),
DELETED_FLAG_VALUE: UserFlags.DELETED.toString(),
hasSelfDeletedFlag,
hasDeletedFlag,
});
const response = {
user_exists: true,
email_cleared: user.email === null,
phone_cleared: user.phone === null,
password_cleared: user.passwordHash === null,
flags: user.flags?.toString(),
has_deleted_flag: hasDeletedFlag,
has_self_deleted_flag: hasSelfDeletedFlag,
pending_deletion_at: user.pendingDeletionAt ? user.pendingDeletionAt.toISOString() : null,
relationships_count: relationships.length,
sessions_count: sessions.length,
oauth_tokens_count: oauthAccessTokens.length,
pinned_dms_count: pinnedDms.length,
saved_messages_count: savedMessages.length,
};
console.log('[test/users/:userId/data-exists] Returning response', {userId: userId.toString(), response});
return ctx.json(response, 200);
});
app.get('/test/users/:userId/relationships', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const userRepository = new UserRepository();
const relationships = await userRepository.listRelationships(userId);
return ctx.json({relationships}, 200);
});
app.get('/test/users/:userId/sessions', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const authSessionRepository = new AuthSessionRepository();
const sessions = await authSessionRepository.listAuthSessions(userId);
return ctx.json({sessions, count: sessions.length}, 200);
});
app.get('/test/users/:userId/messages/count', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const channelRepository = new ChannelRepository();
const messages = await channelRepository.listMessagesByAuthor(userId);
return ctx.json({count: messages.length}, 200);
});
app.post('/test/worker/process-pending-deletions', async (ctx) => {
ensureHarnessAccess(ctx);
console.log('[test/worker/process-pending-deletions] Request received');
const userRepository = new UserRepository();
const redis = new Redis(Config.redis.url);
const redisDeletionQueue = new RedisAccountDeletionQueueService(redis, userRepository);
const snowflakeService = new SnowflakeService(redis);
await snowflakeService.initialize();
const workerDeps = await initializeWorkerDepsWithHarnessEmail(ctx, snowflakeService);
const now = Date.now();
console.log('[test/worker/process-pending-deletions] Looking for ready deletions', {
now: new Date(now).toISOString(),
});
const readyDeletions = await redisDeletionQueue.getReadyDeletions(now, 100);
console.log('[test/worker/process-pending-deletions] Found ready deletions from Redis', {
count: readyDeletions.length,
});
let processed = 0;
const errors: Array<{userId: string; error: string}> = [];
for (const deletion of readyDeletions) {
try {
const userId = createUserID(deletion.userId);
console.log('[test/worker/process-pending-deletions] Processing deletion', {
userId: userId.toString(),
deletionReasonCode: deletion.deletionReasonCode,
});
const user = await userRepository.findUnique(userId);
if (!user || !user.pendingDeletionAt) {
console.log('[test/worker/process-pending-deletions] User not found or no pending deletion', {
userId: userId.toString(),
userFound: !!user,
hasPendingDeletion: !!user?.pendingDeletionAt,
});
continue;
}
const pendingDeletionAt = user.pendingDeletionAt;
await processUserDeletion(userId, deletion.deletionReasonCode, workerDeps);
await userRepository.removePendingDeletion(userId, pendingDeletionAt);
await redisDeletionQueue.removeFromQueue(userId);
console.log('[test/worker/process-pending-deletions] Deletion completed', {userId: userId.toString()});
processed++;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.error('[test/worker/process-pending-deletions] Error processing deletion', {
userId: deletion.userId.toString(),
error: errorMessage,
});
errors.push({
userId: deletion.userId.toString(),
error: errorMessage,
});
}
}
console.log('[test/worker/process-pending-deletions] Completed', {processed, total: readyDeletions.length});
await snowflakeService.shutdown();
await redis.quit();
return ctx.json(
{
scheduled: processed,
total: readyDeletions.length,
errors: errors.length > 0 ? errors : undefined,
},
200,
);
});
app.post('/test/worker/process-pending-deletion/:userId', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
console.log('[test/worker/process-pending-deletion/:userId] Request received', {userId: userId.toString()});
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
console.log('[test/worker/process-pending-deletion/:userId] User not found', {userId: userId.toString()});
throw new UnknownUserError();
}
if (!user.pendingDeletionAt) {
console.log('[test/worker/process-pending-deletion/:userId] User has no pending deletion', {
userId: userId.toString(),
});
throw new NoPendingDeletionError();
}
const deletionReasonCode = user.deletionReasonCode ?? 0;
console.log('[test/worker/process-pending-deletion/:userId] Running deletion synchronously', {
userId: userId.toString(),
deletionReasonCode,
pendingDeletionAt: user.pendingDeletionAt.toISOString(),
});
try {
const redis = new Redis(Config.redis.url);
const snowflakeService = new SnowflakeService(redis);
await snowflakeService.initialize();
const workerDeps = await initializeWorkerDepsWithHarnessEmail(ctx, snowflakeService);
await processUserDeletion(userId, deletionReasonCode, workerDeps);
console.log('[test/worker/process-pending-deletion/:userId] Deletion completed successfully', {
userId: userId.toString(),
});
return ctx.json(
{
success: true,
userId: userId.toString(),
deletionReasonCode,
},
200,
);
} catch (error) {
console.error('[test/worker/process-pending-deletion/:userId] Deletion failed', {
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
});
throw new DeletionFailedError(error instanceof Error ? error.message : String(error));
}
});
app.post('/test/worker/process-inactivity-deletions', async (ctx) => {
ensureHarnessAccess(ctx);
console.log('[test/worker/process-inactivity-deletions] Request received');
const redis = new Redis(Config.redis.url);
const snowflakeService = new SnowflakeService(redis);
await snowflakeService.initialize();
try {
const workerDeps = await initializeWorkerDepsWithHarnessEmail(ctx, snowflakeService);
setWorkerDependencies(workerDeps);
const result = await processInactivityDeletionsCore({
redis: workerDeps.redis,
userRepository: workerDeps.userRepository,
emailService: workerDeps.emailService,
activityTracker: workerDeps.activityTracker,
deletionEligibilityService: workerDeps.deletionEligibilityService,
});
console.log('[test/worker/process-inactivity-deletions] Completed successfully', result);
return ctx.json(
{
success: true,
processed: result.warningsSent + result.deletionsScheduled,
warnings_sent: result.warningsSent,
deletions_scheduled: result.deletionsScheduled,
},
200,
);
} catch (error) {
console.error('[test/worker/process-inactivity-deletions] Failed', {
error: error instanceof Error ? error.message : String(error),
});
throw new ProcessingFailedError(error instanceof Error ? error.message : String(error));
} finally {
await snowflakeService.shutdown();
await redis.quit();
}
});
app.post('/test/worker/send-scheduled-message/:userId/:scheduledMessageId', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const scheduledMessageId = createMessageID(BigInt(ctx.req.param('scheduledMessageId')));
const redis = new Redis(Config.redis.url);
const snowflakeService = new SnowflakeService(redis);
await snowflakeService.initialize();
try {
const workerDeps = await initializeWorkerDepsWithHarnessEmail(ctx, snowflakeService);
setWorkerDependencies(workerDeps);
const scheduledMessageRepository = new ScheduledMessageRepository();
const scheduledMessage = await scheduledMessageRepository.getScheduledMessage(userId, scheduledMessageId);
if (!scheduledMessage) {
return ctx.json({success: false, reason: 'scheduled message not found'}, 404);
}
const logger = {
debug: (message: string, extra?: object) => Logger.debug(extra ?? {}, message),
info: (message: string, extra?: object) => Logger.info(extra ?? {}, message),
warn: (message: string, extra?: object) => Logger.warn(extra ?? {}, message),
error: (message: string, extra?: object) => Logger.error(extra ?? {}, message),
};
const executor = new ScheduledMessageExecutor(workerDeps, logger, scheduledMessageRepository);
await executor.execute({
userId: userId.toString(),
scheduledMessageId: scheduledMessageId.toString(),
expectedScheduledAt: scheduledMessage.scheduledAt.toISOString(),
});
return ctx.json({success: true}, 200);
} finally {
await snowflakeService.shutdown();
await redis.quit();
}
});
app.post('/test/attachment-decay/rows', async (ctx) => {
ensureHarnessAccess(ctx);
const payload = (await ctx.req.json()) as {
rows?: Array<{
attachment_id?: string;
channel_id?: string;
message_id?: string;
expires_at?: string;
uploaded_at?: string;
last_accessed_at?: string;
filename?: string;
size_bytes?: string | number;
cost?: number;
lifetime_days?: number;
status?: string | null;
}>;
};
if (!payload.rows || payload.rows.length === 0) {
throw InputValidationError.create('rows', 'rows is required');
}
const repo = new AttachmentDecayRepository();
let inserted = 0;
for (const row of payload.rows) {
if (!row.attachment_id || !row.channel_id || !row.message_id || !row.expires_at) {
throw InputValidationError.create(
'attachment_id',
'attachment_id, channel_id, message_id, and expires_at are required',
);
}
let attachmentIdNum: bigint;
let channelIdNum: bigint;
let messageIdNum: bigint;
try {
attachmentIdNum = BigInt(row.attachment_id);
channelIdNum = BigInt(row.channel_id);
messageIdNum = BigInt(row.message_id);
} catch {
throw InputValidationError.create(
'attachment_id',
'attachment_id, channel_id, and message_id must be valid integers',
);
}
const expiresAt = new Date(row.expires_at);
if (Number.isNaN(expiresAt.getTime())) {
throw new InvalidTimestampError('expires_at must be a valid timestamp');
}
const uploadedAt = row.uploaded_at ? new Date(row.uploaded_at) : expiresAt;
const lastAccessedAt = row.last_accessed_at ? new Date(row.last_accessed_at) : uploadedAt;
if (Number.isNaN(uploadedAt.getTime()) || Number.isNaN(lastAccessedAt.getTime())) {
throw new InvalidTimestampError('uploaded_at and last_accessed_at must be valid timestamps');
}
const sizeInput = row.size_bytes ?? '1024';
let sizeBytes: bigint;
try {
sizeBytes = typeof sizeInput === 'number' ? BigInt(sizeInput) : BigInt(sizeInput);
} catch {
throw InputValidationError.create('size_bytes', 'size_bytes must be a valid integer');
}
await repo.upsert({
attachment_id: createAttachmentID(attachmentIdNum),
channel_id: createChannelID(channelIdNum),
message_id: createMessageID(messageIdNum),
filename: row.filename ?? 'attachment-decay-test.bin',
size_bytes: sizeBytes,
uploaded_at: uploadedAt,
expires_at: expiresAt,
last_accessed_at: lastAccessedAt,
cost: row.cost ?? 1,
lifetime_days: row.lifetime_days ?? 1,
status: row.status ?? null,
expiry_bucket: getExpiryBucket(expiresAt),
});
inserted++;
}
return ctx.json({inserted}, 200);
});
app.post('/test/attachment-decay/clear', async (ctx) => {
ensureHarnessAccess(ctx);
const repo = new AttachmentDecayRepository();
const deleted = await repo.clearAll(30);
return ctx.json({deleted}, 200);
});
app.post('/test/attachment-decay/query', async (ctx) => {
ensureHarnessAccess(ctx);
const payload = (await ctx.req.json()) as {
bucket?: number | string;
current_time?: string;
limit?: number;
};
const bucketValue = payload.bucket ? Number(payload.bucket) : undefined;
if (!bucketValue) {
throw InputValidationError.create('bucket', 'bucket is required');
}
const currentTime = payload.current_time ? new Date(payload.current_time) : new Date();
if (Number.isNaN(currentTime.getTime())) {
throw new InvalidTimestampError('current_time must be a valid timestamp');
}
const repo = new AttachmentDecayRepository();
const rows = await repo.fetchExpiredByBucket(bucketValue, currentTime, payload.limit ?? 200);
return ctx.json(
{
rows: rows.map((row) => ({
attachment_id: row.attachment_id.toString(),
channel_id: row.channel_id.toString(),
message_id: row.message_id.toString(),
expires_at: row.expires_at.toISOString(),
expiry_bucket: row.expiry_bucket,
})),
},
200,
);
});
app.get('/test/attachment-decay/:attachment_id', async (ctx) => {
ensureHarnessAccess(ctx);
const attachmentId = createAttachmentID(BigInt(ctx.req.param('attachment_id')));
const repo = new AttachmentDecayRepository();
const row = await repo.fetchById(attachmentId);
if (!row) {
return ctx.json({row: null}, 200);
}
return ctx.json(
{
row: {
attachment_id: row.attachment_id.toString(),
channel_id: row.channel_id.toString(),
message_id: row.message_id.toString(),
filename: row.filename,
size_bytes: row.size_bytes.toString(),
uploaded_at: row.uploaded_at.toISOString(),
expires_at: row.expires_at.toISOString(),
last_accessed_at: row.last_accessed_at.toISOString(),
cost: row.cost,
lifetime_days: row.lifetime_days,
status: row.status,
},
},
200,
);
});
app.get('/test/messages/:channel_id/:message_id/with-reference', async (ctx) => {
ensureHarnessAccess(ctx);
const channelId = createChannelID(BigInt(ctx.req.param('channel_id')));
const messageId = createMessageID(BigInt(ctx.req.param('message_id')));
const channelRepository = new ChannelRepository();
const message = await channelRepository.messages.getMessage(channelId, messageId);
if (!message) {
throw new UnknownMessageError();
}
const reference = message.reference;
const referencedMessage =
reference?.channelId && reference?.messageId
? await channelRepository.messages.getMessage(reference.channelId, reference.messageId)
: null;
const decayTargets = [
...message.attachments.map((att) => ({attachmentId: att.id})),
...(referencedMessage?.attachments.map((att) => ({attachmentId: att.id})) ?? []),
];
const attachmentDecayMap = decayTargets.length
? await new AttachmentDecayService().fetchMetadata(decayTargets)
: undefined;
const requestCache = ctx.get('requestCache');
const userCacheService = ctx.get('userCacheService');
const mediaService = ctx.get('mediaService');
const messageResponse = await mapMessageToResponse({
message,
userCacheService,
requestCache,
mediaService,
attachmentDecayMap,
getReferencedMessage: async (refChannelId, referencedMessageId) => {
if (referencedMessage && referencedMessage.id === referencedMessageId) {
return referencedMessage;
}
return channelRepository.messages.getMessage(refChannelId, referencedMessageId);
},
});
return ctx.json(messageResponse, 200);
});
app.post('/test/worker/expire-attachments', async (ctx) => {
ensureHarnessAccess(ctx);
console.log('[test/worker/expire-attachments] Request received');
const redis = new Redis(Config.redis.url);
const snowflakeService = new SnowflakeService(redis);
await snowflakeService.initialize();
try {
const workerDeps = await initializeWorkerDepsWithHarnessEmail(ctx, snowflakeService);
setWorkerDependencies(workerDeps);
await processExpiredAttachments();
console.log('[test/worker/expire-attachments] Completed successfully');
return ctx.json({success: true}, 200);
} finally {
await snowflakeService.shutdown();
await redis.quit();
}
});
app.post('/test/users/:userId/set-last-active-at', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {timestamp} = body as {timestamp?: string};
if (!timestamp) {
throw new InvalidTimestampError('Timestamp is required');
}
let date: Date;
try {
date = new Date(timestamp);
if (Number.isNaN(date.getTime())) {
throw new InvalidTimestampError();
}
} catch {
throw new InvalidTimestampError();
}
console.log('[test/users/:userId/set-last-active-at] Request received', {
userId: userId.toString(),
timestamp: date.toISOString(),
});
try {
const userRepository = new UserRepository();
const redis = new Redis(Config.redis.url);
try {
await userRepository.patchUpsert(userId, {
last_active_at: date,
});
const activityTracker = new RedisActivityTracker(redis);
await activityTracker.updateActivity(userId, date);
console.log('[test/users/:userId/set-last-active-at] Updated successfully', {
userId: userId.toString(),
timestamp: date.toISOString(),
});
return ctx.json(
{
success: true,
userId: userId.toString(),
last_active_at: date.toISOString(),
},
200,
);
} finally {
await redis.quit();
}
} catch (error) {
console.error('[test/users/:userId/set-last-active-at] Failed', {
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
});
throw new UpdateFailedError(error instanceof Error ? error.message : String(error));
}
});
app.post('/test/users/:userId/set-bot-flag', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {is_bot: isBot} = body as {is_bot?: boolean};
if (typeof isBot !== 'boolean') {
throw new InvalidBotFlagError();
}
console.log('[test/users/:userId/set-bot-flag] Request received', {
userId: userId.toString(),
isBot,
});
try {
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
await userRepository.patchUpsert(userId, {
bot: isBot,
});
console.log('[test/users/:userId/set-bot-flag] Updated successfully', {
userId: userId.toString(),
isBot,
});
return ctx.json(
{
success: true,
userId: userId.toString(),
is_bot: isBot,
},
200,
);
} catch (error) {
console.error('[test/users/:userId/set-bot-flag] Failed', {
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
});
throw new UpdateFailedError(error instanceof Error ? error.message : String(error));
}
});
app.post('/test/users/:userId/set-system-flag', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const body = await ctx.req.json();
const {is_system: isSystem} = body as {is_system?: boolean};
if (typeof isSystem !== 'boolean') {
throw new InvalidSystemFlagError();
}
console.log('[test/users/:userId/set-system-flag] Request received', {
userId: userId.toString(),
isSystem,
});
try {
const userRepository = new UserRepository();
const user = await userRepository.findUnique(userId);
if (!user) {
throw new UnknownUserError();
}
await userRepository.patchUpsert(userId, {
system: isSystem,
});
console.log('[test/users/:userId/set-system-flag] Updated successfully', {
userId: userId.toString(),
isSystem,
});
return ctx.json(
{
success: true,
userId: userId.toString(),
is_system: isSystem,
},
200,
);
} catch (error) {
console.error('[test/users/:userId/set-system-flag] Failed', {
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
});
throw new UpdateFailedError(error instanceof Error ? error.message : String(error));
}
});
app.post('/test/users/:userId/harvest/:harvestId/set-expiration', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
const harvestId = BigInt(ctx.req.param('harvestId'));
const body = await ctx.req.json();
const {expires_at} = body as {expires_at?: string};
if (!expires_at) {
throw new InvalidTimestampError('expires_at is required');
}
let date: Date;
try {
date = new Date(expires_at);
if (Number.isNaN(date.getTime())) {
throw new InvalidTimestampError();
}
} catch {
throw new InvalidTimestampError();
}
const harvestRepository = new UserHarvestRepository();
const harvest = await harvestRepository.findByUserAndHarvestId(userId, harvestId);
if (!harvest) {
throw new UnknownHarvestError();
}
harvest.downloadUrlExpiresAt = date;
await harvestRepository.update(harvest);
return ctx.json({success: true}, 200);
});
app.get('/test/users/:userId/presence/has-active', async (ctx) => {
ensureHarnessAccess(ctx);
const userId = createUserID(BigInt(ctx.req.param('userId')));
try {
const gatewayService = ctx.get('gatewayService');
const hasActive = await gatewayService.hasActivePresence(userId);
return ctx.json(
{
user_id: userId.toString(),
has_active: hasActive,
},
200,
);
} catch (error) {
console.error('[test/users/:userId/presence/has-active] Error checking presence', {
userId: userId.toString(),
error: error instanceof Error ? error.message : String(error),
});
return ctx.json(
{
user_id: userId.toString(),
has_active: false,
error: error instanceof Error ? error.message : String(error),
},
200,
);
}
});
app.post('/test/messages/seed', async (ctx) => {
ensureHarnessAccess(ctx);
interface SeedMessageInput {
message_id?: string;
timestamp?: string;
content?: string;
author_id?: string;
}
interface SeedMessagesRequest {
channel_id: string;
messages: Array<SeedMessageInput>;
author_id?: string;
clear_existing?: boolean;
skip_bucket_index?: boolean;
}
const body = (await ctx.req.json()) as SeedMessagesRequest;
if (!body.channel_id) {
throw InputValidationError.create('channel_id', 'channel_id is required');
}
if (!body.messages || !Array.isArray(body.messages) || body.messages.length === 0) {
throw InputValidationError.create('messages', 'messages array is required and must not be empty');
}
const channelId = createChannelID(BigInt(body.channel_id));
const defaultAuthorId = body.author_id ? createUserID(BigInt(body.author_id)) : null;
const skipBucketIndex = body.skip_bucket_index ?? false;
if (body.clear_existing) {
await deleteOneOrMany(ChannelMessageBuckets.deletePartition({channel_id: channelId}));
await deleteOneOrMany(ChannelEmptyBuckets.deletePartition({channel_id: channelId}));
}
const seededMessages: Array<{message_id: string; bucket: number; timestamp: string}> = [];
const bucketsPopulated = new Set<number>();
let latestMessageId: MessageID | null = null;
let latestBucket: number | null = null;
const batch = new BatchBuilder();
for (let i = 0; i < body.messages.length; i++) {
const input = body.messages[i];
let messageId: MessageID;
let timestamp: number;
if (input.message_id) {
messageId = createMessageID(BigInt(input.message_id));
timestamp = SnowflakeUtils.extractTimestamp(messageId);
} else if (input.timestamp) {
const parsedDate = new Date(input.timestamp);
if (Number.isNaN(parsedDate.getTime())) {
throw new InvalidTimestampError(`Invalid timestamp at index ${i}: ${input.timestamp}`);
}
timestamp = parsedDate.getTime();
const baseSnowflake = SnowflakeUtils.getSnowflake(timestamp);
messageId = createMessageID(baseSnowflake | BigInt(i & 0xfff));
} else {
timestamp = Date.now() + i;
messageId = createMessageID(SnowflakeUtils.getSnowflake(timestamp) | BigInt(i & 0xfff));
}
const bucket = BucketUtils.makeBucket(messageId);
const authorId = input.author_id ? createUserID(BigInt(input.author_id)) : defaultAuthorId;
const content = input.content ?? `Test message ${i + 1}`;
const messageRow: MessageRow = {
channel_id: channelId,
bucket,
message_id: messageId,
author_id: authorId,
type: 0,
webhook_id: null,
webhook_name: null,
webhook_avatar_hash: null,
content,
edited_timestamp: null,
pinned_timestamp: null,
flags: 0,
mention_everyone: false,
mention_users: null,
mention_roles: null,
mention_channels: null,
attachments: null,
embeds: null,
sticker_items: null,
message_reference: null,
message_snapshots: null,
call: null,
has_reaction: false,
version: 1,
};
batch.addPrepared(Messages.upsertAll(messageRow));
bucketsPopulated.add(bucket);
if (latestMessageId === null || messageId > latestMessageId) {
latestMessageId = messageId;
latestBucket = bucket;
}
seededMessages.push({
message_id: messageId.toString(),
bucket,
timestamp: new Date(timestamp).toISOString(),
});
}
if (!skipBucketIndex) {
for (const bucket of bucketsPopulated) {
batch.addPrepared(
ChannelMessageBuckets.upsertAll({
channel_id: channelId,
bucket,
updated_at: new Date(),
}),
);
batch.addPrepared(
ChannelEmptyBuckets.deleteByPk({
channel_id: channelId,
bucket,
}),
);
}
}
if (latestMessageId !== null && latestBucket !== null) {
const createdBucket = Math.min(BucketUtils.makeBucket(channelId), ...bucketsPopulated);
const existingState = await fetchOne<ChannelStateRow>(FETCH_CHANNEL_STATE.bind({channel_id: channelId}));
const finalCreatedBucket =
existingState?.created_bucket !== undefined
? Math.min(createdBucket, existingState.created_bucket)
: createdBucket;
const finalLastMessageId =
existingState?.last_message_id && existingState.last_message_id > latestMessageId
? existingState.last_message_id
: latestMessageId;
const finalLastMessageBucket =
existingState?.last_message_id && existingState.last_message_id > latestMessageId
? existingState.last_message_bucket
: skipBucketIndex
? null
: latestBucket;
batch.addPrepared(
ChannelState.upsertAll({
channel_id: channelId,
created_bucket: finalCreatedBucket,
has_messages: true,
last_message_id: finalLastMessageId,
last_message_bucket: finalLastMessageBucket,
updated_at: new Date(),
}),
);
}
await batch.execute();
return ctx.json(
{
messages: seededMessages,
buckets_populated: skipBucketIndex ? [] : Array.from(bucketsPopulated).sort((a, b) => b - a),
channel_state_updated: latestMessageId !== null,
},
200,
);
});
app.get('/test/channels/:channelId/state', async (ctx) => {
ensureHarnessAccess(ctx);
const channelId = createChannelID(BigInt(ctx.req.param('channelId')));
const state = await fetchOne<ChannelStateRow>(FETCH_CHANNEL_STATE.bind({channel_id: channelId}));
if (!state) {
return ctx.json(
{
channel_id: channelId.toString(),
exists: false,
},
200,
);
}
return ctx.json(
{
channel_id: channelId.toString(),
exists: true,
created_bucket: state.created_bucket,
has_messages: state.has_messages,
last_message_id: state.last_message_id?.toString() ?? null,
last_message_bucket: state.last_message_bucket,
updated_at: state.updated_at.toISOString(),
},
200,
);
});
app.get('/test/channels/:channelId/buckets', async (ctx) => {
ensureHarnessAccess(ctx);
const channelId = createChannelID(BigInt(ctx.req.param('channelId')));
const rows = await fetchMany<Pick<ChannelMessageBucketRow, 'bucket' | 'updated_at'>>(
FETCH_CHANNEL_BUCKETS.bind({channel_id: channelId}),
);
return ctx.json(
{
channel_id: channelId.toString(),
buckets: rows.map((r) => ({
bucket: r.bucket,
updated_at: r.updated_at.toISOString(),
})),
count: rows.length,
},
200,
);
});
app.get('/test/channels/:channelId/empty-buckets', async (ctx) => {
ensureHarnessAccess(ctx);
const channelId = createChannelID(BigInt(ctx.req.param('channelId')));
const rows = await fetchMany<Pick<ChannelEmptyBucketRow, 'bucket' | 'updated_at'>>(
FETCH_CHANNEL_EMPTY_BUCKETS.bind({channel_id: channelId}),
);
return ctx.json(
{
channel_id: channelId.toString(),
empty_buckets: rows.map((r) => ({
bucket: r.bucket,
updated_at: r.updated_at.toISOString(),
})),
count: rows.length,
},
200,
);
});
app.delete('/test/channels/:channelId/messages', async (ctx) => {
ensureHarnessAccess(ctx);
const channelId = createChannelID(BigInt(ctx.req.param('channelId')));
const batch = new BatchBuilder();
batch.addPrepared(ChannelMessageBuckets.deletePartition({channel_id: channelId}));
batch.addPrepared(ChannelEmptyBuckets.deletePartition({channel_id: channelId}));
const createdBucket = BucketUtils.makeBucket(channelId);
batch.addPrepared(
ChannelState.upsertAll({
channel_id: channelId,
created_bucket: createdBucket,
has_messages: false,
last_message_id: null,
last_message_bucket: null,
updated_at: new Date(),
}),
);
await batch.execute();
return ctx.json(
{
channel_id: channelId.toString(),
cleared: true,
},
200,
);
});
};