From 38f779a85de5992c507a23bc1e39bb352f380609 Mon Sep 17 00:00:00 2001 From: Stan Runge Date: Fri, 13 Mar 2026 16:13:26 +0100 Subject: [PATCH] remove deploy staging script + bring back schema in staging seed script --- scripts/deploy-staging.sh | 79 ----------------- scripts/seed-staging.ts | 178 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+), 79 deletions(-) delete mode 100755 scripts/deploy-staging.sh diff --git a/scripts/deploy-staging.sh b/scripts/deploy-staging.sh deleted file mode 100755 index ed236fa..0000000 --- a/scripts/deploy-staging.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -# Deploy the staging environment. -# -# Usage: -# ./scripts/deploy-staging.sh # deploy current branch -# ./scripts/deploy-staging.sh v1 # deploy a specific branch -# ./scripts/deploy-staging.sh main --seed # deploy + seed data -# -# Prerequisites: -# - .env.staging exists with all values filled in -# - Docker is running -# - DNS, SSL, and nginx are configured (see docker-compose.staging.yml header) - -COMPOSE_FILE="docker-compose.staging.yml" -PROJECT="esports-staging" -BRANCH="${1:-}" -SEED=false - -# Parse args -for arg in "$@"; do - case $arg in - --seed) SEED=true ;; - *) BRANCH="$arg" ;; - esac -done - -echo "=== Deploying staging ===" - -# Checkout branch if specified -if [ -n "$BRANCH" ] && [ "$BRANCH" != "--seed" ]; then - echo "Switching to branch: $BRANCH" - git fetch origin - git checkout "$BRANCH" - git pull origin "$BRANCH" -fi - -# Check .env.staging exists -if [ ! -f .env.staging ]; then - echo "ERROR: .env.staging not found. Copy .env.staging.example and fill in values." - exit 1 -fi - -# Build and restart -echo "Building containers..." -docker compose -f "$COMPOSE_FILE" -p "$PROJECT" build - -echo "Starting containers..." -docker compose -f "$COMPOSE_FILE" -p "$PROJECT" up -d - -# Wait for DB -echo "Waiting for staging DB to be ready..." -until docker compose -f "$COMPOSE_FILE" -p "$PROJECT" exec staging-db pg_isready -U vash_staging -d vash_staging 2>/dev/null; do - sleep 1 -done -echo "DB ready." - -# Run schema push (reads DATABASE_URL from the staging DB exposed on host port 5433) -echo "Pushing schema to staging DB..." -source .env.staging -export DATABASE_URL="postgresql://vash_staging:${STAGING_DB_PASSWORD}@127.0.0.1:5433/vash_staging" -bun run db:push - -# Optionally seed -if [ "$SEED" = true ]; then - echo "Seeding staging database..." - bun scripts/seed-staging.ts --fresh -fi - -echo "" -echo "=== Staging deployed ===" -echo "URL: https://staging.esports.vash.software" -echo "DB: postgresql://vash_staging:***@127.0.0.1:5433/vash_staging" -echo "" -echo "Useful commands:" -echo " docker compose -f $COMPOSE_FILE -p $PROJECT logs -f staging-app" -echo " docker compose -f $COMPOSE_FILE -p $PROJECT ps" -echo " docker compose -f $COMPOSE_FILE -p $PROJECT down" diff --git a/scripts/seed-staging.ts b/scripts/seed-staging.ts index 0de93f9..250f573 100644 --- a/scripts/seed-staging.ts +++ b/scripts/seed-staging.ts @@ -39,6 +39,184 @@ const DATABASE_URL = process.env.DATABASE_URL; if (!DATABASE_URL) throw new Error('DATABASE_URL not set'); const db = drizzle(DATABASE_URL); +// ── Inline table defs (mirrors schema.ts, avoids $lib imports) ────────── + +const user = pgTable('user', { + id: text('id').primaryKey(), + name: text('name').notNull(), + email: text('email').notNull().unique(), + emailVerified: boolean('email_verified').default(false).notNull(), + image: text('image'), + role: text('role').default('player').notNull(), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull() +}); + +const account = pgTable('account', { + id: text('id').primaryKey(), + accountId: text('account_id').notNull(), + providerId: text('provider_id').notNull(), + userId: text('user_id').notNull(), + accessToken: text('access_token'), + refreshToken: text('refresh_token'), + idToken: text('id_token'), + accessTokenExpiresAt: timestamp('access_token_expires_at'), + refreshTokenExpiresAt: timestamp('refresh_token_expires_at'), + scope: text('scope'), + password: text('password'), + createdAt: timestamp('created_at').defaultNow().notNull(), + updatedAt: timestamp('updated_at').defaultNow().notNull() +}); + +const team = pgTable('team', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + isPersonal: boolean('is_personal').default(false).notNull(), + ownerId: text('owner_id'), + avatarUrl: text('avatar_url'), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() +}); + +const teamMember = pgTable('team_member', { + id: uuid('id').primaryKey().defaultRandom(), + teamId: uuid('team_id').notNull(), + userId: text('user_id').notNull(), + role: text('role').default('member'), + joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow() +}); + +const mappool = pgTable('mappool', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name').notNull(), + createdBy: text('created_by'), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull(), + verifiedAt: timestamp('verified_at', { withTimezone: true }) +}); + +const mappoolSlot = pgTable('mappool_slot', { + id: uuid('id').primaryKey().defaultRandom(), + mappoolId: uuid('mappool_id').notNull(), + category: text('category').notNull(), + orderInCategory: integer('order_in_category').notNull(), + beatmapId: text('beatmap_id').notNull(), + starRating: real('star_rating'), + bpm: real('bpm'), + totalLength: integer('total_length'), + mods: text('mods').array().default([]).notNull(), + title: text('title'), + artist: text('artist'), + version: text('version'), + coverUrl: text('cover_url'), + listCoverUrl: text('list_cover_url') +}); + +const match = pgTable('match', { + id: uuid('id').primaryKey().defaultRandom(), + name: text('name'), + state: text('state').default('CREATED').notNull(), + config: jsonb('config').notNull(), + mappoolId: uuid('mappool_id'), + osuLobbyId: integer('osu_lobby_id'), + scheduledAt: timestamp('scheduled_at', { withTimezone: true }), + startedAt: timestamp('started_at', { withTimezone: true }), + finishedAt: timestamp('finished_at', { withTimezone: true }), + winnerId: uuid('winner_id'), + createdBy: text('created_by'), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() +}); + +const matchParticipant = pgTable('match_participant', { + id: uuid('id').primaryKey().defaultRandom(), + matchId: uuid('match_id').notNull(), + teamId: uuid('team_id').notNull(), + slot: integer('slot').notNull(), + score: integer('score').default(0).notNull(), + rollValue: integer('roll_value'), + pickOrder: integer('pick_order') +}); + +const matchParticipantPlayer = pgTable('match_participant_player', { + id: uuid('id').primaryKey().defaultRandom(), + participantId: uuid('participant_id').notNull(), + userId: text('user_id').notNull() +}); + +const matchGame = pgTable('match_game', { + id: uuid('id').primaryKey().defaultRandom(), + matchId: uuid('match_id').notNull(), + gameNumber: integer('game_number').notNull(), + mappoolSlotId: uuid('mappool_slot_id').notNull(), + pickedByParticipantId: uuid('picked_by_participant_id'), + winnerParticipantId: uuid('winner_participant_id'), + state: text('state').default('PENDING').notNull(), + startedAt: timestamp('started_at', { withTimezone: true }), + finishedAt: timestamp('finished_at', { withTimezone: true }) +}); + +const matchGameScore = pgTable('match_game_score', { + id: uuid('id').primaryKey().defaultRandom(), + matchGameId: uuid('match_game_id').notNull(), + playerId: uuid('player_id').notNull(), + score: integer('score').default(0).notNull(), + accuracy: real('accuracy').default(0).notNull(), + maxCombo: integer('max_combo').default(0).notNull(), + count300: integer('count_300').default(0).notNull(), + count100: integer('count_100').default(0).notNull(), + count50: integer('count_50').default(0).notNull(), + countMiss: integer('count_miss').default(0).notNull(), + mods: text('mods').array().default([]).notNull(), + passed: boolean('passed').default(false).notNull(), + pp: real('pp') +}); + +const playerRating = pgTable('player_rating', { + id: uuid('id').primaryKey().defaultRandom(), + userId: text('user_id').notNull().unique(), + elo: integer('elo').default(1000).notNull(), + wins: integer('wins').default(0).notNull(), + losses: integer('losses').default(0).notNull(), + initialElo: integer('initial_elo'), + osuRankAtSeed: integer('osu_rank_at_seed'), + updatedAt: timestamp('updated_at', { withTimezone: true }).defaultNow().notNull() +}); + +const notification = pgTable('notification', { + id: uuid('id').primaryKey().defaultRandom(), + userId: text('user_id').notNull(), + type: text('type').notNull(), + title: text('title').notNull(), + message: text('message'), + referenceId: text('reference_id'), + read: boolean('read').default(false).notNull(), + actionedAt: timestamp('actioned_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() +}); + +const matchInvite = pgTable('match_invite', { + id: uuid('id').primaryKey().defaultRandom(), + createdBy: text('created_by').notNull(), + creatorTeamId: uuid('creator_team_id').notNull(), + invitedTeamId: uuid('invited_team_id').notNull(), + config: jsonb('config').notNull(), + mappoolId: uuid('mappool_id').notNull(), + name: text('name'), + message: text('message'), + scheduledAt: timestamp('scheduled_at', { withTimezone: true }), + expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(), + status: text('status').default('pending').notNull(), + matchId: uuid('match_id'), + respondedAt: timestamp('responded_at', { withTimezone: true }), + createdAt: timestamp('created_at', { withTimezone: true }).defaultNow().notNull() +}); + +const matchQueue = pgTable('match_queue', { + id: uuid('id').primaryKey().defaultRandom(), + userId: text('user_id').notNull(), + teamId: uuid('team_id').notNull(), + elo: integer('elo').default(1000).notNull(), + joinedAt: timestamp('joined_at', { withTimezone: true }).defaultNow().notNull() +}); + // ── Helpers ───────────────────────────────────────────────────────────── /** Seeded PRNG for reproducible data */