feat: add bio, return multiple results from search

This commit is contained in:
Steve Korshakov 2025-09-19 21:29:15 -07:00
parent 0d46e6fee3
commit 595e23967a
3 changed files with 29 additions and 55 deletions

View File

@ -2,11 +2,11 @@ import { z } from "zod";
import { Fastify } from "../types"; import { Fastify } from "../types";
import { db } from "@/storage/db"; import { db } from "@/storage/db";
import { RelationshipStatus } from "@prisma/client"; import { RelationshipStatus } from "@prisma/client";
import { getPublicUrl } from "@/storage/files";
import { friendAdd } from "@/app/social/friendAdd"; import { friendAdd } from "@/app/social/friendAdd";
import { Context } from "@/context"; import { Context } from "@/context";
import { friendRemove } from "@/app/social/friendRemove"; import { friendRemove } from "@/app/social/friendRemove";
import { friendList } from "@/app/social/friendList"; import { friendList } from "@/app/social/friendList";
import { buildUserProfile } from "@/app/social/type";
export async function userRoutes(app: Fastify) { export async function userRoutes(app: Fastify) {
@ -54,24 +54,11 @@ export async function userRoutes(app: Fastify) {
// Build user profile // Build user profile
return reply.send({ return reply.send({
user: { user: buildUserProfile(user, status)
id: user.id,
firstName: user.firstName || '',
lastName: user.lastName,
avatar: user.avatar ? {
path: user.avatar.path,
url: getPublicUrl(user.avatar.path),
width: user.avatar.width,
height: user.avatar.height,
thumbhash: user.avatar.thumbhash
} : null,
username: user.username || (user.githubUser?.profile?.login || ''),
status: status
}
}); });
}); });
// Search for user // Search for users
app.get('/v1/user/search', { app.get('/v1/user/search', {
schema: { schema: {
querystring: z.object({ querystring: z.object({
@ -79,10 +66,7 @@ export async function userRoutes(app: Fastify) {
}), }),
response: { response: {
200: z.object({ 200: z.object({
user: UserProfileSchema users: z.array(UserProfileSchema)
}),
404: z.object({
error: z.literal('User not found')
}) })
} }
}, },
@ -90,8 +74,8 @@ export async function userRoutes(app: Fastify) {
}, async (request, reply) => { }, async (request, reply) => {
const { query } = request.query; const { query } = request.query;
// Search for user by username or GitHub login // Search for users by username, first 10 matches
const user = await db.account.findFirst({ const users = await db.account.findMany({
where: { where: {
username: { username: {
startsWith: query, startsWith: query,
@ -100,37 +84,27 @@ export async function userRoutes(app: Fastify) {
}, },
include: { include: {
githubUser: true githubUser: true
},
take: 10,
orderBy: {
username: 'asc'
} }
}); });
if (!user) { // Resolve relationship status for each user
return reply.code(404).send({ error: 'User not found' }); const userProfiles = await Promise.all(users.map(async (user) => {
} const relationship = await db.userRelationship.findFirst({
where: {
// Resolve relationship status fromUserId: request.userId,
const relationship = await db.userRelationship.findFirst({ toUserId: user.id
where: { }
fromUserId: request.userId, });
toUserId: user.id const status: RelationshipStatus = relationship?.status || RelationshipStatus.none;
} return buildUserProfile(user, status);
}); }));
const status: RelationshipStatus = relationship?.status || RelationshipStatus.none;
return reply.send({ return reply.send({
user: { users: userProfiles
id: user.id,
firstName: user.firstName || '',
lastName: user.lastName,
avatar: user.avatar ? {
path: user.avatar.path,
url: getPublicUrl(user.avatar.path),
width: user.avatar.width,
height: user.avatar.height,
thumbhash: user.avatar.thumbhash
} : null,
username: user.username || (user.githubUser?.profile?.login || ''),
status: status
}
}); });
}); });
@ -204,5 +178,6 @@ const UserProfileSchema = z.object({
thumbhash: z.string().optional() thumbhash: z.string().optional()
}).nullable(), }).nullable(),
username: z.string(), username: z.string(),
bio: z.string().nullable(),
status: RelationshipStatusSchema status: RelationshipStatusSchema
}); });

View File

@ -4,14 +4,13 @@ import { log } from "@/utils/log";
import { allocateUserSeq } from "@/storage/seq"; import { allocateUserSeq } from "@/storage/seq";
import { buildUpdateAccountUpdate, eventRouter } from "@/app/events/eventRouter"; import { buildUpdateAccountUpdate, eventRouter } from "@/app/events/eventRouter";
import { randomKeyNaked } from "@/utils/randomKeyNaked"; import { randomKeyNaked } from "@/utils/randomKeyNaked";
import { Prisma } from "@prisma/client";
/** /**
* Disconnects a GitHub account from a user profile. * Disconnects a GitHub account from a user profile.
* *
* Flow: * Flow:
* 1. Check if user has GitHub connected - early exit if not * 1. Check if user has GitHub connected - early exit if not
* 2. In transaction: clear GitHub link, username, avatar from account and delete GitHub user record * 2. In transaction: clear GitHub link and username from account (keeps avatar) and delete GitHub user record
* 3. Send socket update after transaction completes * 3. Send socket update after transaction completes
* *
* @param ctx - Request context containing user ID * @param ctx - Request context containing user ID
@ -36,13 +35,12 @@ export async function githubDisconnect(ctx: Context): Promise<void> {
// Step 2: Transaction for atomic database operations // Step 2: Transaction for atomic database operations
await db.$transaction(async (tx) => { await db.$transaction(async (tx) => {
// Clear GitHub connection, username, and avatar from account // Clear GitHub connection and username from account (keep avatar)
await tx.account.update({ await tx.account.update({
where: { id: userId }, where: { id: userId },
data: { data: {
githubUserId: null, githubUserId: null,
username: null, username: null
avatar: Prisma.JsonNull
} }
}); });
@ -56,8 +54,7 @@ export async function githubDisconnect(ctx: Context): Promise<void> {
const updSeq = await allocateUserSeq(userId); const updSeq = await allocateUserSeq(userId);
const updatePayload = buildUpdateAccountUpdate(userId, { const updatePayload = buildUpdateAccountUpdate(userId, {
github: null, github: null,
username: null, username: null
avatar: null
}, updSeq, randomKeyNaked(12)); }, updSeq, randomKeyNaked(12));
eventRouter.emitUpdate({ eventRouter.emitUpdate({

View File

@ -14,6 +14,7 @@ export type UserProfile = {
thumbhash?: string; thumbhash?: string;
} | null; } | null;
username: string; username: string;
bio: string | null;
status: RelationshipStatus; status: RelationshipStatus;
} }
@ -49,6 +50,7 @@ export function buildUserProfile(
lastName: account.lastName, lastName: account.lastName,
avatar, avatar,
username: account.username || githubProfile?.login || '', username: account.username || githubProfile?.login || '',
bio: githubProfile?.bio || null,
status status
}; };
} }