This repository has been archived on 2026-02-06. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
serengo/src/routes/api/friends/+server.ts
Zias van Nes 96a173b73b feat:use local proxy for media
use local proxy for media so that media doesnt need to be requested from
r2 everytime but can be cached locally. this also fixes some csp issues
ive been having.
2025-11-17 10:48:40 +01:00

215 lines
6.2 KiB
TypeScript

import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { db } from '$lib/server/db';
import { friendship, user } from '$lib/server/db/schema';
import { eq, and, or } from 'drizzle-orm';
import { encodeBase64url } from '@oslojs/encoding';
import { getLocalR2Url } from '$lib/server/r2';
import { notificationService } from '$lib/server/notifications';
import { pushService } from '$lib/server/push';
function generateFriendshipId(): string {
const bytes = crypto.getRandomValues(new Uint8Array(15));
return encodeBase64url(bytes);
}
export const GET: RequestHandler = async ({ url, locals }) => {
if (!locals.user) {
throw error(401, 'Unauthorized');
}
const status = url.searchParams.get('status') || 'accepted';
const type = url.searchParams.get('type') || 'friends'; // 'friends', 'sent', 'received'
try {
let friendships;
if (type === 'friends') {
// Get accepted friendships where user is either sender or receiver
const friendshipsRaw = await db
.select({
id: friendship.id,
userId: friendship.userId,
friendId: friendship.friendId,
status: friendship.status,
createdAt: friendship.createdAt
})
.from(friendship)
.where(
and(
eq(friendship.status, status),
or(eq(friendship.userId, locals.user.id), eq(friendship.friendId, locals.user.id))
)
);
// Get friend details for each friendship
friendships = await Promise.all(
friendshipsRaw.map(async (f) => {
const friendUserId = f.userId === locals.user!.id ? f.friendId : f.userId;
const friendUser = await db
.select({
username: user.username,
profilePictureUrl: user.profilePictureUrl
})
.from(user)
.where(eq(user.id, friendUserId))
.limit(1);
return {
id: f.id,
userId: f.userId,
friendId: f.friendId,
status: f.status,
createdAt: f.createdAt,
friendUsername: friendUser[0]?.username || '',
friendProfilePictureUrl: friendUser[0]?.profilePictureUrl || null
};
})
);
} else if (type === 'sent') {
// Get friend requests sent by current user
friendships = await db
.select({
id: friendship.id,
userId: friendship.userId,
friendId: friendship.friendId,
status: friendship.status,
createdAt: friendship.createdAt,
friendUsername: user.username,
friendProfilePictureUrl: user.profilePictureUrl
})
.from(friendship)
.innerJoin(user, eq(friendship.friendId, user.id))
.where(and(eq(friendship.userId, locals.user.id), eq(friendship.status, status)));
} else if (type === 'received') {
// Get friend requests received by current user
friendships = await db
.select({
id: friendship.id,
userId: friendship.userId,
friendId: friendship.friendId,
status: friendship.status,
createdAt: friendship.createdAt,
friendUsername: user.username,
friendProfilePictureUrl: user.profilePictureUrl
})
.from(friendship)
.innerJoin(user, eq(friendship.userId, user.id))
.where(and(eq(friendship.friendId, locals.user.id), eq(friendship.status, status)));
}
// Generate local proxy URLs for profile pictures
const friendshipsWithSignedUrls = (friendships || []).map((friendship) => {
let profilePictureUrl = friendship.friendProfilePictureUrl;
if (profilePictureUrl && !profilePictureUrl.startsWith('http')) {
profilePictureUrl = getLocalR2Url(profilePictureUrl);
}
return {
...friendship,
friendProfilePictureUrl: profilePictureUrl
};
});
return json(friendshipsWithSignedUrls);
} catch (err) {
console.error('Error loading friendships:', err);
throw error(500, 'Failed to load friendships');
}
};
export const POST: RequestHandler = async ({ request, locals }) => {
if (!locals.user) {
throw error(401, 'Unauthorized');
}
const data = await request.json();
const { friendId } = data;
if (!friendId) {
throw error(400, 'Friend ID is required');
}
if (friendId === locals.user.id) {
throw error(400, 'Cannot send friend request to yourself');
}
try {
// Check if friend exists
const friendExists = await db.select().from(user).where(eq(user.id, friendId)).limit(1);
if (friendExists.length === 0) {
throw error(404, 'User not found');
}
// Check if friendship already exists
const existingFriendship = await db
.select()
.from(friendship)
.where(
or(
and(eq(friendship.userId, locals.user.id), eq(friendship.friendId, friendId)),
and(eq(friendship.userId, friendId), eq(friendship.friendId, locals.user.id))
)
)
.limit(1);
if (existingFriendship.length > 0) {
const status = existingFriendship[0].status;
if (status === 'accepted') {
throw error(400, 'Already friends');
} else if (status === 'pending') {
throw error(400, 'Friend request already sent');
} else if (status === 'blocked') {
throw error(400, 'Cannot send friend request');
}
}
// Create new friend request
const newFriendship = await db
.insert(friendship)
.values({
id: generateFriendshipId(),
userId: locals.user.id,
friendId,
status: 'pending'
})
.returning();
// Send notification to friend
const shouldNotify = await notificationService.shouldNotify(friendId, 'friend_request');
if (shouldNotify) {
await notificationService.createNotification({
userId: friendId,
type: 'friend_request',
title: 'New friend request',
message: `${locals.user.username} sent you a friend request`,
data: {
friendshipId: newFriendship[0].id,
senderId: locals.user.id,
senderUsername: locals.user.username
}
});
// Send push notification
await pushService.sendPushNotification(friendId, {
title: 'New friend request',
message: `${locals.user.username} sent you a friend request`,
url: '/friends',
tag: 'friend_request',
data: {
friendshipId: newFriendship[0].id,
senderId: locals.user.id
}
});
}
return json({ success: true, friendship: newFriendship[0] });
} catch (err) {
console.error('Error sending friend request:', err);
if (err instanceof Error && 'status' in err) {
throw err;
}
throw error(500, 'Failed to send friend request');
}
};