208 lines
6.2 KiB
TypeScript
208 lines
6.2 KiB
TypeScript
import { z } from "zod";
|
|
import { Fastify } from "../types";
|
|
import { db } from "@/storage/db";
|
|
import { RelationshipStatus } from "@prisma/client";
|
|
import { getPublicUrl } from "@/storage/files";
|
|
import { friendAdd } from "@/app/social/friendAdd";
|
|
import { Context } from "@/context";
|
|
import { friendRemove } from "@/app/social/friendRemove";
|
|
import { friendList } from "@/app/social/friendList";
|
|
|
|
export async function userRoutes(app: Fastify) {
|
|
|
|
// Get user profile
|
|
app.get('/v1/user/:id', {
|
|
schema: {
|
|
params: z.object({
|
|
id: z.string()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
user: UserProfileSchema
|
|
}),
|
|
404: z.object({
|
|
error: z.literal('User not found')
|
|
})
|
|
}
|
|
},
|
|
preHandler: app.authenticate
|
|
}, async (request, reply) => {
|
|
const { id } = request.params;
|
|
|
|
// Fetch user
|
|
const user = await db.account.findUnique({
|
|
where: {
|
|
id: id
|
|
},
|
|
include: {
|
|
githubUser: true
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
return reply.code(404).send({ error: 'User not found' });
|
|
}
|
|
|
|
// Resolve relationship status
|
|
const relationship = await db.userRelationship.findFirst({
|
|
where: {
|
|
fromUserId: request.userId,
|
|
toUserId: id
|
|
}
|
|
});
|
|
const status: RelationshipStatus = relationship?.status || RelationshipStatus.none;
|
|
|
|
// Build user profile
|
|
return reply.send({
|
|
user: {
|
|
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
|
|
app.get('/v1/user/search', {
|
|
schema: {
|
|
querystring: z.object({
|
|
query: z.string()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
user: UserProfileSchema
|
|
}),
|
|
404: z.object({
|
|
error: z.literal('User not found')
|
|
})
|
|
}
|
|
},
|
|
preHandler: app.authenticate
|
|
}, async (request, reply) => {
|
|
const { query } = request.query;
|
|
|
|
// Search for user by username or GitHub login
|
|
const user = await db.account.findFirst({
|
|
where: {
|
|
username: {
|
|
startsWith: query,
|
|
mode: 'insensitive'
|
|
}
|
|
},
|
|
include: {
|
|
githubUser: true
|
|
}
|
|
});
|
|
|
|
if (!user) {
|
|
return reply.code(404).send({ error: 'User not found' });
|
|
}
|
|
|
|
// Resolve relationship status
|
|
const relationship = await db.userRelationship.findFirst({
|
|
where: {
|
|
fromUserId: request.userId,
|
|
toUserId: user.id
|
|
}
|
|
});
|
|
const status: RelationshipStatus = relationship?.status || RelationshipStatus.none;
|
|
|
|
return reply.send({
|
|
user: {
|
|
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
|
|
}
|
|
});
|
|
});
|
|
|
|
// Add friend
|
|
app.post('/v1/friends/add', {
|
|
schema: {
|
|
body: z.object({
|
|
uid: z.string()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
user: UserProfileSchema.nullable()
|
|
}),
|
|
404: z.object({
|
|
error: z.literal('User not found')
|
|
})
|
|
}
|
|
},
|
|
preHandler: app.authenticate
|
|
}, async (request, reply) => {
|
|
const user = await friendAdd(Context.create(request.userId), request.body.uid);
|
|
return reply.send({ user });
|
|
});
|
|
|
|
app.post('/v1/friends/remove', {
|
|
schema: {
|
|
body: z.object({
|
|
uid: z.string()
|
|
}),
|
|
response: {
|
|
200: z.object({
|
|
user: UserProfileSchema.nullable()
|
|
}),
|
|
404: z.object({
|
|
error: z.literal('User not found')
|
|
})
|
|
}
|
|
},
|
|
preHandler: app.authenticate
|
|
}, async (request, reply) => {
|
|
const user = await friendRemove(Context.create(request.userId), request.body.uid);
|
|
return reply.send({ user });
|
|
});
|
|
|
|
app.get('/v1/friends', {
|
|
schema: {
|
|
response: {
|
|
200: z.object({
|
|
friends: z.array(UserProfileSchema)
|
|
})
|
|
}
|
|
},
|
|
preHandler: app.authenticate
|
|
}, async (request, reply) => {
|
|
const friends = await friendList(Context.create(request.userId));
|
|
return reply.send({ friends });
|
|
});
|
|
};
|
|
|
|
// Shared Zod Schemas
|
|
const RelationshipStatusSchema = z.enum(['none', 'requested', 'pending', 'friend', 'rejected']);
|
|
const UserProfileSchema = z.object({
|
|
id: z.string(),
|
|
firstName: z.string(),
|
|
lastName: z.string().nullable(),
|
|
avatar: z.object({
|
|
path: z.string(),
|
|
url: z.string(),
|
|
width: z.number().optional(),
|
|
height: z.number().optional(),
|
|
thumbhash: z.string().optional()
|
|
}).nullable(),
|
|
username: z.string(),
|
|
status: RelationshipStatusSchema
|
|
}); |