Compare commits
13 Commits
logic-over
...
abed2792dc
| Author | SHA1 | Date | |
|---|---|---|---|
|
abed2792dc
|
|||
|
5d45ec754a
|
|||
|
1a7703b63b
|
|||
|
b7eb7ad1ad
|
|||
|
81645a453a
|
|||
|
deebeb056f
|
|||
|
0c1c9d202d
|
|||
|
ae6a96d73b
|
|||
|
577a3cab56
|
|||
|
d67b9b7911
|
|||
|
e79d574359
|
|||
|
92457f90e8
|
|||
|
|
2122511959 |
32
.dockerignore
Normal file
32
.dockerignore
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
node_modules
|
||||||
|
.svelte-kit
|
||||||
|
build
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
!.env.docker
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.prettierrc
|
||||||
|
.prettierignore
|
||||||
|
.eslintrc
|
||||||
|
.editorconfig
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
pnpm-debug.log
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
*.log
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
*.swp
|
||||||
|
*.swo
|
||||||
|
*.swn
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
dist
|
||||||
|
logs
|
||||||
|
docker-compose.yml
|
||||||
|
Dockerfile
|
||||||
|
README.md
|
||||||
|
AGENTS.md
|
||||||
76
Dockerfile
Normal file
76
Dockerfile
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm ci
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Set build-time environment variables
|
||||||
|
ARG DATABASE_URL
|
||||||
|
ARG GOOGLE_CLIENT_ID
|
||||||
|
ARG GOOGLE_CLIENT_SECRET
|
||||||
|
ARG R2_ACCOUNT_ID
|
||||||
|
ARG R2_ACCESS_KEY_ID
|
||||||
|
ARG R2_SECRET_ACCESS_KEY
|
||||||
|
ARG R2_BUCKET_NAME
|
||||||
|
ARG GOOGLE_MAPS_API_KEY
|
||||||
|
ARG VAPID_PUBLIC_KEY
|
||||||
|
ARG VAPID_PRIVATE_KEY
|
||||||
|
ARG VAPID_SUBJECT
|
||||||
|
|
||||||
|
ENV DATABASE_URL=${DATABASE_URL}
|
||||||
|
ENV GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||||
|
ENV GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||||
|
ENV R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
|
||||||
|
ENV R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
|
||||||
|
ENV R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
|
||||||
|
ENV R2_BUCKET_NAME=${R2_BUCKET_NAME}
|
||||||
|
ENV GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}
|
||||||
|
ENV VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
|
||||||
|
ENV VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
|
||||||
|
ENV VAPID_SUBJECT=${VAPID_SUBJECT}
|
||||||
|
|
||||||
|
# Build the app
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json package-lock.json* ./
|
||||||
|
|
||||||
|
# Install production dependencies only
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
# Copy built app from builder
|
||||||
|
COPY --from=builder /app/build ./build
|
||||||
|
|
||||||
|
# Copy drizzle migrations and config
|
||||||
|
COPY --from=builder /app/drizzle ./drizzle
|
||||||
|
COPY --from=builder /app/drizzle.config.ts ./drizzle.config.ts
|
||||||
|
|
||||||
|
# Copy entrypoint script
|
||||||
|
COPY docker-entrypoint.sh /usr/local/bin/
|
||||||
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Set environment variables
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV ORIGIN=http://localhost:3000
|
||||||
|
|
||||||
|
# Use entrypoint script
|
||||||
|
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||||
|
|
||||||
|
# Start the app
|
||||||
|
CMD ["node", "build"]
|
||||||
64
docker-compose.yml
Normal file
64
docker-compose.yml
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:16-alpine
|
||||||
|
container_name: serengo-postgres
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: serengo
|
||||||
|
POSTGRES_PASSWORD: serengo_password
|
||||||
|
POSTGRES_DB: serengo
|
||||||
|
ports:
|
||||||
|
- '5432:5432'
|
||||||
|
volumes:
|
||||||
|
- postgres_data:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD-SHELL', 'pg_isready -U serengo']
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
- DATABASE_URL=postgresql://serengo:serengo_password@postgres:5432/serengo
|
||||||
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||||
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||||
|
- R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
|
||||||
|
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
|
||||||
|
- R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
|
||||||
|
- R2_BUCKET_NAME=${R2_BUCKET_NAME}
|
||||||
|
- GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}
|
||||||
|
- VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
|
||||||
|
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
|
||||||
|
- VAPID_SUBJECT=${VAPID_SUBJECT}
|
||||||
|
container_name: serengo-app
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
|
- DATABASE_URL=postgresql://serengo:serengo_password@postgres:5432/serengo
|
||||||
|
- ORIGIN=http://localhost:3000
|
||||||
|
# Add your environment variables here or use env_file
|
||||||
|
- GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID}
|
||||||
|
- GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET}
|
||||||
|
- R2_ACCOUNT_ID=${R2_ACCOUNT_ID}
|
||||||
|
- R2_ACCESS_KEY_ID=${R2_ACCESS_KEY_ID}
|
||||||
|
- R2_SECRET_ACCESS_KEY=${R2_SECRET_ACCESS_KEY}
|
||||||
|
- R2_BUCKET_NAME=${R2_BUCKET_NAME}
|
||||||
|
- GOOGLE_MAPS_API_KEY=${GOOGLE_MAPS_API_KEY}
|
||||||
|
- VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
|
||||||
|
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
|
||||||
|
- VAPID_SUBJECT=${VAPID_SUBJECT}
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
# Uncomment to use .env file
|
||||||
|
# env_file:
|
||||||
|
# - .env
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
postgres_data:
|
||||||
|
driver: local
|
||||||
30
docker-entrypoint.sh
Normal file
30
docker-entrypoint.sh
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Running database migrations..."
|
||||||
|
|
||||||
|
# Run migrations using the drizzle migration files
|
||||||
|
node -e "
|
||||||
|
const { drizzle } = require('drizzle-orm/postgres-js');
|
||||||
|
const postgres = require('postgres');
|
||||||
|
const { migrate } = require('drizzle-orm/postgres-js/migrator');
|
||||||
|
|
||||||
|
async function runMigrations() {
|
||||||
|
const migrationClient = postgres(process.env.DATABASE_URL, { max: 1 });
|
||||||
|
const db = drizzle(migrationClient);
|
||||||
|
|
||||||
|
console.log('Starting migration...');
|
||||||
|
await migrate(db, { migrationsFolder: './drizzle' });
|
||||||
|
console.log('Migration completed successfully!');
|
||||||
|
|
||||||
|
await migrationClient.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
runMigrations().catch((err) => {
|
||||||
|
console.error('Migration failed:', err);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
"
|
||||||
|
|
||||||
|
echo "Starting application..."
|
||||||
|
exec "$@"
|
||||||
15
drizzle/0008_common_supreme_intelligence.sql
Normal file
15
drizzle/0008_common_supreme_intelligence.sql
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
CREATE TABLE "location" (
|
||||||
|
"id" text PRIMARY KEY NOT NULL,
|
||||||
|
"user_id" text NOT NULL,
|
||||||
|
"latitude" text NOT NULL,
|
||||||
|
"longitude" text NOT NULL,
|
||||||
|
"location_name" text,
|
||||||
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
ALTER TABLE "find" ADD COLUMN "location_id" text NOT NULL;--> statement-breakpoint
|
||||||
|
ALTER TABLE "location" ADD CONSTRAINT "location_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "find" ADD CONSTRAINT "find_location_id_location_id_fk" FOREIGN KEY ("location_id") REFERENCES "public"."location"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
||||||
|
ALTER TABLE "find" DROP COLUMN "latitude";--> statement-breakpoint
|
||||||
|
ALTER TABLE "find" DROP COLUMN "longitude";--> statement-breakpoint
|
||||||
|
ALTER TABLE "find" DROP COLUMN "location_name";
|
||||||
829
drizzle/meta/0008_snapshot.json
Normal file
829
drizzle/meta/0008_snapshot.json
Normal file
@@ -0,0 +1,829 @@
|
|||||||
|
{
|
||||||
|
"id": "5654d58b-23f8-48cb-9933-5ac32141b75e",
|
||||||
|
"prevId": "1dbab94c-004e-4d34-b171-408bb1d36c91",
|
||||||
|
"version": "7",
|
||||||
|
"dialect": "postgresql",
|
||||||
|
"tables": {
|
||||||
|
"public.find": {
|
||||||
|
"name": "find",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"location_id": {
|
||||||
|
"name": "location_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"name": "description",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"category": {
|
||||||
|
"name": "category",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"is_public": {
|
||||||
|
"name": "is_public",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": 1
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"find_location_id_location_id_fk": {
|
||||||
|
"name": "find_location_id_location_id_fk",
|
||||||
|
"tableFrom": "find",
|
||||||
|
"tableTo": "location",
|
||||||
|
"columnsFrom": [
|
||||||
|
"location_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"find_user_id_user_id_fk": {
|
||||||
|
"name": "find_user_id_user_id_fk",
|
||||||
|
"tableFrom": "find",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.find_comment": {
|
||||||
|
"name": "find_comment",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"find_id": {
|
||||||
|
"name": "find_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"find_comment_find_id_find_id_fk": {
|
||||||
|
"name": "find_comment_find_id_find_id_fk",
|
||||||
|
"tableFrom": "find_comment",
|
||||||
|
"tableTo": "find",
|
||||||
|
"columnsFrom": [
|
||||||
|
"find_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"find_comment_user_id_user_id_fk": {
|
||||||
|
"name": "find_comment_user_id_user_id_fk",
|
||||||
|
"tableFrom": "find_comment",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.find_like": {
|
||||||
|
"name": "find_like",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"find_id": {
|
||||||
|
"name": "find_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"find_like_find_id_find_id_fk": {
|
||||||
|
"name": "find_like_find_id_find_id_fk",
|
||||||
|
"tableFrom": "find_like",
|
||||||
|
"tableTo": "find",
|
||||||
|
"columnsFrom": [
|
||||||
|
"find_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"find_like_user_id_user_id_fk": {
|
||||||
|
"name": "find_like_user_id_user_id_fk",
|
||||||
|
"tableFrom": "find_like",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.find_media": {
|
||||||
|
"name": "find_media",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"find_id": {
|
||||||
|
"name": "find_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"name": "url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"thumbnail_url": {
|
||||||
|
"name": "thumbnail_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"fallback_url": {
|
||||||
|
"name": "fallback_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"fallback_thumbnail_url": {
|
||||||
|
"name": "fallback_thumbnail_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"order_index": {
|
||||||
|
"name": "order_index",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"find_media_find_id_find_id_fk": {
|
||||||
|
"name": "find_media_find_id_find_id_fk",
|
||||||
|
"tableFrom": "find_media",
|
||||||
|
"tableTo": "find",
|
||||||
|
"columnsFrom": [
|
||||||
|
"find_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.friendship": {
|
||||||
|
"name": "friendship",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"friend_id": {
|
||||||
|
"name": "friend_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"friendship_user_id_user_id_fk": {
|
||||||
|
"name": "friendship_user_id_user_id_fk",
|
||||||
|
"tableFrom": "friendship",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
},
|
||||||
|
"friendship_friend_id_user_id_fk": {
|
||||||
|
"name": "friendship_friend_id_user_id_fk",
|
||||||
|
"tableFrom": "friendship",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"friend_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.location": {
|
||||||
|
"name": "location",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"latitude": {
|
||||||
|
"name": "latitude",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"longitude": {
|
||||||
|
"name": "longitude",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"location_name": {
|
||||||
|
"name": "location_name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"location_user_id_user_id_fk": {
|
||||||
|
"name": "location_user_id_user_id_fk",
|
||||||
|
"tableFrom": "location",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.notification": {
|
||||||
|
"name": "notification",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"name": "type",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"name": "title",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"name": "message",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"name": "data",
|
||||||
|
"type": "jsonb",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"is_read": {
|
||||||
|
"name": "is_read",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"notification_user_id_user_id_fk": {
|
||||||
|
"name": "notification_user_id_user_id_fk",
|
||||||
|
"tableFrom": "notification",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.notification_preferences": {
|
||||||
|
"name": "notification_preferences",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"friend_requests": {
|
||||||
|
"name": "friend_requests",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"friend_accepted": {
|
||||||
|
"name": "friend_accepted",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"find_liked": {
|
||||||
|
"name": "find_liked",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"find_commented": {
|
||||||
|
"name": "find_commented",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"push_enabled": {
|
||||||
|
"name": "push_enabled",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"notification_preferences_user_id_user_id_fk": {
|
||||||
|
"name": "notification_preferences_user_id_user_id_fk",
|
||||||
|
"tableFrom": "notification_preferences",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.notification_subscription": {
|
||||||
|
"name": "notification_subscription",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"name": "endpoint",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"p256dh_key": {
|
||||||
|
"name": "p256dh_key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"auth_key": {
|
||||||
|
"name": "auth_key",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
"name": "user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"is_active": {
|
||||||
|
"name": "is_active",
|
||||||
|
"type": "boolean",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"default": "now()"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"notification_subscription_user_id_user_id_fk": {
|
||||||
|
"name": "notification_subscription_user_id_user_id_fk",
|
||||||
|
"tableFrom": "notification_subscription",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.session": {
|
||||||
|
"name": "session",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "timestamp with time zone",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_user_id_user_id_fk": {
|
||||||
|
"name": "session_user_id_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
},
|
||||||
|
"public.user": {
|
||||||
|
"name": "user",
|
||||||
|
"schema": "",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"age": {
|
||||||
|
"name": "age",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"name": "username",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
"password_hash": {
|
||||||
|
"name": "password_hash",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"google_id": {
|
||||||
|
"name": "google_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
"profile_picture_url": {
|
||||||
|
"name": "profile_picture_url",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {
|
||||||
|
"user_username_unique": {
|
||||||
|
"name": "user_username_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"username"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"user_google_id_unique": {
|
||||||
|
"name": "user_google_id_unique",
|
||||||
|
"nullsNotDistinct": false,
|
||||||
|
"columns": [
|
||||||
|
"google_id"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"policies": {},
|
||||||
|
"checkConstraints": {},
|
||||||
|
"isRLSEnabled": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"enums": {},
|
||||||
|
"schemas": {},
|
||||||
|
"sequences": {},
|
||||||
|
"roles": {},
|
||||||
|
"policies": {},
|
||||||
|
"views": {},
|
||||||
|
"_meta": {
|
||||||
|
"columns": {},
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -57,6 +57,13 @@
|
|||||||
"when": 1762522687342,
|
"when": 1762522687342,
|
||||||
"tag": "0007_grey_dark_beast",
|
"tag": "0007_grey_dark_beast",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 8,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1765885558230,
|
||||||
|
"tag": "0008_common_supreme_intelligence",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -25,6 +25,7 @@
|
|||||||
"@lucide/svelte": "^0.544.0",
|
"@lucide/svelte": "^0.544.0",
|
||||||
"@oslojs/crypto": "^1.0.1",
|
"@oslojs/crypto": "^1.0.1",
|
||||||
"@oslojs/encoding": "^1.1.0",
|
"@oslojs/encoding": "^1.1.0",
|
||||||
|
"@sveltejs/adapter-node": "^5.4.0",
|
||||||
"@sveltejs/kit": "^2.22.0",
|
"@sveltejs/kit": "^2.22.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
"@sveltejs/vite-plugin-svelte": "^6.0.0",
|
||||||
"@tailwindcss/vite": "^4.1.13",
|
"@tailwindcss/vite": "^4.1.13",
|
||||||
@@ -32,7 +33,6 @@
|
|||||||
"bits-ui": "^2.11.4",
|
"bits-ui": "^2.11.4",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"drizzle-kit": "^0.30.2",
|
"drizzle-kit": "^0.30.2",
|
||||||
"drizzle-orm": "^0.40.0",
|
|
||||||
"eslint": "^9.22.0",
|
"eslint": "^9.22.0",
|
||||||
"eslint-config-prettier": "^10.0.1",
|
"eslint-config-prettier": "^10.0.1",
|
||||||
"eslint-plugin-storybook": "^9.1.8",
|
"eslint-plugin-storybook": "^9.1.8",
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
"@node-rs/argon2": "^2.0.2",
|
"@node-rs/argon2": "^2.0.2",
|
||||||
"@sveltejs/adapter-vercel": "^5.10.2",
|
"@sveltejs/adapter-vercel": "^5.10.2",
|
||||||
"arctic": "^3.7.0",
|
"arctic": "^3.7.0",
|
||||||
|
"drizzle-orm": "^0.40.0",
|
||||||
"lucide-svelte": "^0.553.0",
|
"lucide-svelte": "^0.553.0",
|
||||||
"nanoid": "^5.1.6",
|
"nanoid": "^5.1.6",
|
||||||
"postgres": "^3.4.5",
|
"postgres": "^3.4.5",
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
!origin ||
|
!origin ||
|
||||||
origin.includes('localhost') ||
|
origin.includes('localhost') ||
|
||||||
origin.includes('127.0.0.1') ||
|
origin.includes('127.0.0.1') ||
|
||||||
origin.includes('serengo.ziasvannes.tech')
|
origin.includes('serengo.zias.be')
|
||||||
) {
|
) {
|
||||||
// Allow in development and serengo.ziasvannes.tech
|
// Allow in development and serengo.zias.be
|
||||||
}
|
}
|
||||||
// In production, you would add: else if (origin !== 'yourdomain.com') { return new Response('Forbidden', { status: 403 }); }
|
// In production, you would add: else if (origin !== 'yourdomain.com') { return new Response('Forbidden', { status: 403 }); }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
|
|
||||||
let title = $state('');
|
let title = $state('');
|
||||||
let description = $state('');
|
let description = $state('');
|
||||||
let locationName = $state('');
|
|
||||||
let category = $state('cafe');
|
let category = $state('cafe');
|
||||||
let isPublic = $state(true);
|
let isPublic = $state(true);
|
||||||
let selectedFiles = $state<FileList | null>(null);
|
let selectedFiles = $state<FileList | null>(null);
|
||||||
@@ -93,7 +92,6 @@
|
|||||||
locationId,
|
locationId,
|
||||||
title: title.trim(),
|
title: title.trim(),
|
||||||
description: description.trim() || null,
|
description: description.trim() || null,
|
||||||
locationName: locationName.trim() || null,
|
|
||||||
category,
|
category,
|
||||||
isPublic,
|
isPublic,
|
||||||
media: uploadedMedia
|
media: uploadedMedia
|
||||||
@@ -118,7 +116,6 @@
|
|||||||
function resetForm() {
|
function resetForm() {
|
||||||
title = '';
|
title = '';
|
||||||
description = '';
|
description = '';
|
||||||
locationName = '';
|
|
||||||
category = 'cafe';
|
category = 'cafe';
|
||||||
isPublic = true;
|
isPublic = true;
|
||||||
selectedFiles = null;
|
selectedFiles = null;
|
||||||
@@ -177,15 +174,6 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="field">
|
|
||||||
<Label for="location-name">Location name (optional)</Label>
|
|
||||||
<Input
|
|
||||||
name="location-name"
|
|
||||||
placeholder="Café Central, Brussels"
|
|
||||||
bind:value={locationName}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<Label for="category">Category</Label>
|
<Label for="category">Category</Label>
|
||||||
@@ -279,7 +267,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -459,7 +459,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -254,6 +254,7 @@
|
|||||||
<style>
|
<style>
|
||||||
.find-card {
|
.find-card {
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
|
margin-top: 1rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
let latitude = $state('');
|
let latitude = $state('');
|
||||||
let longitude = $state('');
|
let longitude = $state('');
|
||||||
|
let locationName = $state('');
|
||||||
let isSubmitting = $state(false);
|
let isSubmitting = $state(false);
|
||||||
let useManualLocation = $state(false);
|
let useManualLocation = $state(false);
|
||||||
|
|
||||||
@@ -59,7 +60,8 @@
|
|||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
latitude: lat,
|
latitude: lat,
|
||||||
longitude: lng
|
longitude: lng,
|
||||||
|
locationName: locationName.trim() || null
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -85,6 +87,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePlaceSelected(place: PlaceResult) {
|
function handlePlaceSelected(place: PlaceResult) {
|
||||||
|
locationName = place.name;
|
||||||
latitude = place.latitude.toString();
|
latitude = place.latitude.toString();
|
||||||
longitude = place.longitude.toString();
|
longitude = place.longitude.toString();
|
||||||
}
|
}
|
||||||
@@ -100,6 +103,7 @@
|
|||||||
function resetForm() {
|
function resetForm() {
|
||||||
latitude = '';
|
latitude = '';
|
||||||
longitude = '';
|
longitude = '';
|
||||||
|
locationName = '';
|
||||||
useManualLocation = false;
|
useManualLocation = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +162,16 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="field">
|
||||||
|
<Label for="location-name">Location Name (Optional)</Label>
|
||||||
|
<Input
|
||||||
|
name="location-name"
|
||||||
|
type="text"
|
||||||
|
placeholder="Café Central, Brussels"
|
||||||
|
bind:value={locationName}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{#if useManualLocation || (!latitude && !longitude)}
|
{#if useManualLocation || (!latitude && !longitude)}
|
||||||
<div class="field-group">
|
<div class="field-group">
|
||||||
<div class="field">
|
<div class="field">
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FindsList from '../finds/FindsList.svelte';
|
import FindsList from '../finds/FindsList.svelte';
|
||||||
import { Button } from '$lib/components/button';
|
import { Button } from '$lib/components/button';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import EditFindModal from '../finds/EditFindModal.svelte';
|
||||||
|
|
||||||
interface Find {
|
interface Find {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -16,6 +18,8 @@
|
|||||||
likeCount?: number;
|
likeCount?: number;
|
||||||
isLikedByUser?: boolean;
|
isLikedByUser?: boolean;
|
||||||
isFromFriend?: boolean;
|
isFromFriend?: boolean;
|
||||||
|
latitude?: string;
|
||||||
|
longitude?: string;
|
||||||
media?: Array<{
|
media?: Array<{
|
||||||
id: string;
|
id: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -47,6 +51,8 @@
|
|||||||
let { isOpen, location, currentUserId, onClose, onCreateFind }: Props = $props();
|
let { isOpen, location, currentUserId, onClose, onCreateFind }: Props = $props();
|
||||||
|
|
||||||
let isMobile = $state(false);
|
let isMobile = $state(false);
|
||||||
|
let showEditModal = $state(false);
|
||||||
|
let findToEdit = $state<Find | null>(null);
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (typeof window === 'undefined') return;
|
if (typeof window === 'undefined') return;
|
||||||
@@ -64,6 +70,35 @@
|
|||||||
function handleCreateFind() {
|
function handleCreateFind() {
|
||||||
onCreateFind?.();
|
onCreateFind?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleFindExplore(findId: string) {
|
||||||
|
goto(`/finds/${findId}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFindEdit(findData: any) {
|
||||||
|
const find = location?.finds?.find((f) => f.id === findData.id);
|
||||||
|
if (find) {
|
||||||
|
findToEdit = find;
|
||||||
|
showEditModal = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEditModalClose() {
|
||||||
|
showEditModal = false;
|
||||||
|
findToEdit = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFindUpdated() {
|
||||||
|
showEditModal = false;
|
||||||
|
findToEdit = null;
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFindDeleted() {
|
||||||
|
showEditModal = false;
|
||||||
|
findToEdit = null;
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isOpen && location}
|
{#if isOpen && location}
|
||||||
@@ -94,12 +129,17 @@
|
|||||||
<FindsList
|
<FindsList
|
||||||
finds={location.finds.map((find) => ({
|
finds={location.finds.map((find) => ({
|
||||||
id: find.id,
|
id: find.id,
|
||||||
|
locationId: find.locationId,
|
||||||
title: find.title,
|
title: find.title,
|
||||||
description: find.description,
|
description: find.description,
|
||||||
category: find.category,
|
category: find.category,
|
||||||
locationName: find.locationName,
|
locationName: find.locationName,
|
||||||
|
latitude: find.latitude,
|
||||||
|
longitude: find.longitude,
|
||||||
isPublic: find.isPublic,
|
isPublic: find.isPublic,
|
||||||
userId: find.userId,
|
userId: find.userId,
|
||||||
|
username: find.username,
|
||||||
|
profilePictureUrl: find.profilePictureUrl,
|
||||||
user: {
|
user: {
|
||||||
username: find.username,
|
username: find.username,
|
||||||
profilePictureUrl: find.profilePictureUrl
|
profilePictureUrl: find.profilePictureUrl
|
||||||
@@ -110,6 +150,8 @@
|
|||||||
}))}
|
}))}
|
||||||
hideTitle={true}
|
hideTitle={true}
|
||||||
{currentUserId}
|
{currentUserId}
|
||||||
|
onFindExplore={handleFindExplore}
|
||||||
|
onEdit={handleFindEdit}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
@@ -147,6 +189,32 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if showEditModal && findToEdit}
|
||||||
|
<EditFindModal
|
||||||
|
isOpen={showEditModal}
|
||||||
|
find={{
|
||||||
|
id: findToEdit.id,
|
||||||
|
title: findToEdit.title,
|
||||||
|
description: findToEdit.description || null,
|
||||||
|
latitude: findToEdit.latitude || location?.latitude || '0',
|
||||||
|
longitude: findToEdit.longitude || location?.longitude || '0',
|
||||||
|
locationName: findToEdit.locationName || null,
|
||||||
|
category: findToEdit.category || null,
|
||||||
|
isPublic: findToEdit.isPublic ?? 1,
|
||||||
|
media: (findToEdit.media || []).map((m) => ({
|
||||||
|
id: m.id,
|
||||||
|
type: m.type,
|
||||||
|
url: m.url,
|
||||||
|
thumbnailUrl: m.thumbnailUrl || null,
|
||||||
|
orderIndex: m.orderIndex ?? null
|
||||||
|
}))
|
||||||
|
}}
|
||||||
|
onClose={handleEditModalClose}
|
||||||
|
onFindUpdated={handleFindUpdated}
|
||||||
|
onFindDeleted={handleFindDeleted}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.modal-container {
|
.modal-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -155,7 +223,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -104,6 +104,8 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
max-height: calc(100vh - 200px);
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
|
|||||||
@@ -560,7 +560,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -273,7 +273,7 @@
|
|||||||
top: 100%;
|
top: 100%;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
background: hsl(var(--background));
|
background: hsl(var(--background) / 0.95);
|
||||||
border: 1px solid hsl(var(--border));
|
border: 1px solid hsl(var(--border));
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestions-header {
|
.suggestions-header {
|
||||||
@@ -290,7 +290,7 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: hsl(var(--muted-foreground));
|
color: hsl(var(--muted-foreground));
|
||||||
border-bottom: 1px solid hsl(var(--border));
|
border-bottom: 1px solid hsl(var(--border));
|
||||||
background: hsl(var(--muted));
|
background: hsl(var(--muted) / 0.95);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +301,7 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border: none;
|
border: none;
|
||||||
background: hsl(var(--background));
|
background: hsl(var(--background) / 0.95);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color 0.2s ease;
|
transition: background-color 0.2s ease;
|
||||||
@@ -314,7 +314,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.suggestion-item:hover:not(:disabled) {
|
.suggestion-item:hover:not(:disabled) {
|
||||||
background: hsl(var(--muted) / 0.5);
|
background: hsl(var(--muted) / 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
.suggestion-item:disabled {
|
.suggestion-item:disabled {
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export const location = pgTable('location', {
|
|||||||
.references(() => user.id, { onDelete: 'cascade' }),
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
latitude: text('latitude').notNull(), // Using text for precision
|
latitude: text('latitude').notNull(), // Using text for precision
|
||||||
longitude: text('longitude').notNull(), // Using text for precision
|
longitude: text('longitude').notNull(), // Using text for precision
|
||||||
|
locationName: text('location_name'), // e.g., "Café Belga, Brussels"
|
||||||
createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull()
|
createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -43,7 +44,6 @@ export const find = pgTable('find', {
|
|||||||
.references(() => user.id, { onDelete: 'cascade' }),
|
.references(() => user.id, { onDelete: 'cascade' }),
|
||||||
title: text('title').notNull(),
|
title: text('title').notNull(),
|
||||||
description: text('description'),
|
description: text('description'),
|
||||||
locationName: text('location_name'), // e.g., "Café Belga, Brussels"
|
|
||||||
category: text('category'), // e.g., "cafe", "restaurant", "park", "landmark"
|
category: text('category'), // e.g., "cafe", "restaurant", "park", "landmark"
|
||||||
isPublic: integer('is_public').default(1), // Using integer for boolean (1 = true, 0 = false)
|
isPublic: integer('is_public').default(1), // Using integer for boolean (1 = true, 0 = false)
|
||||||
createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(),
|
createdAt: timestamp('created_at', { withTimezone: true, mode: 'date' }).defaultNow().notNull(),
|
||||||
|
|||||||
@@ -4,5 +4,5 @@ import { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } from '$env/static/private';
|
|||||||
export const google = new Google(
|
export const google = new Google(
|
||||||
GOOGLE_CLIENT_ID,
|
GOOGLE_CLIENT_ID,
|
||||||
GOOGLE_CLIENT_SECRET,
|
GOOGLE_CLIENT_SECRET,
|
||||||
'https://serengo.ziasvannes.tech/login/google/callback'
|
'https://serengo.zias.be/login/google/callback'
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -4,23 +4,12 @@
|
|||||||
import { Header } from '$lib';
|
import { Header } from '$lib';
|
||||||
import { page } from '$app/state';
|
import { page } from '$app/state';
|
||||||
import { Toaster } from '$lib/components/sonner/index.js';
|
import { Toaster } from '$lib/components/sonner/index.js';
|
||||||
import { Skeleton } from '$lib/components/skeleton';
|
|
||||||
import LocationManager from '$lib/components/map/LocationManager.svelte';
|
import LocationManager from '$lib/components/map/LocationManager.svelte';
|
||||||
import NotificationManager from '$lib/components/notifications/NotificationManager.svelte';
|
import NotificationManager from '$lib/components/notifications/NotificationManager.svelte';
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { browser } from '$app/environment';
|
|
||||||
|
|
||||||
let { children, data } = $props();
|
let { children, data } = $props();
|
||||||
let isLoginRoute = $derived(page.url.pathname.startsWith('/login'));
|
let isLoginRoute = $derived(page.url.pathname.startsWith('/login'));
|
||||||
let showHeader = $derived(!isLoginRoute && data?.user);
|
let showHeader = $derived(!isLoginRoute && data?.user);
|
||||||
let isLoading = $state(false);
|
|
||||||
|
|
||||||
// Handle loading state only on client to prevent hydration mismatch
|
|
||||||
onMount(() => {
|
|
||||||
if (browser) {
|
|
||||||
isLoading = !isLoginRoute && !data?.user && data !== null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -51,40 +40,6 @@
|
|||||||
|
|
||||||
{#if showHeader && data.user}
|
{#if showHeader && data.user}
|
||||||
<Header user={data.user} />
|
<Header user={data.user} />
|
||||||
{:else if isLoading}
|
|
||||||
<header class="header-skeleton">
|
|
||||||
<div class="header-content">
|
|
||||||
<Skeleton class="h-8 w-32" />
|
|
||||||
<Skeleton class="h-10 w-10 rounded-full" />
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{@render children?.()}
|
{@render children?.()}
|
||||||
|
|
||||||
<style>
|
|
||||||
.header-skeleton {
|
|
||||||
padding: 0 20px;
|
|
||||||
height: 64px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
position: relative;
|
|
||||||
z-index: 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content {
|
|
||||||
max-width: 1200px;
|
|
||||||
width: 100%;
|
|
||||||
margin: 0 auto;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.header-skeleton {
|
|
||||||
padding: 0 16px;
|
|
||||||
height: 56px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -321,7 +321,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
|||||||
locationId: find.locationId,
|
locationId: find.locationId,
|
||||||
title: find.title,
|
title: find.title,
|
||||||
description: find.description,
|
description: find.description,
|
||||||
locationName: find.locationName,
|
locationName: location.locationName,
|
||||||
category: find.category,
|
category: find.category,
|
||||||
isPublic: find.isPublic,
|
isPublic: find.isPublic,
|
||||||
createdAt: find.createdAt,
|
createdAt: find.createdAt,
|
||||||
@@ -98,9 +98,10 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
|||||||
})
|
})
|
||||||
.from(find)
|
.from(find)
|
||||||
.innerJoin(user, eq(find.userId, user.id))
|
.innerJoin(user, eq(find.userId, user.id))
|
||||||
|
.innerJoin(location, eq(find.locationId, location.id))
|
||||||
.leftJoin(findLike, eq(find.id, findLike.findId))
|
.leftJoin(findLike, eq(find.id, findLike.findId))
|
||||||
.where(whereConditions)
|
.where(whereConditions)
|
||||||
.groupBy(find.id, user.username, user.profilePictureUrl)
|
.groupBy(find.id, user.username, user.profilePictureUrl, location.locationName)
|
||||||
.orderBy(order === 'desc' ? desc(find.createdAt) : find.createdAt);
|
.orderBy(order === 'desc' ? desc(find.createdAt) : find.createdAt);
|
||||||
|
|
||||||
// Get media for all finds
|
// Get media for all finds
|
||||||
@@ -198,7 +199,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await request.json();
|
const data = await request.json();
|
||||||
const { locationId, title, description, locationName, category, isPublic, media } = data;
|
const { locationId, title, description, category, isPublic, media } = data;
|
||||||
|
|
||||||
if (!title || !locationId) {
|
if (!title || !locationId) {
|
||||||
throw error(400, 'Title and locationId are required');
|
throw error(400, 'Title and locationId are required');
|
||||||
@@ -234,7 +235,6 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
userId: locals.user.id,
|
userId: locals.user.id,
|
||||||
title,
|
title,
|
||||||
description,
|
description,
|
||||||
locationName,
|
|
||||||
category,
|
category,
|
||||||
isPublic: isPublic ? 1 : 0
|
isPublic: isPublic ? 1 : 0
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const GET: RequestHandler = async ({ params, locals }) => {
|
|||||||
description: find.description,
|
description: find.description,
|
||||||
latitude: location.latitude,
|
latitude: location.latitude,
|
||||||
longitude: location.longitude,
|
longitude: location.longitude,
|
||||||
locationName: find.locationName,
|
locationName: location.locationName,
|
||||||
category: find.category,
|
category: find.category,
|
||||||
isPublic: find.isPublic,
|
isPublic: find.isPublic,
|
||||||
createdAt: find.createdAt,
|
createdAt: find.createdAt,
|
||||||
@@ -50,6 +50,7 @@ export const GET: RequestHandler = async ({ params, locals }) => {
|
|||||||
find.id,
|
find.id,
|
||||||
location.latitude,
|
location.latitude,
|
||||||
location.longitude,
|
location.longitude,
|
||||||
|
location.locationName,
|
||||||
user.username,
|
user.username,
|
||||||
user.profilePictureUrl
|
user.profilePictureUrl
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
|||||||
id: location.id,
|
id: location.id,
|
||||||
latitude: location.latitude,
|
latitude: location.latitude,
|
||||||
longitude: location.longitude,
|
longitude: location.longitude,
|
||||||
|
locationName: location.locationName,
|
||||||
createdAt: location.createdAt,
|
createdAt: location.createdAt,
|
||||||
userId: location.userId,
|
userId: location.userId,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
@@ -109,7 +110,6 @@ export const GET: RequestHandler = async ({ url, locals }) => {
|
|||||||
id: find.id,
|
id: find.id,
|
||||||
title: find.title,
|
title: find.title,
|
||||||
description: find.description,
|
description: find.description,
|
||||||
locationName: find.locationName,
|
|
||||||
category: find.category,
|
category: find.category,
|
||||||
isPublic: find.isPublic,
|
isPublic: find.isPublic,
|
||||||
createdAt: find.createdAt,
|
createdAt: find.createdAt,
|
||||||
@@ -239,7 +239,7 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const data = await request.json();
|
const data = await request.json();
|
||||||
const { latitude, longitude } = data;
|
const { latitude, longitude, locationName } = data;
|
||||||
|
|
||||||
if (!latitude || !longitude) {
|
if (!latitude || !longitude) {
|
||||||
throw error(400, 'Latitude and longitude are required');
|
throw error(400, 'Latitude and longitude are required');
|
||||||
@@ -254,7 +254,8 @@ export const POST: RequestHandler = async ({ request, locals }) => {
|
|||||||
id: locationId,
|
id: locationId,
|
||||||
userId: locals.user.id,
|
userId: locals.user.id,
|
||||||
latitude: latitude.toString(),
|
latitude: latitude.toString(),
|
||||||
longitude: longitude.toString()
|
longitude: longitude.toString(),
|
||||||
|
locationName: locationName || null
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,34 @@
|
|||||||
|
|
||||||
// Get first media for OG image
|
// Get first media for OG image
|
||||||
let ogImage = $derived(data.find?.media?.[0]?.url || '');
|
let ogImage = $derived(data.find?.media?.[0]?.url || '');
|
||||||
|
|
||||||
|
// Convert find to location format for map marker
|
||||||
|
let findAsLocation = $derived(
|
||||||
|
data.find
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
id: data.find.id,
|
||||||
|
latitude: data.find.latitude,
|
||||||
|
longitude: data.find.longitude,
|
||||||
|
createdAt: new Date(data.find.createdAt),
|
||||||
|
userId: data.find.userId,
|
||||||
|
user: {
|
||||||
|
id: data.find.userId,
|
||||||
|
username: data.find.username
|
||||||
|
},
|
||||||
|
finds: [
|
||||||
|
{
|
||||||
|
id: data.find.id,
|
||||||
|
title: data.find.title,
|
||||||
|
description: data.find.description || undefined,
|
||||||
|
isPublic: data.find.isPublic ?? 1,
|
||||||
|
media: data.find.media || []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: []
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@@ -164,8 +192,10 @@
|
|||||||
<!-- Fullscreen map -->
|
<!-- Fullscreen map -->
|
||||||
<div class="map-section">
|
<div class="map-section">
|
||||||
<Map
|
<Map
|
||||||
autoCenter={true}
|
autoCenter={false}
|
||||||
center={[parseFloat(data.find?.longitude || '0'), parseFloat(data.find?.latitude || '0')]}
|
center={[parseFloat(data.find?.longitude || '0'), parseFloat(data.find?.latitude || '0')]}
|
||||||
|
zoom={15}
|
||||||
|
locations={findAsLocation}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -457,7 +487,6 @@
|
|||||||
width: 40%;
|
width: 40%;
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
height: calc(100vh - 100px);
|
|
||||||
backdrop-filter: blur(10px);
|
backdrop-filter: blur(10px);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ Disallow: /_app/
|
|||||||
Disallow: /.svelte-kit/
|
Disallow: /.svelte-kit/
|
||||||
|
|
||||||
# Sitemap location
|
# Sitemap location
|
||||||
Sitemap: https://serengo.ziasvannes.tech/sitemap.xml
|
Sitemap: https://serengo.zias.be/sitemap.xml
|
||||||
|
|
||||||
# Crawl delay for polite crawling
|
# Crawl delay for polite crawling
|
||||||
Crawl-delay: 1
|
Crawl-delay: 1
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import adapter from '@sveltejs/adapter-vercel';
|
import adapter from '@sveltejs/adapter-node';
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
@@ -13,7 +13,7 @@ const config = {
|
|||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter(),
|
adapter: adapter(),
|
||||||
csrf: {
|
csrf: {
|
||||||
trustedOrigins: ['http://localhost:3000', 'https://serengo.ziasvannes.tech']
|
trustedOrigins: ['http://localhost:3000', 'https://serengo.zias.be']
|
||||||
},
|
},
|
||||||
alias: {
|
alias: {
|
||||||
'@/*': './src/lib/*'
|
'@/*': './src/lib/*'
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { defineConfig } from 'vite';
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), sveltekit()],
|
plugins: [tailwindcss(), sveltekit()],
|
||||||
preview: { allowedHosts: ['ziasvannes.tech'] },
|
preview: { allowedHosts: ['zias.be'] },
|
||||||
build: {
|
build: {
|
||||||
target: 'es2020',
|
target: 'es2020',
|
||||||
cssCodeSplit: true
|
cssCodeSplit: true
|
||||||
|
|||||||
Reference in New Issue
Block a user