feat:big update to public finds

This commit is contained in:
2025-11-22 20:04:25 +01:00
parent 9f608067fc
commit 5285a15335
6 changed files with 862 additions and 38 deletions

View File

@@ -12,10 +12,6 @@ function generateFindId(): string {
}
export const GET: RequestHandler = async ({ url, locals }) => {
if (!locals.user) {
throw error(401, 'Unauthorized');
}
const lat = url.searchParams.get('lat');
const lng = url.searchParams.get('lng');
const radius = url.searchParams.get('radius') || '50';
@@ -25,9 +21,9 @@ export const GET: RequestHandler = async ({ url, locals }) => {
const includeFriends = url.searchParams.get('includeFriends') === 'true';
try {
// Get user's friends if needed
// Get user's friends if needed and user is logged in
let friendIds: string[] = [];
if (includeFriends || includePrivate) {
if (locals.user && (includeFriends || includePrivate)) {
const friendships = await db
.select({
userId: friendship.userId,
@@ -37,7 +33,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
.where(
and(
eq(friendship.status, 'accepted'),
or(eq(friendship.userId, locals.user!.id), eq(friendship.friendId, locals.user!.id))
or(eq(friendship.userId, locals.user.id), eq(friendship.friendId, locals.user.id))
)
);
@@ -47,12 +43,12 @@ export const GET: RequestHandler = async ({ url, locals }) => {
// Build privacy conditions
const conditions = [sql`${find.isPublic} = 1`]; // Always include public finds
if (includePrivate) {
if (locals.user && includePrivate) {
// Include user's own finds (both public and private)
conditions.push(sql`${find.userId} = ${locals.user!.id}`);
conditions.push(sql`${find.userId} = ${locals.user.id}`);
}
if (includeFriends && friendIds.length > 0) {
if (locals.user && includeFriends && friendIds.length > 0) {
// Include friends' finds (both public and private)
conditions.push(
sql`${find.userId} IN (${sql.join(
@@ -103,19 +99,23 @@ export const GET: RequestHandler = async ({ url, locals }) => {
username: user.username,
profilePictureUrl: user.profilePictureUrl,
likeCount: sql<number>`COALESCE(COUNT(DISTINCT ${findLike.id}), 0)`,
isLikedByUser: sql<boolean>`CASE WHEN EXISTS(
SELECT 1 FROM ${findLike}
WHERE ${findLike.findId} = ${find.id}
AND ${findLike.userId} = ${locals.user.id}
) THEN 1 ELSE 0 END`,
isFromFriend: sql<boolean>`CASE WHEN ${
friendIds.length > 0
? sql`${find.userId} IN (${sql.join(
friendIds.map((id) => sql`${id}`),
sql`, `
)})`
: sql`FALSE`
} THEN 1 ELSE 0 END`
isLikedByUser: locals.user
? sql<boolean>`CASE WHEN EXISTS(
SELECT 1 FROM ${findLike}
WHERE ${findLike.findId} = ${find.id}
AND ${findLike.userId} = ${locals.user.id}
) THEN 1 ELSE 0 END`
: sql<boolean>`0`,
isFromFriend: locals.user
? sql<boolean>`CASE WHEN ${
friendIds.length > 0
? sql`${find.userId} IN (${sql.join(
friendIds.map((id) => sql`${id}`),
sql`, `
)})`
: sql`FALSE`
} THEN 1 ELSE 0 END`
: sql<boolean>`0`
})
.from(find)
.innerJoin(user, eq(find.userId, user.id))

View File

@@ -0,0 +1,115 @@
import { json, error } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
import { db } from '$lib/server/db';
import { find, findMedia, user, findLike, findComment } from '$lib/server/db/schema';
import { eq, sql } from 'drizzle-orm';
import { getLocalR2Url } from '$lib/server/r2';
export const GET: RequestHandler = async ({ params, locals }) => {
const findId = params.findId;
if (!findId) {
throw error(400, 'Find ID is required');
}
try {
// Get the find with user info and like count
const findResult = await db
.select({
id: find.id,
title: find.title,
description: find.description,
latitude: find.latitude,
longitude: find.longitude,
locationName: find.locationName,
category: find.category,
isPublic: find.isPublic,
createdAt: find.createdAt,
userId: find.userId,
username: user.username,
profilePictureUrl: user.profilePictureUrl,
likeCount: sql<number>`COALESCE(COUNT(DISTINCT ${findLike.id}), 0)`,
commentCount: sql<number>`COALESCE((
SELECT COUNT(*) FROM ${findComment}
WHERE ${findComment.findId} = ${find.id}
), 0)`,
isLikedByUser: locals.user
? sql<boolean>`CASE WHEN EXISTS(
SELECT 1 FROM ${findLike}
WHERE ${findLike.findId} = ${find.id}
AND ${findLike.userId} = ${locals.user.id}
) THEN 1 ELSE 0 END`
: sql<boolean>`0`
})
.from(find)
.innerJoin(user, eq(find.userId, user.id))
.leftJoin(findLike, eq(find.id, findLike.findId))
.where(eq(find.id, findId))
.groupBy(find.id, user.username, user.profilePictureUrl)
.limit(1);
if (findResult.length === 0) {
throw error(404, 'Find not found');
}
const findData = findResult[0];
// Check if the find is public or if user has access
const isOwner = locals.user && findData.userId === locals.user.id;
const isPublic = findData.isPublic === 1;
if (!isPublic && !isOwner) {
throw error(403, 'This find is private');
}
// Get media for the find
const media = await db
.select({
id: findMedia.id,
findId: findMedia.findId,
type: findMedia.type,
url: findMedia.url,
thumbnailUrl: findMedia.thumbnailUrl,
orderIndex: findMedia.orderIndex
})
.from(findMedia)
.where(eq(findMedia.findId, findId))
.orderBy(findMedia.orderIndex);
// Generate signed URLs for media
const mediaWithSignedUrls = await Promise.all(
media.map(async (mediaItem) => {
const localUrl = getLocalR2Url(mediaItem.url);
const localThumbnailUrl =
mediaItem.thumbnailUrl && !mediaItem.thumbnailUrl.startsWith('/')
? getLocalR2Url(mediaItem.thumbnailUrl)
: mediaItem.thumbnailUrl;
return {
...mediaItem,
url: localUrl,
thumbnailUrl: localThumbnailUrl
};
})
);
// Generate local proxy URL for user profile picture
let userProfilePictureUrl = findData.profilePictureUrl;
if (userProfilePictureUrl && !userProfilePictureUrl.startsWith('http')) {
userProfilePictureUrl = getLocalR2Url(userProfilePictureUrl);
}
return json({
...findData,
profilePictureUrl: userProfilePictureUrl,
media: mediaWithSignedUrls,
isLikedByUser: Boolean(findData.isLikedByUser)
});
} catch (err) {
console.error('Error loading find:', err);
if (err instanceof Error && 'status' in err) {
throw err;
}
throw error(500, 'Failed to load find');
}
};