fix: persist homeUserId for federated users to ensure consistent avatar colors
Store the original home snowflake ID (homeUserId) during federation replication so that avatar gradient colors resolve identically across instances. Previously, replicated users got new snowflake IDs on each instance, causing different gradient colors. Now Avatar, UserProfilePopout, VoiceUser, StreamTile, and VoiceChannel all resolve through homeUserId when available. Includes backfill logic for existing federated users missing the field.
This commit is contained in:
@@ -60,7 +60,8 @@ export function runMigrations(db: Database.Database): void {
|
||||
name: 'users',
|
||||
columns: [
|
||||
{ name: 'home_instance', type: 'TEXT' },
|
||||
{ name: 'replicated_instances', type: "TEXT DEFAULT '[]'" }
|
||||
{ name: 'replicated_instances', type: "TEXT DEFAULT '[]'" },
|
||||
{ name: 'home_user_id', type: 'TEXT' }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,6 +10,7 @@ export const users = sqliteTable('users', {
|
||||
customStatus: text('custom_status'),
|
||||
isAdmin: integer('is_admin').default(0),
|
||||
homeInstance: text('home_instance'),
|
||||
homeUserId: text('home_user_id'),
|
||||
replicatedInstances: text('replicated_instances').default('[]'),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
||||
},
|
||||
},
|
||||
}, async (request, reply) => {
|
||||
const { username, password, displayName, homeInstance } = request.body;
|
||||
const { username, password, displayName, homeInstance, homeUserId } = request.body;
|
||||
|
||||
if (!username || typeof username !== 'string') {
|
||||
return reply.code(400).send({ error: 'Username is required', statusCode: 400 });
|
||||
@@ -102,6 +102,7 @@ export async function authRoutes(app: FastifyInstance): Promise<void> {
|
||||
status: 'online',
|
||||
isAdmin: isFirstUser ? 1 : 0,
|
||||
homeInstance: homeInstance || null,
|
||||
homeUserId: (homeInstance && homeUserId && typeof homeUserId === 'string') ? homeUserId : null,
|
||||
createdAt: now,
|
||||
}).run();
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export async function userRoutes(app: FastifyInstance): Promise<void> {
|
||||
});
|
||||
|
||||
app.patch<{ Body: UpdateUserRequest }>('/api/users/@me', { preHandler: authenticate }, async (request, reply) => {
|
||||
const { displayName, avatar, customStatus, status, replicatedInstances } = request.body;
|
||||
const { displayName, avatar, customStatus, status, replicatedInstances, homeUserId } = request.body;
|
||||
const db = getDb();
|
||||
|
||||
const updateData: Record<string, string | null | undefined> = {};
|
||||
@@ -97,6 +97,14 @@ export async function userRoutes(app: FastifyInstance): Promise<void> {
|
||||
updateData.replicatedInstances = JSON.stringify(replicatedInstances);
|
||||
}
|
||||
|
||||
if (homeUserId !== undefined) {
|
||||
// Only allow setting homeUserId for replicated users (has homeInstance)
|
||||
const currentUser = db.select().from(schema.users).where(eq(schema.users.id, request.userId)).get();
|
||||
if (currentUser?.homeInstance && typeof homeUserId === 'string' && homeUserId.length > 0) {
|
||||
updateData.homeUserId = homeUserId;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(updateData).length === 0) {
|
||||
return reply.code(400).send({ error: 'No fields to update', statusCode: 400 });
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export function sanitizeUser(row: typeof schema.users.$inferSelect): User {
|
||||
isAdmin: row.isAdmin === 1,
|
||||
createdAt: row.createdAt,
|
||||
homeInstance: row.homeInstance ?? null,
|
||||
homeUserId: row.homeUserId ?? null,
|
||||
replicatedInstances,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user