diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5a80f05 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +# Use a Node.js base image +FROM node:18-alpine + +# Set the working directory inside the container +WORKDIR /app + +# Copy package.json and package-lock.json (or yarn.lock) +# to install dependencies +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of your application code +COPY . . + +# Expose the port your app listens on (e.g., 5500) +EXPOSE 5500 + +# Command to run your application +CMD [ "npm", "start" ] \ No newline at end of file diff --git a/compose.yml b/compose.yml index e6dfc85..3d585a1 100644 --- a/compose.yml +++ b/compose.yml @@ -15,6 +15,28 @@ services: retries: 5 start_period: 30s timeout: 10s + +# ADD THIS NEW SERVICE FOR YOUR NODE.JS BACKEND + backend: + build: . # This tells Docker to build an image from your current directory (where your Dockerfile should be) + ports: + - "5500:5500" # Expose your server port (adjust if your app uses a different port) + environment: + # Pass environment variables from your .env file into the container + # Codespaces will automatically pick up .env variables here. + # Alternatively, you can explicitly list them if not using Codespaces' auto-loading + DATABASE_URL: postgresql://postgres:mysecretpassword@db:5432/mydatabase?sslmode=disable # <--- Ensure this matches your .env and db service + GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID} # Pass from .env + GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET} # Pass from .env + JWT_SECRET: ${JWT_SECRET} # Pass from .env + SERVER_URL: ${SERVER_URL} # Pass from .env + depends_on: + db: + condition: service_healthy # Ensure db is healthy before starting backend + # command: npm start # Or whatever command starts your server (e.g., node index.js) + # The default command in your Dockerfile will usually be `npm start` + volumes: + - .:/app # Mount current directory into the container for live updates (optional but useful for dev) volumes: postgres_db: diff --git a/src/db/migrations/0013_modern_shinko_yamashiro.sql b/src/db/migrations/0013_modern_shinko_yamashiro.sql new file mode 100644 index 0000000..e3f5eee --- /dev/null +++ b/src/db/migrations/0013_modern_shinko_yamashiro.sql @@ -0,0 +1 @@ +ALTER TABLE "ideas" ALTER COLUMN "event_id" SET DATA TYPE integer; \ No newline at end of file diff --git a/src/db/migrations/meta/0013_snapshot.json b/src/db/migrations/meta/0013_snapshot.json new file mode 100644 index 0000000..1ddd286 --- /dev/null +++ b/src/db/migrations/meta/0013_snapshot.json @@ -0,0 +1,580 @@ +{ + "id": "5799c0d7-3fad-4c16-a16e-35c22df54c8e", + "prevId": "460e8740-b267-437e-aee9-c990b6589488", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": true, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.events": { + "name": "events", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "event_date": { + "name": "event_date", + "type": "date", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + }, + "stage": { + "name": "stage", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "eventTime": { + "name": "eventTime", + "type": "time", + "primaryKey": false, + "notNull": false + }, + "link": { + "name": "link", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "current_sub_stage": { + "name": "current_sub_stage", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'1'" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "checked_in": { + "name": "checked_in", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.idea_event_metadata": { + "name": "idea_event_metadata", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "idea_id": { + "name": "idea_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "technologies": { + "name": "technologies", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "contributors": { + "name": "contributors", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "is_built": { + "name": "is_built", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "idea_event_metadata_idea_id_fkey": { + "name": "idea_event_metadata_idea_id_fkey", + "tableFrom": "idea_event_metadata", + "tableTo": "ideas", + "columnsFrom": [ + "idea_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "idea_event_metadata_event_id_fkey": { + "name": "idea_event_metadata_event_id_fkey", + "tableFrom": "idea_event_metadata", + "tableTo": "events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ideas": { + "name": "ideas", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "idea": { + "name": "idea", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "technologies": { + "name": "technologies", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "likes": { + "name": "likes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_built": { + "name": "is_built", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + }, + "stage": { + "name": "stage", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 1 + }, + "average_score": { + "name": "average_score", + "type": "double precision", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "contributors": { + "name": "contributors", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "''" + }, + "image_url": { + "name": "image_url", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.likes": { + "name": "likes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "idea_id": { + "name": "idea_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "liked_at": { + "name": "liked_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "likes_idea_id_fkey": { + "name": "likes_idea_id_fkey", + "tableFrom": "likes", + "tableTo": "ideas", + "columnsFrom": [ + "idea_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_user_like": { + "name": "unique_user_like", + "nullsNotDistinct": false, + "columns": [ + "user_email", + "idea_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.results": { + "name": "results", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "varchar(50)", + "primaryKey": false, + "notNull": true + }, + "winning_idea_id": { + "name": "winning_idea_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "votes": { + "name": "votes", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "results_event_id_fkey": { + "name": "results_event_id_fkey", + "tableFrom": "results", + "tableTo": "events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "results_winning_idea_id_fkey": { + "name": "results_winning_idea_id_fkey", + "tableFrom": "results", + "tableTo": "ideas", + "columnsFrom": [ + "winning_idea_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "unique_event_category": { + "name": "unique_event_category", + "nullsNotDistinct": false, + "columns": [ + "event_id", + "category" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "varchar(255)", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.votes": { + "name": "votes", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "idea_id": { + "name": "idea_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "event_id": { + "name": "event_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "vote_type": { + "name": "vote_type", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false, + "default": "CURRENT_TIMESTAMP" + } + }, + "indexes": {}, + "foreignKeys": { + "votes_idea_id_fkey": { + "name": "votes_idea_id_fkey", + "tableFrom": "votes", + "tableTo": "ideas", + "columnsFrom": [ + "idea_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "votes_event_id_fkey": { + "name": "votes_event_id_fkey", + "tableFrom": "votes", + "tableTo": "events", + "columnsFrom": [ + "event_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": {}, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json index 2caae2c..751ac7b 100644 --- a/src/db/migrations/meta/_journal.json +++ b/src/db/migrations/meta/_journal.json @@ -92,6 +92,13 @@ "when": 1752346893917, "tag": "0012_known_changeling", "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1754893628013, + "tag": "0013_modern_shinko_yamashiro", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schemas/schema.ts b/src/db/schemas/schema.ts index bc335b7..f1842cc 100644 --- a/src/db/schemas/schema.ts +++ b/src/db/schemas/schema.ts @@ -27,6 +27,8 @@ export const events = pgTable("events", { checkedIn: text("checked_in").default(""), }); +// In your schemas.ts file + export const ideas = pgTable("ideas", { id: serial().primaryKey().notNull(), email: varchar({ length: 255 }).notNull(), @@ -36,7 +38,7 @@ export const ideas = pgTable("ideas", { likes: integer().default(0), createdAt: timestamp("created_at", { mode: "string" }).default(sql`CURRENT_TIMESTAMP`), updatedAt: timestamp("updated_at", { mode: "string" }).default(sql`CURRENT_TIMESTAMP`), - eventId: text("event_id").notNull(), // ✅ now a comma-separated string + eventId: integer("event_id").notNull(), // <-- CHANGE THIS FROM `text` to `integer` isBuilt: boolean("is_built").default(false), stage: integer().default(1), averageScore: doublePrecision("average_score").default(0), @@ -44,6 +46,10 @@ export const ideas = pgTable("ideas", { imageUrl: text("image_url"), }); +// No change needed for foreign keys in other tables that reference eventId, +// as they are already defined as integer (e.g., votes.eventId, results.eventId, ideaEventMetadata.eventId). +// The issue was specifically with ideas.eventId being `text`. + export const likes = pgTable( "likes", {