import { z } from "zod"; export const UserSchema = z.object({ id: z.uuid(), username: z.string(), displayName: z.string().nullable(), bio: z.string().nullable(), avatarUrl: z.url().nullable(), headerUrl: z.url().nullable(), customCss: z.string().nullable(), topFriends: z.array(z.string()), joinedAt: z.coerce.date(), }); export const MeSchema = z.object({ id: z.uuid(), username: z.string(), displayName: z.string().nullable(), bio: z.string().nullable(), avatarUrl: z.url().nullable(), headerUrl: z.url().nullable(), customCss: z.string().nullable(), topFriends: z.array(z.string()), joinedAt: z.coerce.date(), following: z.array(UserSchema), }); export const ThoughtSchema = z.object({ id: z.uuid(), authorUsername: z.string(), content: z.string(), visibility: z.enum(["Public", "FriendsOnly", "Private"]), replyToId: z.uuid().nullable(), createdAt: z.coerce.date(), }); export const RegisterSchema = z.object({ username: z.string().min(3), email: z.email(), password: z.string().min(6), }); export const LoginSchema = z.object({ username: z.string().min(3), password: z.string().min(6), }); export const CreateThoughtSchema = z.object({ content: z.string().min(1).max(128), visibility: z.enum(["Public", "FriendsOnly", "Private"]).optional(), replyToId: z.string().uuid().optional(), }); export const UpdateProfileSchema = z.object({ displayName: z.string().max(50).optional(), bio: z.string().max(4000).optional(), avatarUrl: z.url().or(z.literal("")).optional(), headerUrl: z.url().or(z.literal("")).optional(), customCss: z.string().optional(), topFriends: z.array(z.string()).max(8).optional(), }); export const SearchResultsSchema = z.object({ users: z.object({ users: z.array(UserSchema) }), thoughts: z.object({ thoughts: z.array(ThoughtSchema) }), }); export const ApiKeySchema = z.object({ id: z.uuid(), name: z.string(), keyPrefix: z.string(), createdAt: z.coerce.date(), }); export const ApiKeyResponseSchema = ApiKeySchema.extend({ plaintextKey: z.string().optional(), }); export const ApiKeyListSchema = z.object({ apiKeys: z.array(ApiKeySchema), }); export const CreateApiKeySchema = z.object({ name: z.string().min(1, "Key name cannot be empty."), }); export const ThoughtThreadSchema: z.ZodType<{ id: string; authorUsername: string; content: string; visibility: "Public" | "FriendsOnly" | "Private"; replyToId: string | null; createdAt: Date; replies: ThoughtThread[]; }> = z.object({ id: z.uuid(), authorUsername: z.string(), content: z.string(), visibility: z.enum(["Public", "FriendsOnly", "Private"]), replyToId: z.uuid().nullable(), createdAt: z.coerce.date(), replies: z.lazy(() => z.array(ThoughtThreadSchema)), }); export type User = z.infer; export type Me = z.infer; export type Thought = z.infer; export type Register = z.infer; export type Login = z.infer; export type ApiKey = z.infer; export type ApiKeyResponse = z.infer; export type ThoughtThread = z.infer; const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"; async function apiFetch( endpoint: string, options: RequestInit = {}, schema: z.ZodType, token?: string | null ): Promise { const headers: Record = { "Content-Type": "application/json", ...(options.headers as Record), }; if (token) { headers["Authorization"] = `Bearer ${token}`; } const response = await fetch(`${API_BASE_URL}${endpoint}`, { ...options, headers, }); if (!response.ok) { throw new Error(`API request failed with status ${response.status}`); } if (response.status === 204) { return null as T; } const data = await response.json(); return schema.parse(data); } export const registerUser = (data: z.infer) => apiFetch("/auth/register", { method: "POST", body: JSON.stringify(data), }, UserSchema); export const loginUser = (data: z.infer) => apiFetch("/auth/login", { method: "POST", body: JSON.stringify(data), }, z.object({ token: z.string() })); export const getFeed = (token: string) => apiFetch( "/feed", {}, z.object({ thoughts: z.array(ThoughtSchema) }), token ); // --- User API Functions --- export const getUserProfile = (username: string, token: string | null) => apiFetch(`/users/${username}`, {}, UserSchema, token); export const getUserThoughts = (username: string, token: string | null) => apiFetch( `/users/${username}/thoughts`, {}, z.object({ thoughts: z.array(ThoughtSchema) }), token ); export const createThought = ( data: z.infer, token: string ) => apiFetch( "/thoughts", { method: "POST", body: JSON.stringify(data), }, ThoughtSchema, token ); export const followUser = (username: string, token: string) => apiFetch( `/users/${username}/follow`, { method: "POST" }, z.null(), // Expect a 204 No Content response, which we treat as null token ); export const unfollowUser = (username: string, token: string) => apiFetch( `/users/${username}/follow`, { method: "DELETE" }, z.null(), // Expect a 204 No Content response token ); export const getMe = (token: string) => apiFetch("/users/me", {}, MeSchema, token); export const getPopularTags = () => apiFetch( "/tags/popular", {}, z.array(z.string()) // Expect an array of strings ); export const deleteThought = (thoughtId: string, token: string) => apiFetch( `/thoughts/${thoughtId}`, { method: "DELETE" }, z.null(), // Expect a 204 No Content response token ); export const updateProfile = ( data: z.infer, token: string ) => apiFetch( "/users/me", { method: "PUT", body: JSON.stringify(data), }, UserSchema, // Expect the updated user object back token ); export const getThoughtsByTag = (tagName: string, token: string | null) => apiFetch( `/tags/${tagName}`, {}, z.object({ thoughts: z.array(ThoughtSchema) }), token ); export const getThoughtById = (thoughtId: string, token: string | null) => apiFetch( `/thoughts/${thoughtId}`, {}, ThoughtSchema, // Expect a single thought object token ); export const getFollowingList = (username: string, token: string | null) => apiFetch( `/users/${username}/following`, {}, z.object({ users: z.array(UserSchema) }), token ); export const getFollowersList = (username: string, token: string | null) => apiFetch( `/users/${username}/followers`, {}, z.object({ users: z.array(UserSchema) }), token ); export const getFriends = (token: string) => apiFetch( "/friends", {}, z.object({ users: z.array(UserSchema) }), token ); export const search = (query: string, token: string | null) => apiFetch( `/search?q=${encodeURIComponent(query)}`, {}, SearchResultsSchema, token ); export const getApiKeys = (token: string) => apiFetch(`/users/me/api-keys`, {}, ApiKeyListSchema, token); export const createApiKey = ( data: z.infer, token: string ) => apiFetch( `/users/me/api-keys`, { method: "POST", body: JSON.stringify(data), }, ApiKeyResponseSchema, token ); export const deleteApiKey = (keyId: string, token: string) => apiFetch( `/users/me/api-keys/${keyId}`, { method: "DELETE" }, z.null(), token ); export const getThoughtThread = (thoughtId: string, token: string | null) => apiFetch(`/thoughts/${thoughtId}/thread`, {}, ThoughtThreadSchema, token);