feat:finds on homepage
This commit is contained in:
@@ -1,13 +1,155 @@
|
||||
import { redirect } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import { db } from '$lib/server/db';
|
||||
import { find, findMedia, user } from '$lib/server/db/schema';
|
||||
import { eq, and, sql, desc } from 'drizzle-orm';
|
||||
import { getSignedR2Url } from '$lib/server/r2';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (!event.locals.user) {
|
||||
// if not logged in, redirect to login page
|
||||
return redirect(302, '/login');
|
||||
export const load: PageServerLoad = async ({ locals, url }) => {
|
||||
if (!locals.user) {
|
||||
return redirect(302, '/login');
|
||||
}
|
||||
|
||||
return {
|
||||
user: event.locals.user
|
||||
};
|
||||
// Get query parameters for location-based filtering
|
||||
const lat = url.searchParams.get('lat');
|
||||
const lng = url.searchParams.get('lng');
|
||||
const radius = url.searchParams.get('radius') || '50'; // Default 50km radius
|
||||
|
||||
try {
|
||||
// Build where conditions
|
||||
const baseCondition = sql`(${find.isPublic} = 1 OR ${find.userId} = ${locals.user.id})`;
|
||||
let whereConditions = baseCondition;
|
||||
|
||||
// Add location filtering if coordinates provided
|
||||
if (lat && lng) {
|
||||
const radiusKm = parseFloat(radius);
|
||||
// Simple bounding box query for MVP (can upgrade to proper distance calculation later)
|
||||
const latOffset = radiusKm / 111; // Approximate degrees per km for latitude
|
||||
const lngOffset = radiusKm / (111 * Math.cos((parseFloat(lat) * Math.PI) / 180));
|
||||
|
||||
const locationConditions = and(
|
||||
baseCondition,
|
||||
sql`${find.latitude} BETWEEN ${parseFloat(lat) - latOffset} AND ${
|
||||
parseFloat(lat) + latOffset
|
||||
}`,
|
||||
sql`${find.longitude} BETWEEN ${parseFloat(lng) - lngOffset} AND ${
|
||||
parseFloat(lng) + lngOffset
|
||||
}`
|
||||
);
|
||||
|
||||
if (locationConditions) {
|
||||
whereConditions = locationConditions;
|
||||
}
|
||||
}
|
||||
|
||||
// Get all finds with filtering
|
||||
const finds = 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
|
||||
})
|
||||
.from(find)
|
||||
.innerJoin(user, eq(find.userId, user.id))
|
||||
.where(whereConditions)
|
||||
.orderBy(desc(find.createdAt))
|
||||
.limit(100);
|
||||
|
||||
// Get media for all finds
|
||||
const findIds = finds.map((f) => f.id);
|
||||
let media: Array<{
|
||||
id: string;
|
||||
findId: string;
|
||||
type: string;
|
||||
url: string;
|
||||
thumbnailUrl: string | null;
|
||||
orderIndex: number | null;
|
||||
}> = [];
|
||||
|
||||
if (findIds.length > 0) {
|
||||
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(
|
||||
sql`${findMedia.findId} IN (${sql.join(
|
||||
findIds.map((id) => sql`${id}`),
|
||||
sql`, `
|
||||
)})`
|
||||
)
|
||||
.orderBy(findMedia.orderIndex);
|
||||
}
|
||||
|
||||
// Group media by find
|
||||
const mediaByFind = media.reduce(
|
||||
(acc, item) => {
|
||||
if (!acc[item.findId]) {
|
||||
acc[item.findId] = [];
|
||||
}
|
||||
acc[item.findId].push(item);
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, typeof media>
|
||||
);
|
||||
|
||||
// Combine finds with their media and generate signed URLs
|
||||
const findsWithMedia = await Promise.all(
|
||||
finds.map(async (findItem) => {
|
||||
const findMedia = mediaByFind[findItem.id] || [];
|
||||
|
||||
// Generate signed URLs for all media items
|
||||
const mediaWithSignedUrls = await Promise.all(
|
||||
findMedia.map(async (mediaItem) => {
|
||||
// Extract path from URL if it's still a full URL, otherwise use as-is
|
||||
const path = mediaItem.url.startsWith('https://')
|
||||
? mediaItem.url.split('/').slice(3).join('/')
|
||||
: mediaItem.url;
|
||||
|
||||
const thumbnailPath = mediaItem.thumbnailUrl?.startsWith('https://')
|
||||
? mediaItem.thumbnailUrl.split('/').slice(3).join('/')
|
||||
: mediaItem.thumbnailUrl;
|
||||
|
||||
const [signedUrl, signedThumbnailUrl] = await Promise.all([
|
||||
getSignedR2Url(path, 24 * 60 * 60), // 24 hours
|
||||
thumbnailPath ? getSignedR2Url(thumbnailPath, 24 * 60 * 60) : Promise.resolve(null)
|
||||
]);
|
||||
|
||||
return {
|
||||
...mediaItem,
|
||||
url: signedUrl,
|
||||
thumbnailUrl: signedThumbnailUrl
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
...findItem,
|
||||
media: mediaWithSignedUrls
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
finds: findsWithMedia
|
||||
};
|
||||
} catch (err) {
|
||||
console.error('Error loading finds:', err);
|
||||
return {
|
||||
finds: []
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user