import { pgTable, integer, text, timestamp, boolean, jsonb, real } from 'drizzle-orm/pg-core'; export const user = pgTable('user', { id: text('id').primaryKey(), age: integer('age'), username: text('username').notNull().unique(), passwordHash: text('password_hash'), googleId: text('google_id').unique(), profilePictureUrl: text('profile_picture_url') }); export const session = pgTable('session', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => user.id), expiresAt: timestamp('expires_at', { withTimezone: true, mode: 'date' }).notNull() }); export type Session = typeof session.$inferSelect; export type User = typeof user.$inferSelect; // Location table - represents geographical points where finds can be made export const location = pgTable('location', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), latitude: text('latitude').notNull(), // Using text for precision longitude: text('longitude').notNull(), // Using text for precision locationName: text('location_name'), // e.g., "Café Belga, Brussels" averageRating: integer('average_rating'), // Average rating (1-5 scale, stored as integer * 100 for precision) ratingCount: integer('rating_count').default(0), // Total number of finds with ratings at this location createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); // Find table - represents posts/content made at a location export const find = pgTable('find', { id: text('id').primaryKey(), locationId: text('location_id') .notNull() .references(() => location.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), title: text('title').notNull(), description: text('description'), category: text('category'), // e.g., "cafe", "restaurant", "park", "landmark" rating: integer('rating'), // Average rating for this find (1-5 stars, stored as integer * 100) ratingCount: integer('rating_count').default(0), // Number of ratings for this find isPublic: integer('is_public').default(1), // Using integer for boolean (1 = true, 0 = false) createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const findMedia = pgTable('find_media', { id: text('id').primaryKey(), findId: text('find_id') .notNull() .references(() => find.id, { onDelete: 'cascade' }), type: text('type').notNull(), // 'photo' or 'video' url: text('url').notNull(), thumbnailUrl: text('thumbnail_url'), fallbackUrl: text('fallback_url'), // JPEG fallback for WebP images fallbackThumbnailUrl: text('fallback_thumbnail_url'), // JPEG fallback for WebP thumbnails orderIndex: integer('order_index').default(0), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const findLike = pgTable('find_like', { id: text('id').primaryKey(), findId: text('find_id') .notNull() .references(() => find.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const findRating = pgTable('find_rating', { id: text('id').primaryKey(), findId: text('find_id') .notNull() .references(() => find.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), rating: integer('rating').notNull(), // 1-5 stars (stored as 100-500 for precision) createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const friendship = pgTable('friendship', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), friendId: text('friend_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), status: text('status').notNull(), // 'pending', 'accepted', 'blocked' createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const findComment = pgTable('find_comment', { id: text('id').primaryKey(), findId: text('find_id') .notNull() .references(() => find.id, { onDelete: 'cascade' }), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), content: text('content').notNull(), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); // Notification system tables export const notification = pgTable('notification', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), type: text('type').notNull(), // 'friend_request', 'friend_accepted', 'find_liked', 'find_commented' title: text('title').notNull(), message: text('message').notNull(), data: jsonb('data'), // Additional context data (findId, friendId, etc.) isRead: boolean('is_read').default(false), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const notificationSubscription = pgTable('notification_subscription', { id: text('id').primaryKey(), userId: text('user_id') .notNull() .references(() => user.id, { onDelete: 'cascade' }), endpoint: text('endpoint').notNull(), p256dhKey: text('p256dh_key').notNull(), authKey: text('auth_key').notNull(), userAgent: text('user_agent'), isActive: boolean('is_active').default(true), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); export const notificationPreferences = pgTable('notification_preferences', { userId: text('user_id') .primaryKey() .references(() => user.id, { onDelete: 'cascade' }), friendRequests: boolean('friend_requests').default(true), friendAccepted: boolean('friend_accepted').default(true), findLiked: boolean('find_liked').default(true), findCommented: boolean('find_commented').default(true), pushEnabled: boolean('push_enabled').default(true), createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(), updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull() }); // Type exports for the tables export type Location = typeof location.$inferSelect; export type Find = typeof find.$inferSelect; export type FindMedia = typeof findMedia.$inferSelect; export type FindLike = typeof findLike.$inferSelect; export type FindRating = typeof findRating.$inferSelect; export type FindComment = typeof findComment.$inferSelect; export type Friendship = typeof friendship.$inferSelect; export type Notification = typeof notification.$inferSelect; export type NotificationSubscription = typeof notificationSubscription.$inferSelect; export type NotificationPreferences = typeof notificationPreferences.$inferSelect; export type LocationInsert = typeof location.$inferInsert; export type FindInsert = typeof find.$inferInsert; export type FindMediaInsert = typeof findMedia.$inferInsert; export type FindLikeInsert = typeof findLike.$inferInsert; export type FindRatingInsert = typeof findRating.$inferInsert; export type FindCommentInsert = typeof findComment.$inferInsert; export type FriendshipInsert = typeof friendship.$inferInsert; export type NotificationInsert = typeof notification.$inferInsert; export type NotificationSubscriptionInsert = typeof notificationSubscription.$inferInsert; export type NotificationPreferencesInsert = typeof notificationPreferences.$inferInsert;