diff --git a/server/src/db/schema.ts b/server/src/db/schema.ts index f2de1cec7..a5dfde59c 100644 --- a/server/src/db/schema.ts +++ b/server/src/db/schema.ts @@ -15,7 +15,7 @@ import { import { createInsertSchema, createSelectSchema } from 'drizzle-zod'; import { createId } from '@paralleldrive/cuid2'; -interface OfflineMap { +export interface OfflineMapMetadata { name: string; styleURL: string; bounds: [number[], number[]]; @@ -43,7 +43,7 @@ export const user = sqliteTable('user', { mode: 'timestamp', }), offlineMaps: text('offline_maps', { mode: 'json' }).$type< - Record + Record >(), role: text('role', { enum: ['admin', 'user'] }) .default('user') @@ -145,7 +145,7 @@ export const pack = sqliteTable('pack', { // pkWithCustomName: primaryKey({ // name: 'id', // columns: [table.packId, table.ownerId], -// }), +// }), // }; // }, // ); @@ -279,11 +279,13 @@ export const offlineMap = sqliteTable( .primaryKey() .$defaultFn(() => createId()), name: text('name').notNull(), - bounds: text('bounds', { mode: 'json' }).$type(), + bounds: text('bounds', { mode: 'json' }).$type< + OfflineMapMetadata['bounds'] + >(), minZoom: integer('minZoom').notNull(), maxZoom: integer('maxZoom').notNull(), metadata: text('metadata', { mode: 'json' }).$type< - OfflineMap['metadata'] + OfflineMapMetadata['metadata'] >(), owner_id: text('owner_id').references(() => user.id, { onDelete: 'cascade', @@ -422,7 +424,9 @@ export const trip = sqliteTable('trip', { }), is_public: integer('is_public', { mode: 'boolean' }), activity: text('activity').default('trip'), - bounds: text('bounds', { mode: 'json' }).$type(), + bounds: text('bounds', { mode: 'json' }).$type< + OfflineMapMetadata['bounds'] + >(), type: text('type').default('trip'), scores: text('scores', { mode: 'json' }) .$type() @@ -651,3 +655,6 @@ export type Conversation = InferSelectModel; export type InsertConversation = InferInsertModel; export const insertConversationSchema = createInsertSchema(conversation); export const selectConversationSchema = createSelectSchema(conversation); + +export type OfflineMap = InferSelectModel; +export type InsertOfflineMap = InferInsertModel; diff --git a/server/src/modules/feed/controllers/getPublicFeed.ts b/server/src/modules/feed/controllers/getPublicFeed.ts index 85804f831..38a92c746 100644 --- a/server/src/modules/feed/controllers/getPublicFeed.ts +++ b/server/src/modules/feed/controllers/getPublicFeed.ts @@ -1,7 +1,11 @@ -import { getNextOffset, getPaginationResponse } from 'src/helpers/pagination'; +import { + getNextOffset, + getPaginationResponse, +} from '../../../helpers/pagination'; import { protectedProcedure } from '../../../trpc'; import { getFeedService } from '../services'; import { z } from 'zod'; +import { FeedQueryBy, PaginationParams } from '../models'; export function getPublicFeedRoute() { return protectedProcedure @@ -18,10 +22,10 @@ export function getPublicFeedRoute() { .query(async (opts) => { const { queryBy, searchTerm, excludeType, pagination } = opts.input; const { data, totalCount, currentPagination } = await getFeedService( - queryBy, + queryBy as FeedQueryBy, { searchTerm, isPublic: true }, excludeType, - pagination, + pagination as PaginationParams, ); return { data, diff --git a/server/src/modules/feed/controllers/getUserPacksFeed.ts b/server/src/modules/feed/controllers/getUserPacksFeed.ts index d419690be..25b496799 100644 --- a/server/src/modules/feed/controllers/getUserPacksFeed.ts +++ b/server/src/modules/feed/controllers/getUserPacksFeed.ts @@ -1,7 +1,8 @@ -import { getPaginationResponse } from 'src/helpers/pagination'; +import { getPaginationResponse } from '../../../helpers/pagination'; import { protectedProcedure } from '../../../trpc'; import { getFeedService } from '../services'; import { z } from 'zod'; +import { FeedQueryBy, PaginationParams } from '../models'; export function getUserPacksFeedRoute() { return protectedProcedure @@ -22,10 +23,10 @@ export function getUserPacksFeedRoute() { const { queryBy, searchTerm, ownerId, pagination, isPublic, itemId } = opts.input; const { data, totalCount, currentPagination } = await getFeedService( - queryBy, + queryBy as FeedQueryBy, { searchTerm, ownerId, isPublic, itemId }, 'trips', - pagination, + pagination as PaginationParams, ); return { data, diff --git a/server/src/modules/feed/controllers/getUserTripsFeed.ts b/server/src/modules/feed/controllers/getUserTripsFeed.ts index 2751840de..87b472cd3 100644 --- a/server/src/modules/feed/controllers/getUserTripsFeed.ts +++ b/server/src/modules/feed/controllers/getUserTripsFeed.ts @@ -2,10 +2,11 @@ import { getNextOffset, getPaginationParams, getPaginationResponse, -} from 'src/helpers/pagination'; +} from '../../../helpers/pagination'; import { protectedProcedure } from '../../../trpc'; import { getFeedService } from '../services'; import { z } from 'zod'; +import { FeedQueryBy, PaginationParams } from '../models'; export function getUserTripsFeedRoute() { return protectedProcedure @@ -24,10 +25,10 @@ export function getUserTripsFeedRoute() { .query(async (opts) => { const { queryBy, searchTerm, ownerId, pagination, isPublic } = opts.input; const { data, totalCount, currentPagination } = await getFeedService( - queryBy, + queryBy as FeedQueryBy, { searchTerm, ownerId, isPublic }, 'packs', - pagination, + pagination as PaginationParams, ); return { data, diff --git a/server/src/modules/feed/model/feed.ts b/server/src/modules/feed/model/feed.ts index 5540901e2..6c193fc58 100644 --- a/server/src/modules/feed/model/feed.ts +++ b/server/src/modules/feed/model/feed.ts @@ -1,4 +1,4 @@ -import { and, asc, desc, eq, like, sql } from 'drizzle-orm'; +import { and, asc, desc, eq, like, sql, SQL } from 'drizzle-orm'; import { DbClient } from '../../../db/client'; import { item, @@ -7,7 +7,7 @@ import { trip, userFavoritePacks, } from '../../../db/schema'; -import { literal } from 'src/drizzle/helpers'; +import { literal } from '../../../drizzle/helpers'; import { getPaginationParams, getPrevOffset, @@ -66,7 +66,7 @@ export class Feed { pagination?: PaginationParams, ) { try { - let packsQuery = DbClient.instance + let packsQuery: any = DbClient.instance .select({ id: pack.id, createdAt: sql`${pack.createdAt} as createdAt`, @@ -82,12 +82,12 @@ export class Feed { quantity: sql`COALESCE(SUM(${itemPacks.quantity}), 0)`, userFavorites: sql`GROUP_CONCAT(DISTINCT ${userFavoritePacks.userId}) as userFavorites`, total_weight: sql`COALESCE(SUM(${item.weight} * ${itemPacks.quantity}), 0) as total_weight`, - hasItem: modifiers.itemId + hasItem: modifiers?.itemId ? sql`CASE WHEN COUNT(DISTINCT CASE WHEN ${itemPacks.itemId} = ${modifiers.itemId} THEN 1 ELSE NULL END) > 0 THEN TRUE ELSE FALSE END as hasItem` - : literal(null), - activity: literal(null), - start_date: literal(null), - end_date: literal(null), + : sql`NULL as hasItem`, + activity: sql`NULL as activity`, + start_date: sql`NULL as start_date`, + end_date: sql`NULL as end_date`, }) .from(pack) .leftJoin(userFavoritePacks, eq(pack.id, userFavoritePacks.packId)) @@ -95,19 +95,22 @@ export class Feed { .leftJoin(item, eq(itemPacks.itemId, item.id)) .groupBy(pack.id); - if (modifiers.includeUserFavoritesOnly) { - packsQuery = packsQuery.having( - this.generateHavingConditions(modifiers, pack), - ); + if (modifiers?.includeUserFavoritesOnly) { + // Cast or conditionally apply .having() only if we have valid conditions + const havingConditions = this.generateHavingConditions(modifiers, pack); + if (havingConditions) { + packsQuery = (packsQuery as any).having(havingConditions as any); + } } if (modifiers) { - packsQuery = packsQuery.where( - this.generateWhereConditions(modifiers, pack), - ); + const whereConditions = this.generateWhereConditions(modifiers, pack); + if (whereConditions) { + packsQuery = (packsQuery as any).where(whereConditions as any); + } } - let tripsQuery = DbClient.instance + let tripsQuery: any = DbClient.instance .select({ id: trip.id, createdAt: sql`${trip.createdAt} as createdAt`, @@ -120,10 +123,10 @@ export class Feed { description: trip.description, destination: trip.destination, favorites_count: literal('0'), - quantity: literal(null), + quantity: sql`NULL as quantity`, userFavorites: literal('[]'), total_weight: literal('0'), - hasItem: literal(null), + hasItem: sql`NULL as hasItem`, activity: trip.activity, start_date: trip.start_date, end_date: trip.end_date, @@ -137,10 +140,10 @@ export class Feed { } if (excludeType === 'packs') { - packsQuery = null; + packsQuery = null as any; } if (excludeType === 'trips') { - tripsQuery = null; + tripsQuery = null as any; } packsQuery = this.applyPackOrders( @@ -149,9 +152,9 @@ export class Feed { tripsQuery === null, ); - let feedQuery = null; + let feedQuery: any = null; if (packsQuery && tripsQuery) { - feedQuery = packsQuery.union(tripsQuery); + feedQuery = (packsQuery as any).union(tripsQuery as any); } else if (packsQuery) { feedQuery = packsQuery; } else if (tripsQuery) { @@ -170,7 +173,9 @@ export class Feed { queryBy === 'Most Recent' ? desc(sql`createdAt`) : asc(sql`createdAt`); - feedQuery = feedQuery.orderBy(order); + if (feedQuery) { + feedQuery = feedQuery.orderBy(order); + } } const feedData = await feedQuery.limit(limit).offset(offset).all(); @@ -251,17 +256,17 @@ export class Feed { modifiers: Modifiers, table: typeof trip | typeof pack, ) { - const conditions = []; + const conditions: SQL[] = []; - if (modifiers.isPublic !== undefined) { + if (modifiers?.isPublic !== undefined) { conditions.push(eq(table.is_public, modifiers.isPublic)); } - if (modifiers.ownerId) { + if (modifiers?.ownerId) { conditions.push(eq(table.owner_id, modifiers.ownerId)); } - if (modifiers.searchTerm) { + if (modifiers?.searchTerm) { conditions.push(like(table.name, `%${modifiers.searchTerm}%`)); } @@ -272,9 +277,9 @@ export class Feed { modifiers: Modifiers, table: typeof trip | typeof pack, ) { - const conditions = []; + const conditions: SQL[] = []; - if (modifiers.ownerId && modifiers.includeUserFavoritesOnly) { + if (modifiers?.ownerId && modifiers.includeUserFavoritesOnly) { conditions.push( sql`userFavorites LIKE CONCAT('%', ${modifiers.ownerId}, '%')`, ); diff --git a/server/src/modules/feed/models.ts b/server/src/modules/feed/models.ts index daa1c5b27..8012f6e6d 100644 --- a/server/src/modules/feed/models.ts +++ b/server/src/modules/feed/models.ts @@ -12,3 +12,8 @@ export type FeedQueryBy = | 'Lightest' | 'Heaviest' | 'Oldest'; + +export interface PaginationParams { + limit: number; + offset: number; +} diff --git a/server/src/modules/feed/services/getFeedService.ts b/server/src/modules/feed/services/getFeedService.ts index 073c87831..1d77af6bb 100644 --- a/server/src/modules/feed/services/getFeedService.ts +++ b/server/src/modules/feed/services/getFeedService.ts @@ -2,16 +2,16 @@ import { PaginationParams } from '../../../helpers/pagination'; import { Feed } from '../model'; -import { Modifiers } from '../models'; +import { Modifiers, FeedQueryBy } from '../models'; /** * Retrieves public trips based on the given query parameter. * @param {PrismaClient} prisma - Prisma client. - * @param {string} queryBy - The query parameter to sort the trips. + * @param {FeedQueryBy} queryBy - The query parameter to sort the trips. * @return {Promise} The public trips. */ export const getFeedService = async ( - queryBy: string, + queryBy: FeedQueryBy, modifiers?: Modifiers, excludeType?: 'trips' | 'packs', pagination?: PaginationParams, diff --git a/server/src/modules/map/controllers/getOfflineMaps.ts b/server/src/modules/map/controllers/getOfflineMaps.ts index fbc1eed48..e4a417b47 100644 --- a/server/src/modules/map/controllers/getOfflineMaps.ts +++ b/server/src/modules/map/controllers/getOfflineMaps.ts @@ -1,25 +1,30 @@ -import { getPaginationResponse } from 'src/helpers/pagination'; +import { getPaginationResponse } from '../../../helpers/pagination'; import { protectedProcedure } from '../../../trpc'; import { getOfflineMapsService } from '../services'; import { z } from 'zod'; +import { OfflineMap } from '../../../db/schema'; export function getOfflineMapsRoute() { return protectedProcedure .input( z.object({ ownerId: z.string(), - pagination: z.object({ limit: z.number(), offset: z.number() }), + pagination: z + .object({ limit: z.number(), offset: z.number() }) + .strict(), }), ) - .query(async (opts) => { - const { pagination, ownerId } = opts.input; - const { offlineMaps, totalCount } = await getOfflineMapsService( - ownerId, - pagination, - ); - return { - data: offlineMaps, - ...getPaginationResponse(pagination, totalCount), - }; - }); + .query( + async (opts): Promise<{ data: OfflineMap[]; totalCount: number }> => { + const { pagination, ownerId } = opts.input; + const { offlineMaps, totalCount } = await getOfflineMapsService( + ownerId, + pagination as { limit: number; offset: number }, + ); + return { + data: offlineMaps, + ...getPaginationResponse(pagination, Number(totalCount)), + }; + }, + ); } diff --git a/server/src/modules/map/model/offlineMap.ts b/server/src/modules/map/model/offlineMap.ts index 56d314298..6ba126096 100644 --- a/server/src/modules/map/model/offlineMap.ts +++ b/server/src/modules/map/model/offlineMap.ts @@ -36,7 +36,10 @@ export class OfflineMap { try { const newOfflineMap = await DbClient.instance .insert(offlineMapTable) - .values(data) + .values({ + ...data, + metadata: data.metadata as any, + }) .returning() .get(); return newOfflineMap; diff --git a/server/src/modules/map/services/saveOfflineMapService.ts b/server/src/modules/map/services/saveOfflineMapService.ts index 6f98feb11..6acbb23df 100644 --- a/server/src/modules/map/services/saveOfflineMapService.ts +++ b/server/src/modules/map/services/saveOfflineMapService.ts @@ -1,7 +1,4 @@ -// services/tripService.ts - -import { GeojsonStorageService } from 'src/services/geojsonStorage'; -import { PaginationParams } from '../../../helpers/pagination'; +import { GeojsonStorageService } from '../../../services/geojsonStorage'; import { OfflineMap } from '../model'; import { OfflineMap as IOfflineMap } from '../models'; @@ -10,13 +7,14 @@ export const saveOfflineMapService = async ( executionCtx: ExecutionContext, ) => { try { - const { - metadata: { shape, ...metadata }, - ...offlineMapData - } = offlineMap; + const { metadata, ...offlineMapData } = offlineMap; + const { shape, ...restMetadata } = metadata as { + shape: string; + userId: string; + }; const offlineMapClass = new OfflineMap(); const newOfflineMap = await offlineMapClass.create({ - metadata, + metadata: restMetadata, ...offlineMapData, }); diff --git a/server/src/queue/client.ts b/server/src/queue/client.ts index e70339248..8b6040a78 100644 --- a/server/src/queue/client.ts +++ b/server/src/queue/client.ts @@ -18,6 +18,6 @@ export class Queue { } async addTask(task: QueueTask): Promise { - return this.queue.add(task); + return this.queue.add(task) as Promise; } } diff --git a/server/src/routes/geojsonRoutes.ts b/server/src/routes/geojsonRoutes.ts index 50fbf96d6..aec947537 100644 --- a/server/src/routes/geojsonRoutes.ts +++ b/server/src/routes/geojsonRoutes.ts @@ -3,7 +3,7 @@ import { tryCatchWrapper } from '../helpers/tryCatchWrapper'; import { GeojsonStorageService, type ResourceType, -} from 'src/services/geojsonStorage'; +} from '../services/geojsonStorage'; const router = new Hono(); @@ -15,7 +15,7 @@ router.get( const object = await GeojsonStorageService.retrieve( params.resource as ResourceType, - params.resourceId, + params.resourceId as string, ); if (!object) { @@ -33,7 +33,7 @@ router.get( ctx.header('Content-Type', 'application/geo+json'); const json = await object.json(); - return ctx.json(json); + return ctx.json(json as any); // Ensure json matches expected type } catch (error) { return ctx.json({ error: error.message }, 500); }