This commit is contained in:
@@ -0,0 +1,599 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "Sport" AS ENUM ('football', 'basketball');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "UserRole" AS ENUM ('user', 'superadmin');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SubscriptionStatus" AS ENUM ('free', 'active', 'expired');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "PlayerPosition" AS ENUM ('goalkeeper', 'defender', 'midfielder', 'striker');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "EventType" AS ENUM ('goal', 'card', 'substitute');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "MatchPosition" AS ENUM ('home', 'away');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "countries" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"flag_url" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "countries_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "leagues" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"country_id" TEXT,
|
||||
"sport" "Sport" NOT NULL,
|
||||
"competition_slug" TEXT,
|
||||
"code" TEXT,
|
||||
"logo_url" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "leagues_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "teams" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT,
|
||||
"sport" "Sport" NOT NULL,
|
||||
"logo_url" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "teams_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "players" (
|
||||
"id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"slug" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "players_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "matches" (
|
||||
"id" TEXT NOT NULL,
|
||||
"league_id" TEXT,
|
||||
"home_team_id" TEXT,
|
||||
"away_team_id" TEXT,
|
||||
"sport" "Sport" NOT NULL,
|
||||
"match_name" TEXT,
|
||||
"match_slug" TEXT,
|
||||
"mst_utc" BIGINT NOT NULL,
|
||||
"status" TEXT,
|
||||
"state" TEXT,
|
||||
"score_home" INTEGER,
|
||||
"score_away" INTEGER,
|
||||
"ht_score_home" INTEGER,
|
||||
"ht_score_away" INTEGER,
|
||||
"winner" TEXT,
|
||||
"iddaa_code" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "matches_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "odd_categories" (
|
||||
"db_id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"category_json_id" INTEGER,
|
||||
"name" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "odd_categories_pkey" PRIMARY KEY ("db_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "odd_selections" (
|
||||
"db_id" SERIAL NOT NULL,
|
||||
"odd_category_db_id" INTEGER NOT NULL,
|
||||
"name" TEXT,
|
||||
"odd_value" TEXT,
|
||||
"position" TEXT,
|
||||
"sov" DOUBLE PRECISION,
|
||||
"state" TEXT,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "odd_selections_pkey" PRIMARY KEY ("db_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_team_stats" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"team_id" TEXT NOT NULL,
|
||||
"possession_percentage" DOUBLE PRECISION,
|
||||
"shots_on_target" INTEGER,
|
||||
"shots_off_target" INTEGER,
|
||||
"total_shots" INTEGER,
|
||||
"total_passes" INTEGER,
|
||||
"corners" INTEGER,
|
||||
"fouls" INTEGER,
|
||||
"offsides" INTEGER,
|
||||
"points" INTEGER,
|
||||
"rebounds" INTEGER,
|
||||
"assists" INTEGER,
|
||||
"fg_made" INTEGER,
|
||||
"fg_attempted" INTEGER,
|
||||
"three_pt_made" INTEGER,
|
||||
"three_pt_attempted" INTEGER,
|
||||
"ft_made" INTEGER,
|
||||
"ft_attempted" INTEGER,
|
||||
"steals" INTEGER,
|
||||
"blocks" INTEGER,
|
||||
"turnovers" INTEGER,
|
||||
"q1_score" INTEGER,
|
||||
"q2_score" INTEGER,
|
||||
"q3_score" INTEGER,
|
||||
"q4_score" INTEGER,
|
||||
"ot_score" INTEGER,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "match_team_stats_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_player_participation" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"player_id" TEXT NOT NULL,
|
||||
"team_id" TEXT NOT NULL,
|
||||
"position" "PlayerPosition",
|
||||
"shirt_number" INTEGER,
|
||||
"is_starting" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "match_player_participation_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_player_events" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"player_id" TEXT NOT NULL,
|
||||
"team_id" TEXT NOT NULL,
|
||||
"event_type" "EventType" NOT NULL,
|
||||
"event_subtype" TEXT,
|
||||
"time_minute" TEXT NOT NULL,
|
||||
"time_seconds" INTEGER,
|
||||
"period_id" INTEGER,
|
||||
"assist_player_id" TEXT,
|
||||
"score_after" TEXT,
|
||||
"player_out_id" TEXT,
|
||||
"position" "MatchPosition",
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "match_player_events_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_player_stats" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"player_id" TEXT NOT NULL,
|
||||
"team_id" TEXT NOT NULL,
|
||||
"minutes" TEXT,
|
||||
"points" INTEGER,
|
||||
"rebounds" INTEGER,
|
||||
"assists" INTEGER,
|
||||
"steals" INTEGER,
|
||||
"blocks" INTEGER,
|
||||
"turnovers" INTEGER,
|
||||
"fg_made" INTEGER,
|
||||
"fg_attempted" INTEGER,
|
||||
"three_pt_made" INTEGER,
|
||||
"three_pt_attempted" INTEGER,
|
||||
"ft_made" INTEGER,
|
||||
"ft_attempted" INTEGER,
|
||||
"fouls" INTEGER,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "match_player_stats_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_officials" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"role_id" INTEGER NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "match_officials_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "official_roles" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
|
||||
CONSTRAINT "official_roles_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "live_matches" (
|
||||
"id" TEXT NOT NULL,
|
||||
"league_id" TEXT,
|
||||
"home_team_id" TEXT,
|
||||
"away_team_id" TEXT,
|
||||
"sport" TEXT,
|
||||
"match_name" TEXT,
|
||||
"match_slug" TEXT,
|
||||
"mst_utc" BIGINT,
|
||||
"status" TEXT,
|
||||
"state" TEXT,
|
||||
"substate" TEXT,
|
||||
"score_home" INTEGER,
|
||||
"score_away" INTEGER,
|
||||
"json_data" JSONB,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"is_processed_by_bot" BOOLEAN NOT NULL DEFAULT false,
|
||||
|
||||
CONSTRAINT "live_matches_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "predictions" (
|
||||
"match_id" TEXT NOT NULL,
|
||||
"prediction_json" JSONB NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "predictions_pkey" PRIMARY KEY ("match_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "ai_predictions_log" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"model_version" TEXT NOT NULL,
|
||||
"recommended_bets" JSONB,
|
||||
"confidence_score" DOUBLE PRECISION,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"is_resolved" BOOLEAN NOT NULL DEFAULT false,
|
||||
"actual_result" TEXT,
|
||||
"is_correct" BOOLEAN,
|
||||
"accuracy_score" DOUBLE PRECISION,
|
||||
|
||||
CONSTRAINT "ai_predictions_log_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "users" (
|
||||
"id" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password_hash" TEXT NOT NULL,
|
||||
"first_name" TEXT,
|
||||
"last_name" TEXT,
|
||||
"role" "UserRole" NOT NULL DEFAULT 'user',
|
||||
"subscription_status" "SubscriptionStatus" NOT NULL DEFAULT 'free',
|
||||
"subscription_expires_at" TIMESTAMP(3),
|
||||
"encrypted_api_key" TEXT,
|
||||
"is_active" BOOLEAN NOT NULL DEFAULT true,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
"deleted_at" TIMESTAMP(3),
|
||||
|
||||
CONSTRAINT "users_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "usage_limits" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"analysis_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"coupon_count" INTEGER NOT NULL DEFAULT 0,
|
||||
"last_reset_date" DATE NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "usage_limits_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "analyses" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"match_ids" TEXT NOT NULL,
|
||||
"analysis_result_json" TEXT NOT NULL,
|
||||
"is_deleted" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "analyses_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "refresh_tokens" (
|
||||
"id" TEXT NOT NULL,
|
||||
"token" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"expires_at" TIMESTAMP(3) NOT NULL,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "app_settings" (
|
||||
"key" TEXT NOT NULL,
|
||||
"value" TEXT,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "app_settings_pkey" PRIMARY KEY ("key")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "translations" (
|
||||
"id" TEXT NOT NULL,
|
||||
"key" TEXT NOT NULL,
|
||||
"locale" TEXT NOT NULL,
|
||||
"value" TEXT NOT NULL,
|
||||
"namespace" TEXT NOT NULL DEFAULT 'common',
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "translations_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "countries_name_key" ON "countries"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "leagues_sport_idx" ON "leagues"("sport");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "leagues_country_id_idx" ON "leagues"("country_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "leagues_name_country_id_sport_key" ON "leagues"("name", "country_id", "sport");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "teams_sport_idx" ON "teams"("sport");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "teams_name_idx" ON "teams"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "players_slug_key" ON "players"("slug");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "players_name_idx" ON "players"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_mst_utc_idx" ON "matches"("mst_utc" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_sport_idx" ON "matches"("sport");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_state_idx" ON "matches"("state");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_league_id_idx" ON "matches"("league_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_home_team_id_idx" ON "matches"("home_team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_away_team_id_idx" ON "matches"("away_team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "matches_iddaa_code_idx" ON "matches"("iddaa_code");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "odd_categories_match_id_idx" ON "odd_categories"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "odd_selections_odd_category_db_id_idx" ON "odd_selections"("odd_category_db_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_team_stats_match_id_idx" ON "match_team_stats"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_team_stats_team_id_idx" ON "match_team_stats"("team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "match_team_stats_match_id_team_id_key" ON "match_team_stats"("match_id", "team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_participation_match_id_idx" ON "match_player_participation"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_participation_player_id_idx" ON "match_player_participation"("player_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_participation_team_id_idx" ON "match_player_participation"("team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "match_player_participation_match_id_player_id_team_id_key" ON "match_player_participation"("match_id", "player_id", "team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_events_match_id_idx" ON "match_player_events"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_events_player_id_idx" ON "match_player_events"("player_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_events_team_id_idx" ON "match_player_events"("team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_events_event_type_idx" ON "match_player_events"("event_type");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_events_assist_player_id_idx" ON "match_player_events"("assist_player_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_player_stats_match_id_idx" ON "match_player_stats"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "match_player_stats_match_id_player_id_team_id_key" ON "match_player_stats"("match_id", "player_id", "team_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_officials_match_id_idx" ON "match_officials"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "match_officials_role_id_idx" ON "match_officials"("role_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "match_officials_match_id_name_role_id_key" ON "match_officials"("match_id", "name", "role_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "official_roles_name_key" ON "official_roles"("name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "live_matches_mst_utc_idx" ON "live_matches"("mst_utc" ASC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "live_matches_state_idx" ON "live_matches"("state");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "live_matches_is_processed_by_bot_idx" ON "live_matches"("is_processed_by_bot");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ai_predictions_log_match_id_idx" ON "ai_predictions_log"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "ai_predictions_log_created_at_idx" ON "ai_predictions_log"("created_at" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_email_idx" ON "users"("email");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "users_subscription_status_subscription_expires_at_idx" ON "users"("subscription_status", "subscription_expires_at");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "usage_limits_user_id_key" ON "usage_limits"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "usage_limits_user_id_idx" ON "usage_limits"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "usage_limits_last_reset_date_idx" ON "usage_limits"("last_reset_date");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "analyses_user_id_idx" ON "analyses"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "analyses_created_at_idx" ON "analyses"("created_at" DESC);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "refresh_tokens_token_key" ON "refresh_tokens"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "refresh_tokens_token_idx" ON "refresh_tokens"("token");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "refresh_tokens_user_id_idx" ON "refresh_tokens"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "translations_key_idx" ON "translations"("key");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "translations_locale_idx" ON "translations"("locale");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "translations_namespace_idx" ON "translations"("namespace");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "translations_key_locale_namespace_key" ON "translations"("key", "locale", "namespace");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "leagues" ADD CONSTRAINT "leagues_country_id_fkey" FOREIGN KEY ("country_id") REFERENCES "countries"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "matches" ADD CONSTRAINT "matches_league_id_fkey" FOREIGN KEY ("league_id") REFERENCES "leagues"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "matches" ADD CONSTRAINT "matches_home_team_id_fkey" FOREIGN KEY ("home_team_id") REFERENCES "teams"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "matches" ADD CONSTRAINT "matches_away_team_id_fkey" FOREIGN KEY ("away_team_id") REFERENCES "teams"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "odd_categories" ADD CONSTRAINT "odd_categories_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "odd_selections" ADD CONSTRAINT "odd_selections_odd_category_db_id_fkey" FOREIGN KEY ("odd_category_db_id") REFERENCES "odd_categories"("db_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_team_stats" ADD CONSTRAINT "match_team_stats_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_team_stats" ADD CONSTRAINT "match_team_stats_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_participation" ADD CONSTRAINT "match_player_participation_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_participation" ADD CONSTRAINT "match_player_participation_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "players"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_participation" ADD CONSTRAINT "match_player_participation_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_events" ADD CONSTRAINT "match_player_events_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_events" ADD CONSTRAINT "match_player_events_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "players"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_events" ADD CONSTRAINT "match_player_events_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_events" ADD CONSTRAINT "match_player_events_assist_player_id_fkey" FOREIGN KEY ("assist_player_id") REFERENCES "players"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_events" ADD CONSTRAINT "match_player_events_player_out_id_fkey" FOREIGN KEY ("player_out_id") REFERENCES "players"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_stats" ADD CONSTRAINT "match_player_stats_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_stats" ADD CONSTRAINT "match_player_stats_player_id_fkey" FOREIGN KEY ("player_id") REFERENCES "players"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_player_stats" ADD CONSTRAINT "match_player_stats_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_officials" ADD CONSTRAINT "match_officials_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_officials" ADD CONSTRAINT "match_officials_role_id_fkey" FOREIGN KEY ("role_id") REFERENCES "official_roles"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "live_matches" ADD CONSTRAINT "live_matches_league_id_fkey" FOREIGN KEY ("league_id") REFERENCES "leagues"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "live_matches" ADD CONSTRAINT "live_matches_home_team_id_fkey" FOREIGN KEY ("home_team_id") REFERENCES "teams"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "live_matches" ADD CONSTRAINT "live_matches_away_team_id_fkey" FOREIGN KEY ("away_team_id") REFERENCES "teams"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "predictions" ADD CONSTRAINT "predictions_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "usage_limits" ADD CONSTRAINT "usage_limits_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "analyses" ADD CONSTRAINT "analyses_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "refresh_tokens" ADD CONSTRAINT "refresh_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
-- Drop unused columns from live_matches
|
||||
DROP INDEX IF EXISTS "live_matches_is_processed_by_bot_idx";
|
||||
|
||||
ALTER TABLE "live_matches"
|
||||
DROP COLUMN IF EXISTS "json_data",
|
||||
DROP COLUMN IF EXISTS "is_processed_by_bot",
|
||||
DROP COLUMN IF EXISTS "away_red_cards",
|
||||
DROP COLUMN IF EXISTS "current_minute",
|
||||
DROP COLUMN IF EXISTS "home_red_cards",
|
||||
DROP COLUMN IF EXISTS "momentum_score";
|
||||
@@ -0,0 +1,253 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[match_id,name]` on the table `odd_categories` will be added. If there are existing duplicate values, this will fail.
|
||||
- A unique constraint covering the columns `[odd_category_db_id,name]` on the table `odd_selections` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TotoBulletinStatus" AS ENUM ('UPCOMING', 'IN_PROGRESS', 'COMPLETED', 'CANCELLED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "TotoMatchResult" AS ENUM ('HOME', 'DRAW', 'AWAY');
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "live_matches" ADD COLUMN "lineups" JSONB,
|
||||
ADD COLUMN "odds" JSONB,
|
||||
ADD COLUMN "odds_updated_at" TIMESTAMP(3),
|
||||
ADD COLUMN "referee_name" TEXT,
|
||||
ADD COLUMN "sidelined" JSONB;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "odd_selections" ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "match_ai_features" (
|
||||
"match_id" TEXT NOT NULL,
|
||||
"home_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"away_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"home_form_score" DOUBLE PRECISION NOT NULL DEFAULT 50.0,
|
||||
"away_form_score" DOUBLE PRECISION NOT NULL DEFAULT 50.0,
|
||||
"missing_players_impact" DOUBLE PRECISION NOT NULL DEFAULT 0.0,
|
||||
"calculator_ver" TEXT NOT NULL DEFAULT 'v1.0',
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "match_ai_features_pkey" PRIMARY KEY ("match_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "team_elo_ratings" (
|
||||
"team_id" TEXT NOT NULL,
|
||||
"overall_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"home_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"away_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"form_elo" DOUBLE PRECISION NOT NULL DEFAULT 1500.0,
|
||||
"matches_played" INTEGER NOT NULL DEFAULT 0,
|
||||
"recent_form" TEXT NOT NULL DEFAULT '',
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "team_elo_ratings_pkey" PRIMARY KEY ("team_id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "odds_history" (
|
||||
"id" BIGSERIAL NOT NULL,
|
||||
"selection_id" INTEGER NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"previous_value" DOUBLE PRECISION NOT NULL,
|
||||
"new_value" DOUBLE PRECISION NOT NULL,
|
||||
"bookmaker" TEXT DEFAULT 'MACKOLIK',
|
||||
"change_time" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "odds_history_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_coupons" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"strategy" TEXT NOT NULL,
|
||||
"total_odds" DOUBLE PRECISION NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"isPublic" BOOLEAN NOT NULL DEFAULT false,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "user_coupons_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "user_coupon_items" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"coupon_id" TEXT NOT NULL,
|
||||
"match_id" TEXT NOT NULL,
|
||||
"selection" TEXT NOT NULL,
|
||||
"odd_at_time" DOUBLE PRECISION NOT NULL,
|
||||
"is_correct" BOOLEAN,
|
||||
|
||||
CONSTRAINT "user_coupon_items_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "toto_bulletins" (
|
||||
"id" TEXT NOT NULL,
|
||||
"game_cycle_no" INTEGER NOT NULL,
|
||||
"program_name" TEXT,
|
||||
"season" TEXT,
|
||||
"status" "TotoBulletinStatus" NOT NULL DEFAULT 'UPCOMING',
|
||||
"payin_begin_date" TIMESTAMP(3),
|
||||
"payin_end_date" TIMESTAMP(3),
|
||||
"pool_total" DOUBLE PRECISION,
|
||||
"rollover_amount" DOUBLE PRECISION,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "toto_bulletins_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "toto_bulletin_matches" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"bulletin_id" TEXT NOT NULL,
|
||||
"match_order" INTEGER NOT NULL,
|
||||
"home_team_name" TEXT NOT NULL,
|
||||
"away_team_name" TEXT NOT NULL,
|
||||
"league_name" TEXT,
|
||||
"kickoff_time" TIMESTAMP(3),
|
||||
"match_id" TEXT,
|
||||
"result" "TotoMatchResult",
|
||||
"is_cancelled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"draw_result" "TotoMatchResult",
|
||||
|
||||
CONSTRAINT "toto_bulletin_matches_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "toto_results" (
|
||||
"id" TEXT NOT NULL,
|
||||
"bulletin_id" TEXT NOT NULL,
|
||||
"winners_15" INTEGER NOT NULL DEFAULT 0,
|
||||
"prize_15" DOUBLE PRECISION,
|
||||
"winners_14" INTEGER NOT NULL DEFAULT 0,
|
||||
"prize_14" DOUBLE PRECISION,
|
||||
"winners_13" INTEGER NOT NULL DEFAULT 0,
|
||||
"prize_13" DOUBLE PRECISION,
|
||||
"winners_12" INTEGER NOT NULL DEFAULT 0,
|
||||
"prize_12" DOUBLE PRECISION,
|
||||
"rollover_next" DOUBLE PRECISION,
|
||||
"pool_distributed" DOUBLE PRECISION,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "toto_results_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "toto_coupons" (
|
||||
"id" TEXT NOT NULL,
|
||||
"user_id" TEXT NOT NULL,
|
||||
"bulletin_id" TEXT NOT NULL,
|
||||
"strategy" TEXT,
|
||||
"column_count" INTEGER NOT NULL,
|
||||
"total_cost" DOUBLE PRECISION NOT NULL,
|
||||
"status" TEXT NOT NULL DEFAULT 'PENDING',
|
||||
"total_prize" DOUBLE PRECISION,
|
||||
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||
|
||||
CONSTRAINT "toto_coupons_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "toto_columns" (
|
||||
"id" SERIAL NOT NULL,
|
||||
"coupon_id" TEXT NOT NULL,
|
||||
"predictions" VARCHAR(15) NOT NULL,
|
||||
"correct_count" INTEGER,
|
||||
"prize_amount" DOUBLE PRECISION,
|
||||
|
||||
CONSTRAINT "toto_columns_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "odds_history_match_id_change_time_idx" ON "odds_history"("match_id", "change_time");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "odds_history_selection_id_idx" ON "odds_history"("selection_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_coupons_user_id_idx" ON "user_coupons"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_coupons_status_idx" ON "user_coupons"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "user_coupon_items_coupon_id_idx" ON "user_coupon_items"("coupon_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "toto_bulletins_game_cycle_no_key" ON "toto_bulletins"("game_cycle_no");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_bulletins_status_idx" ON "toto_bulletins"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_bulletin_matches_bulletin_id_idx" ON "toto_bulletin_matches"("bulletin_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_bulletin_matches_match_id_idx" ON "toto_bulletin_matches"("match_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "toto_bulletin_matches_bulletin_id_match_order_key" ON "toto_bulletin_matches"("bulletin_id", "match_order");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "toto_results_bulletin_id_key" ON "toto_results"("bulletin_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_coupons_user_id_idx" ON "toto_coupons"("user_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_coupons_bulletin_id_idx" ON "toto_coupons"("bulletin_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_coupons_status_idx" ON "toto_coupons"("status");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_columns_coupon_id_idx" ON "toto_columns"("coupon_id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "toto_columns_correct_count_idx" ON "toto_columns"("correct_count");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "odd_categories_match_id_name_key" ON "odd_categories"("match_id", "name");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "odd_selections_odd_category_db_id_name_key" ON "odd_selections"("odd_category_db_id", "name");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "match_ai_features" ADD CONSTRAINT "match_ai_features_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "team_elo_ratings" ADD CONSTRAINT "team_elo_ratings_team_id_fkey" FOREIGN KEY ("team_id") REFERENCES "teams"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "odds_history" ADD CONSTRAINT "odds_history_selection_id_fkey" FOREIGN KEY ("selection_id") REFERENCES "odd_selections"("db_id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_coupons" ADD CONSTRAINT "user_coupons_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_coupon_items" ADD CONSTRAINT "user_coupon_items_coupon_id_fkey" FOREIGN KEY ("coupon_id") REFERENCES "user_coupons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "user_coupon_items" ADD CONSTRAINT "user_coupon_items_match_id_fkey" FOREIGN KEY ("match_id") REFERENCES "matches"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "toto_bulletin_matches" ADD CONSTRAINT "toto_bulletin_matches_bulletin_id_fkey" FOREIGN KEY ("bulletin_id") REFERENCES "toto_bulletins"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "toto_results" ADD CONSTRAINT "toto_results_bulletin_id_fkey" FOREIGN KEY ("bulletin_id") REFERENCES "toto_bulletins"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "toto_coupons" ADD CONSTRAINT "toto_coupons_bulletin_id_fkey" FOREIGN KEY ("bulletin_id") REFERENCES "toto_bulletins"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "toto_columns" ADD CONSTRAINT "toto_columns_coupon_id_fkey" FOREIGN KEY ("coupon_id") REFERENCES "toto_coupons"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,55 @@
|
||||
-- Step 1: Create sport-specific team stats tables
|
||||
-- Run separately to avoid memory issues
|
||||
|
||||
-- 1a. Create football_team_stats
|
||||
CREATE TABLE IF NOT EXISTS football_team_stats (
|
||||
id SERIAL PRIMARY KEY,
|
||||
match_id TEXT NOT NULL,
|
||||
team_id TEXT NOT NULL,
|
||||
possession_percentage NUMERIC(5,2),
|
||||
shots_on_target INT,
|
||||
shots_off_target INT,
|
||||
total_shots INT,
|
||||
total_passes INT,
|
||||
corners INT,
|
||||
fouls INT,
|
||||
offsides INT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_football_team_stats_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_football_team_stats_team FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uq_football_team_stats_match_team UNIQUE (match_id, team_id)
|
||||
);
|
||||
|
||||
-- 1b. Create basketball_team_stats
|
||||
CREATE TABLE IF NOT EXISTS basketball_team_stats (
|
||||
id SERIAL PRIMARY KEY,
|
||||
match_id TEXT NOT NULL,
|
||||
team_id TEXT NOT NULL,
|
||||
points INT,
|
||||
rebounds INT,
|
||||
assists INT,
|
||||
fg_made INT,
|
||||
fg_attempted INT,
|
||||
three_pt_made INT,
|
||||
three_pt_attempted INT,
|
||||
ft_made INT,
|
||||
ft_attempted INT,
|
||||
steals INT,
|
||||
blocks INT,
|
||||
turnovers INT,
|
||||
q1_score INT,
|
||||
q2_score INT,
|
||||
q3_score INT,
|
||||
q4_score INT,
|
||||
ot_score INT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_basketball_team_stats_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_basketball_team_stats_team FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uq_basketball_team_stats_match_team UNIQUE (match_id, team_id)
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_football_team_stats_match ON football_team_stats(match_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_football_team_stats_team ON football_team_stats(team_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_basketball_team_stats_match ON basketball_team_stats(match_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_basketball_team_stats_team ON basketball_team_stats(team_id);
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Step 2: Copy team stats data (separate transaction)
|
||||
|
||||
INSERT INTO football_team_stats (match_id, team_id, possession_percentage, shots_on_target, shots_off_target, total_shots, total_passes, corners, fouls, offsides, created_at)
|
||||
SELECT match_id, team_id, possession_percentage, shots_on_target, shots_off_target, total_shots, total_passes, corners, fouls, offsides, created_at
|
||||
FROM match_team_stats
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_team_stats.match_id AND m.sport = 'football');
|
||||
|
||||
INSERT INTO basketball_team_stats (match_id, team_id, points, rebounds, assists, fg_made, fg_attempted, three_pt_made, three_pt_attempted, ft_made, ft_attempted, steals, blocks, turnovers, q1_score, q2_score, q3_score, q4_score, ot_score, created_at)
|
||||
SELECT match_id, team_id, points, rebounds, assists, fg_made, fg_attempted, three_pt_made, three_pt_attempted, ft_made, ft_attempted, steals, blocks, turnovers, q1_score, q2_score, q3_score, q4_score, ot_score, created_at
|
||||
FROM match_team_stats
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_team_stats.match_id AND m.sport = 'basketball');
|
||||
|
||||
-- Reset sequences
|
||||
SELECT setval('football_team_stats_id_seq', COALESCE((SELECT MAX(id) FROM football_team_stats), 0) + 1, false);
|
||||
SELECT setval('basketball_team_stats_id_seq', COALESCE((SELECT MAX(id) FROM basketball_team_stats), 0) + 1, false);
|
||||
@@ -0,0 +1,97 @@
|
||||
-- Step 3: Create and populate AI features tables
|
||||
|
||||
-- 3a. Create football_ai_features
|
||||
CREATE TABLE IF NOT EXISTS football_ai_features (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
home_elo FLOAT DEFAULT 1500.0,
|
||||
away_elo FLOAT DEFAULT 1500.0,
|
||||
home_home_elo FLOAT DEFAULT 1500.0,
|
||||
away_away_elo FLOAT DEFAULT 1500.0,
|
||||
home_form_elo FLOAT DEFAULT 1500.0,
|
||||
away_form_elo FLOAT DEFAULT 1500.0,
|
||||
elo_diff FLOAT DEFAULT 0.0,
|
||||
home_form_score FLOAT DEFAULT 50.0,
|
||||
away_form_score FLOAT DEFAULT 50.0,
|
||||
home_goals_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_goals_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_clean_sheet_rate FLOAT DEFAULT 0.0,
|
||||
away_clean_sheet_rate FLOAT DEFAULT 0.0,
|
||||
home_scoring_rate FLOAT DEFAULT 0.0,
|
||||
away_scoring_rate FLOAT DEFAULT 0.0,
|
||||
home_win_streak INT DEFAULT 0,
|
||||
away_win_streak INT DEFAULT 0,
|
||||
implied_home FLOAT DEFAULT 0.33,
|
||||
implied_draw FLOAT DEFAULT 0.33,
|
||||
implied_away FLOAT DEFAULT 0.33,
|
||||
implied_over25 FLOAT DEFAULT 0.5,
|
||||
implied_btts_yes FLOAT DEFAULT 0.5,
|
||||
odds_overround FLOAT DEFAULT 0.0,
|
||||
home_avg_possession FLOAT DEFAULT 50.0,
|
||||
away_avg_possession FLOAT DEFAULT 50.0,
|
||||
home_avg_shots_on_target FLOAT DEFAULT 0.0,
|
||||
away_avg_shots_on_target FLOAT DEFAULT 0.0,
|
||||
home_shot_conversion FLOAT DEFAULT 0.0,
|
||||
away_shot_conversion FLOAT DEFAULT 0.0,
|
||||
home_avg_corners FLOAT DEFAULT 0.0,
|
||||
away_avg_corners FLOAT DEFAULT 0.0,
|
||||
h2h_total INT DEFAULT 0,
|
||||
h2h_home_win_rate FLOAT DEFAULT 0.0,
|
||||
h2h_avg_goals FLOAT DEFAULT 0.0,
|
||||
h2h_over25_rate FLOAT DEFAULT 0.0,
|
||||
h2h_btts_rate FLOAT DEFAULT 0.0,
|
||||
referee_avg_cards FLOAT DEFAULT 0.0,
|
||||
referee_home_bias FLOAT DEFAULT 0.0,
|
||||
referee_avg_goals FLOAT DEFAULT 0.0,
|
||||
league_avg_goals FLOAT DEFAULT 0.0,
|
||||
league_home_win_pct FLOAT DEFAULT 0.0,
|
||||
league_over25_pct FLOAT DEFAULT 0.0,
|
||||
missing_players_impact FLOAT DEFAULT 0.0,
|
||||
calculator_ver TEXT DEFAULT 'v2.0',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_football_ai_features_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 3b. Create basketball_ai_features
|
||||
CREATE TABLE IF NOT EXISTS basketball_ai_features (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
home_elo FLOAT DEFAULT 1500.0,
|
||||
away_elo FLOAT DEFAULT 1500.0,
|
||||
home_home_elo FLOAT DEFAULT 1500.0,
|
||||
away_away_elo FLOAT DEFAULT 1500.0,
|
||||
home_form_elo FLOAT DEFAULT 1500.0,
|
||||
away_form_elo FLOAT DEFAULT 1500.0,
|
||||
elo_diff FLOAT DEFAULT 0.0,
|
||||
home_form_score FLOAT DEFAULT 50.0,
|
||||
away_form_score FLOAT DEFAULT 50.0,
|
||||
home_pts_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_pts_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_win_streak INT DEFAULT 0,
|
||||
away_win_streak INT DEFAULT 0,
|
||||
implied_home FLOAT DEFAULT 0.5,
|
||||
implied_away FLOAT DEFAULT 0.5,
|
||||
implied_over_total FLOAT DEFAULT 0.5,
|
||||
implied_spread_home FLOAT DEFAULT 0.5,
|
||||
odds_overround FLOAT DEFAULT 0.0,
|
||||
home_avg_pts FLOAT DEFAULT 0.0,
|
||||
away_avg_pts FLOAT DEFAULT 0.0,
|
||||
home_avg_rebounds FLOAT DEFAULT 0.0,
|
||||
away_avg_rebounds FLOAT DEFAULT 0.0,
|
||||
home_fg_pct FLOAT DEFAULT 0.0,
|
||||
away_fg_pct FLOAT DEFAULT 0.0,
|
||||
home_avg_three_pt_made FLOAT DEFAULT 0.0,
|
||||
away_avg_three_pt_made FLOAT DEFAULT 0.0,
|
||||
home_avg_turnovers FLOAT DEFAULT 0.0,
|
||||
away_avg_turnovers FLOAT DEFAULT 0.0,
|
||||
h2h_total INT DEFAULT 0,
|
||||
h2h_home_win_rate FLOAT DEFAULT 0.0,
|
||||
h2h_avg_pts FLOAT DEFAULT 0.0,
|
||||
h2h_avg_margin FLOAT DEFAULT 0.0,
|
||||
missing_players_impact FLOAT DEFAULT 0.0,
|
||||
calculator_ver TEXT DEFAULT 'v2.0',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_basketball_ai_features_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
|
||||
);
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Step 4: Copy AI features data (actual schema match)
|
||||
|
||||
INSERT INTO football_ai_features (
|
||||
match_id, home_elo, away_elo, home_form_score, away_form_score,
|
||||
missing_players_impact, calculator_ver, updated_at
|
||||
)
|
||||
SELECT
|
||||
match_id, home_elo, away_elo, home_form_score, away_form_score,
|
||||
missing_players_impact, calculator_ver, updated_at
|
||||
FROM match_ai_features
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_ai_features.match_id AND m.sport = 'football');
|
||||
|
||||
-- For basketball, map available columns
|
||||
INSERT INTO basketball_ai_features (
|
||||
match_id, home_elo, away_elo, home_form_score, away_form_score,
|
||||
missing_players_impact, calculator_ver, updated_at
|
||||
)
|
||||
SELECT
|
||||
match_id, home_elo, away_elo, home_form_score, away_form_score,
|
||||
missing_players_impact, calculator_ver, updated_at
|
||||
FROM match_ai_features
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_ai_features.match_id AND m.sport = 'basketball');
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Step 5: Rename match_player_stats to basketball_player_stats
|
||||
|
||||
ALTER TABLE match_player_stats RENAME TO basketball_player_stats;
|
||||
ALTER INDEX match_player_stats_pkey RENAME TO basketball_player_stats_pkey;
|
||||
ALTER INDEX match_player_stats_match_id_player_id_team_id_key RENAME TO basketball_player_stats_match_id_player_id_team_id_key;
|
||||
ALTER INDEX match_player_stats_match_id_idx RENAME TO basketball_player_stats_match_id_idx;
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT match_player_stats_match_id_fkey TO basketball_player_stats_match_id_fkey;
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT match_player_stats_player_id_fkey TO basketball_player_stats_player_id_fkey;
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT match_player_stats_team_id_fkey TO basketball_player_stats_team_id_fkey;
|
||||
@@ -0,0 +1,12 @@
|
||||
-- Step 6: Add sport column to odd_categories (2.7M rows - should be fine)
|
||||
|
||||
ALTER TABLE odd_categories ADD COLUMN IF NOT EXISTS sport TEXT;
|
||||
ALTER TABLE odd_selections ADD COLUMN IF NOT EXISTS sport TEXT;
|
||||
|
||||
-- Backfill from matches
|
||||
UPDATE odd_categories SET sport = m.sport
|
||||
FROM matches m
|
||||
WHERE odd_categories.match_id = m.id AND odd_categories.sport IS NULL;
|
||||
|
||||
-- Indexes for sport filtering
|
||||
CREATE INDEX IF NOT EXISTS idx_odd_categories_sport ON odd_categories(sport) WHERE sport IS NOT NULL;
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Step 7: Update odd_selections sport from odd_categories
|
||||
-- Run in batches to avoid memory issues
|
||||
|
||||
-- Check how many need updating
|
||||
SELECT 'remaining' as info, COUNT(*) FROM odd_selections WHERE sport IS NULL;
|
||||
|
||||
-- Batch update (run multiple times until 0 remaining)
|
||||
UPDATE odd_selections SET sport = oc.sport
|
||||
FROM odd_categories oc
|
||||
WHERE odd_selections.odd_category_db_id = oc.db_id
|
||||
AND odd_selections.sport IS NULL
|
||||
LIMIT 5000000;
|
||||
|
||||
-- Check remaining
|
||||
SELECT 'after_batch1' as info, COUNT(*) FROM odd_selections WHERE sport IS NULL;
|
||||
@@ -0,0 +1,35 @@
|
||||
-- Step 7: Batch update odd_selections - remaining rows
|
||||
|
||||
-- Batch 2: Next 3M rows
|
||||
WITH batch AS (
|
||||
SELECT os.db_id
|
||||
FROM odd_selections os
|
||||
JOIN odd_categories oc ON os.odd_category_db_id = oc.db_id
|
||||
WHERE os.sport IS NULL
|
||||
LIMIT 3000000
|
||||
)
|
||||
UPDATE odd_selections SET sport = oc.sport
|
||||
FROM batch b
|
||||
JOIN odd_categories oc ON odd_selections.odd_category_db_id = oc.db_id
|
||||
WHERE odd_selections.db_id = b.db_id;
|
||||
|
||||
-- Batch 3: Next 3M rows
|
||||
WITH batch AS (
|
||||
SELECT os.db_id
|
||||
FROM odd_selections os
|
||||
JOIN odd_categories oc ON os.odd_category_db_id = oc.db_id
|
||||
WHERE os.sport IS NULL
|
||||
LIMIT 3000000
|
||||
)
|
||||
UPDATE odd_selections SET sport = oc.sport
|
||||
FROM batch b
|
||||
JOIN odd_categories oc ON odd_selections.odd_category_db_id = oc.db_id
|
||||
WHERE odd_selections.db_id = b.db_id;
|
||||
|
||||
-- Batch 4: Remaining rows
|
||||
UPDATE odd_selections SET sport = oc.sport
|
||||
FROM odd_categories oc
|
||||
WHERE odd_selections.odd_category_db_id = oc.db_id AND odd_selections.sport IS NULL;
|
||||
|
||||
-- Final index
|
||||
CREATE INDEX IF NOT EXISTS idx_odd_selections_sport ON odd_selections(sport) WHERE sport IS NOT NULL;
|
||||
@@ -0,0 +1,28 @@
|
||||
-- Step 8: Verification queries
|
||||
-- Run this BEFORE dropping old tables
|
||||
|
||||
-- Count verification
|
||||
SELECT 'match_team_stats' as tbl, COUNT(*) FROM match_team_stats
|
||||
UNION ALL
|
||||
SELECT 'football_team_stats', COUNT(*) FROM football_team_stats
|
||||
UNION ALL
|
||||
SELECT 'basketball_team_stats', COUNT(*) FROM basketball_team_stats
|
||||
UNION ALL
|
||||
SELECT 'match_ai_features', COUNT(*) FROM match_ai_features
|
||||
UNION ALL
|
||||
SELECT 'football_ai_features', COUNT(*) FROM football_ai_features
|
||||
UNION ALL
|
||||
SELECT 'basketball_ai_features', COUNT(*) FROM basketball_ai_features
|
||||
UNION ALL
|
||||
SELECT 'basketball_player_stats', COUNT(*) FROM basketball_player_stats
|
||||
UNION ALL
|
||||
SELECT 'odd_categories (with sport)', COUNT(*) FROM odd_categories WHERE sport IS NOT NULL
|
||||
UNION ALL
|
||||
SELECT 'odd_selections (with sport)', COUNT(*) FROM odd_selections WHERE sport IS NOT NULL;
|
||||
|
||||
-- Sport distribution
|
||||
SELECT 'football_team_stats by sport' as info, m.sport, COUNT(*)
|
||||
FROM football_team_stats fts JOIN matches m ON m.id = fts.match_id GROUP BY m.sport
|
||||
UNION ALL
|
||||
SELECT 'basketball_team_stats by sport', m.sport, COUNT(*)
|
||||
FROM basketball_team_stats bts JOIN matches m ON m.id = bts.match_id GROUP BY m.sport;
|
||||
@@ -0,0 +1,5 @@
|
||||
-- Step 9: Drop old tables (RUN ONLY AFTER VERIFICATION!)
|
||||
-- Uncomment to execute
|
||||
|
||||
-- DROP TABLE IF EXISTS match_team_stats CASCADE;
|
||||
-- DROP TABLE IF EXISTS match_ai_features CASCADE;
|
||||
@@ -0,0 +1,278 @@
|
||||
-- Migration: Sport-specific table partitioning
|
||||
-- Purpose: Separate football and basketball data for better organization, query performance, and sport-specific schemas
|
||||
-- Date: 2026-04-03
|
||||
-- Impact: Zero data loss, backward compatible during migration, old tables dropped after verification
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================
|
||||
-- 1. match_team_stats → football_team_stats + basketball_team_stats
|
||||
-- ============================================
|
||||
-- Rationale: Football uses possession/shots/corners, Basketball uses points/quarters/fg/3pt
|
||||
|
||||
-- 1a. Create football_team_stats
|
||||
CREATE TABLE IF NOT EXISTS football_team_stats (
|
||||
id SERIAL PRIMARY KEY,
|
||||
match_id TEXT NOT NULL,
|
||||
team_id TEXT NOT NULL,
|
||||
possession_percentage NUMERIC(5,2),
|
||||
shots_on_target INT,
|
||||
shots_off_target INT,
|
||||
total_shots INT,
|
||||
total_passes INT,
|
||||
corners INT,
|
||||
fouls INT,
|
||||
offsides INT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_football_team_stats_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_football_team_stats_team FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uq_football_team_stats_match_team UNIQUE (match_id, team_id)
|
||||
);
|
||||
|
||||
-- 1b. Create basketball_team_stats
|
||||
CREATE TABLE IF NOT EXISTS basketball_team_stats (
|
||||
id SERIAL PRIMARY KEY,
|
||||
match_id TEXT NOT NULL,
|
||||
team_id TEXT NOT NULL,
|
||||
points INT,
|
||||
rebounds INT,
|
||||
assists INT,
|
||||
fg_made INT,
|
||||
fg_attempted INT,
|
||||
three_pt_made INT,
|
||||
three_pt_attempted INT,
|
||||
ft_made INT,
|
||||
ft_attempted INT,
|
||||
steals INT,
|
||||
blocks INT,
|
||||
turnovers INT,
|
||||
q1_score INT,
|
||||
q2_score INT,
|
||||
q3_score INT,
|
||||
q4_score INT,
|
||||
ot_score INT,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_basketball_team_stats_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_basketball_team_stats_team FOREIGN KEY (team_id) REFERENCES teams(id) ON DELETE CASCADE,
|
||||
CONSTRAINT uq_basketball_team_stats_match_team UNIQUE (match_id, team_id)
|
||||
);
|
||||
|
||||
-- 1c. Copy data from match_team_stats to sport-specific tables
|
||||
INSERT INTO football_team_stats (match_id, team_id, possession_percentage, shots_on_target, shots_off_target, total_shots, total_passes, corners, fouls, offsides, created_at)
|
||||
SELECT match_id, team_id, possession_percentage, shots_on_target, shots_off_target, total_shots, total_passes, corners, fouls, offsides, created_at
|
||||
FROM match_team_stats
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_team_stats.match_id AND m.sport = 'football');
|
||||
|
||||
INSERT INTO basketball_team_stats (match_id, team_id, points, rebounds, assists, fg_made, fg_attempted, three_pt_made, three_pt_attempted, ft_made, ft_attempted, steals, blocks, turnovers, q1_score, q2_score, q3_score, q4_score, ot_score, created_at)
|
||||
SELECT match_id, team_id, points, rebounds, assists, fg_made, fg_attempted, three_pt_made, three_pt_attempted, ft_made, ft_attempted, steals, blocks, turnovers, q1_score, q2_score, q3_score, q4_score, ot_score, created_at
|
||||
FROM match_team_stats
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_team_stats.match_id AND m.sport = 'basketball');
|
||||
|
||||
-- 1d. Reset sequences
|
||||
SELECT setval('football_team_stats_id_seq', COALESCE((SELECT MAX(id) FROM football_team_stats), 0) + 1, false);
|
||||
SELECT setval('basketball_team_stats_id_seq', COALESCE((SELECT MAX(id) FROM basketball_team_stats), 0) + 1, false);
|
||||
|
||||
-- 1e. Create indexes
|
||||
CREATE INDEX idx_football_team_stats_match ON football_team_stats(match_id);
|
||||
CREATE INDEX idx_football_team_stats_team ON football_team_stats(team_id);
|
||||
CREATE INDEX idx_basketball_team_stats_match ON basketball_team_stats(match_id);
|
||||
CREATE INDEX idx_basketball_team_stats_team ON basketball_team_stats(team_id);
|
||||
|
||||
-- ============================================
|
||||
-- 2. match_player_stats → basketball_player_stats
|
||||
-- ============================================
|
||||
-- Rationale: match_player_stats is already 99% basketball data
|
||||
|
||||
ALTER TABLE match_player_stats RENAME TO basketball_player_stats;
|
||||
ALTER INDEX match_player_stats_pkey RENAME TO basketball_player_stats_pkey;
|
||||
ALTER INDEX match_player_stats_match_id_player_id_team_id_key RENAME TO basketball_player_stats_match_id_player_id_team_id_key;
|
||||
ALTER INDEX match_player_stats_match_id_idx RENAME TO basketball_player_stats_match_id_idx;
|
||||
|
||||
-- Update FK constraint names
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT "match_player_stats_match_id_fkey" TO "basketball_player_stats_match_id_fkey";
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT "match_player_stats_player_id_fkey" TO "basketball_player_stats_player_id_fkey";
|
||||
ALTER TABLE basketball_player_stats RENAME CONSTRAINT "match_player_stats_team_id_fkey" TO "basketball_player_stats_team_id_fkey";
|
||||
|
||||
-- ============================================
|
||||
-- 3. match_ai_features → football_ai_features + basketball_ai_features
|
||||
-- ============================================
|
||||
-- Rationale: Different feature calculation pipelines per sport
|
||||
|
||||
-- 3a. Create football_ai_features (same structure as current)
|
||||
CREATE TABLE IF NOT EXISTS football_ai_features (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
home_elo FLOAT DEFAULT 1500.0,
|
||||
away_elo FLOAT DEFAULT 1500.0,
|
||||
home_home_elo FLOAT DEFAULT 1500.0,
|
||||
away_away_elo FLOAT DEFAULT 1500.0,
|
||||
home_form_elo FLOAT DEFAULT 1500.0,
|
||||
away_form_elo FLOAT DEFAULT 1500.0,
|
||||
elo_diff FLOAT DEFAULT 0.0,
|
||||
home_form_score FLOAT DEFAULT 50.0,
|
||||
away_form_score FLOAT DEFAULT 50.0,
|
||||
home_goals_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_goals_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_clean_sheet_rate FLOAT DEFAULT 0.0,
|
||||
away_clean_sheet_rate FLOAT DEFAULT 0.0,
|
||||
home_scoring_rate FLOAT DEFAULT 0.0,
|
||||
away_scoring_rate FLOAT DEFAULT 0.0,
|
||||
home_win_streak INT DEFAULT 0,
|
||||
away_win_streak INT DEFAULT 0,
|
||||
implied_home FLOAT DEFAULT 0.33,
|
||||
implied_draw FLOAT DEFAULT 0.33,
|
||||
implied_away FLOAT DEFAULT 0.33,
|
||||
implied_over25 FLOAT DEFAULT 0.5,
|
||||
implied_btts_yes FLOAT DEFAULT 0.5,
|
||||
odds_overround FLOAT DEFAULT 0.0,
|
||||
home_avg_possession FLOAT DEFAULT 50.0,
|
||||
away_avg_possession FLOAT DEFAULT 50.0,
|
||||
home_avg_shots_on_target FLOAT DEFAULT 0.0,
|
||||
away_avg_shots_on_target FLOAT DEFAULT 0.0,
|
||||
home_shot_conversion FLOAT DEFAULT 0.0,
|
||||
away_shot_conversion FLOAT DEFAULT 0.0,
|
||||
home_avg_corners FLOAT DEFAULT 0.0,
|
||||
away_avg_corners FLOAT DEFAULT 0.0,
|
||||
h2h_total INT DEFAULT 0,
|
||||
h2h_home_win_rate FLOAT DEFAULT 0.0,
|
||||
h2h_avg_goals FLOAT DEFAULT 0.0,
|
||||
h2h_over25_rate FLOAT DEFAULT 0.0,
|
||||
h2h_btts_rate FLOAT DEFAULT 0.0,
|
||||
referee_avg_cards FLOAT DEFAULT 0.0,
|
||||
referee_home_bias FLOAT DEFAULT 0.0,
|
||||
referee_avg_goals FLOAT DEFAULT 0.0,
|
||||
league_avg_goals FLOAT DEFAULT 0.0,
|
||||
league_home_win_pct FLOAT DEFAULT 0.0,
|
||||
league_over25_pct FLOAT DEFAULT 0.0,
|
||||
missing_players_impact FLOAT DEFAULT 0.0,
|
||||
calculator_ver TEXT DEFAULT 'v2.0',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_football_ai_features_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 3b. Create basketball_ai_features (adapted for basketball)
|
||||
CREATE TABLE IF NOT EXISTS basketball_ai_features (
|
||||
match_id TEXT PRIMARY KEY,
|
||||
home_elo FLOAT DEFAULT 1500.0,
|
||||
away_elo FLOAT DEFAULT 1500.0,
|
||||
home_home_elo FLOAT DEFAULT 1500.0,
|
||||
away_away_elo FLOAT DEFAULT 1500.0,
|
||||
home_form_elo FLOAT DEFAULT 1500.0,
|
||||
away_form_elo FLOAT DEFAULT 1500.0,
|
||||
elo_diff FLOAT DEFAULT 0.0,
|
||||
home_form_score FLOAT DEFAULT 50.0,
|
||||
away_form_score FLOAT DEFAULT 50.0,
|
||||
home_pts_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_pts_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
away_conceded_avg_5 FLOAT DEFAULT 0.0,
|
||||
home_win_streak INT DEFAULT 0,
|
||||
away_win_streak INT DEFAULT 0,
|
||||
implied_home FLOAT DEFAULT 0.5,
|
||||
implied_away FLOAT DEFAULT 0.5,
|
||||
implied_over_total FLOAT DEFAULT 0.5,
|
||||
implied_spread_home FLOAT DEFAULT 0.5,
|
||||
odds_overround FLOAT DEFAULT 0.0,
|
||||
home_avg_pts FLOAT DEFAULT 0.0,
|
||||
away_avg_pts FLOAT DEFAULT 0.0,
|
||||
home_avg_rebounds FLOAT DEFAULT 0.0,
|
||||
away_avg_rebounds FLOAT DEFAULT 0.0,
|
||||
home_fg_pct FLOAT DEFAULT 0.0,
|
||||
away_fg_pct FLOAT DEFAULT 0.0,
|
||||
home_avg_three_pt_made FLOAT DEFAULT 0.0,
|
||||
away_avg_three_pt_made FLOAT DEFAULT 0.0,
|
||||
home_avg_turnovers FLOAT DEFAULT 0.0,
|
||||
away_avg_turnovers FLOAT DEFAULT 0.0,
|
||||
h2h_total INT DEFAULT 0,
|
||||
h2h_home_win_rate FLOAT DEFAULT 0.0,
|
||||
h2h_avg_pts FLOAT DEFAULT 0.0,
|
||||
h2h_avg_margin FLOAT DEFAULT 0.0,
|
||||
missing_players_impact FLOAT DEFAULT 0.0,
|
||||
calculator_ver TEXT DEFAULT 'v2.0',
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
||||
CONSTRAINT fk_basketball_ai_features_match FOREIGN KEY (match_id) REFERENCES matches(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- 3c. Copy data
|
||||
INSERT INTO football_ai_features (match_id, home_elo, away_elo, home_home_elo, away_away_elo, home_form_elo, away_form_elo, elo_diff, home_form_score, away_form_score, home_goals_avg_5, away_goals_avg_5, home_conceded_avg_5, away_conceded_avg_5, home_clean_sheet_rate, away_clean_sheet_rate, home_scoring_rate, away_scoring_rate, home_win_streak, away_win_streak, implied_home, implied_draw, implied_away, implied_over25, implied_btts_yes, odds_overround, home_avg_possession, away_avg_possession, home_avg_shots_on_target, away_avg_shots_on_target, home_shot_conversion, away_shot_conversion, home_avg_corners, away_avg_corners, h2h_total, h2h_home_win_rate, h2h_avg_goals, h2h_over25_rate, h2h_btts_rate, referee_avg_cards, referee_home_bias, referee_avg_goals, league_avg_goals, league_home_win_pct, league_over25_pct, missing_players_impact, calculator_ver, updated_at)
|
||||
SELECT match_id, home_elo, away_elo, home_home_elo, away_away_elo, home_form_elo, away_form_elo, elo_diff, home_form_score, away_form_score, home_goals_avg_5, away_goals_avg_5, home_conceded_avg_5, away_conceded_avg_5, home_clean_sheet_rate, away_clean_sheet_rate, home_scoring_rate, away_scoring_rate, home_win_streak, away_win_streak, implied_home, implied_draw, implied_away, implied_over25, implied_btts_yes, odds_overround, home_avg_possession, away_avg_possession, home_avg_shots_on_target, away_avg_shots_on_target, home_shot_conversion, away_shot_conversion, home_avg_corners, away_avg_corners, h2h_total, h2h_home_win_rate, h2h_avg_goals, h2h_over25_rate, h2h_btts_rate, referee_avg_cards, referee_home_bias, referee_avg_goals, league_avg_goals, league_home_win_pct, league_over25_pct, missing_players_impact, calculator_ver, updated_at
|
||||
FROM match_ai_features
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_ai_features.match_id AND m.sport = 'football');
|
||||
|
||||
INSERT INTO basketball_ai_features (match_id, home_elo, away_elo, home_home_elo, away_away_elo, home_form_elo, away_form_elo, elo_diff, home_form_score, away_form_score, home_pts_avg_5, away_pts_avg_5, home_conceded_avg_5, away_conceded_avg_5, home_win_streak, away_win_streak, implied_home, implied_away, implied_over_total, implied_spread_home, odds_overround, home_avg_pts, away_avg_pts, home_avg_rebounds, away_avg_rebounds, home_fg_pct, away_fg_pct, home_avg_three_pt_made, away_avg_three_pt_made, home_avg_turnovers, away_avg_turnovers, h2h_total, h2h_home_win_rate, h2h_avg_pts, h2h_avg_margin, missing_players_impact, calculator_ver, updated_at)
|
||||
SELECT match_id, home_elo, away_elo, home_home_elo, away_away_elo, home_form_elo, away_form_elo, elo_diff, home_form_score, away_form_score, home_goals_avg_5, away_goals_avg_5, home_conceded_avg_5, away_conceded_avg_5, home_win_streak, away_win_streak, implied_home, implied_away, implied_over25 as implied_over_total, implied_btts_yes as implied_spread_home, odds_overround, home_avg_possession as home_avg_pts, away_avg_possession as away_avg_pts, 0 as home_avg_rebounds, 0 as away_avg_rebounds, 0 as home_fg_pct, 0 as away_fg_pct, 0 as home_avg_three_pt_made, 0 as away_avg_three_pt_made, 0 as home_avg_turnovers, 0 as away_avg_turnovers, h2h_total, h2h_home_win_rate, h2h_avg_goals * 6 as h2h_avg_pts, 0 as h2h_avg_margin, missing_players_impact, calculator_ver, updated_at
|
||||
FROM match_ai_features
|
||||
WHERE EXISTS (SELECT 1 FROM matches m WHERE m.id = match_ai_features.match_id AND m.sport = 'basketball');
|
||||
|
||||
-- ============================================
|
||||
-- 4. Add sport column to odd_categories for partitioning
|
||||
-- ============================================
|
||||
-- Rationale: 509MB table, needs sport-based filtering
|
||||
|
||||
ALTER TABLE odd_categories ADD COLUMN IF NOT EXISTS sport TEXT;
|
||||
ALTER TABLE odd_selections ADD COLUMN IF NOT EXISTS sport TEXT;
|
||||
|
||||
-- Backfill sport from matches
|
||||
UPDATE odd_categories SET sport = m.sport
|
||||
FROM matches m
|
||||
WHERE odd_categories.match_id = m.id AND odd_categories.sport IS NULL;
|
||||
|
||||
UPDATE odd_selections SET sport = oc.sport
|
||||
FROM odd_categories oc
|
||||
WHERE odd_selections.odd_category_db_id = oc.db_id AND odd_selections.sport IS NULL;
|
||||
|
||||
-- Create indexes for sport filtering
|
||||
CREATE INDEX IF NOT EXISTS idx_odd_categories_sport ON odd_categories(sport) WHERE sport IS NOT NULL;
|
||||
CREATE INDEX IF NOT EXISTS idx_odd_selections_sport ON odd_selections(sport) WHERE sport IS NOT NULL;
|
||||
|
||||
-- ============================================
|
||||
-- 5. Add indexes for match_player_participation (via JOIN optimization)
|
||||
-- ============================================
|
||||
-- Rationale: 833MB table, needs efficient match_id lookups
|
||||
-- Note: Can't use subquery in index predicate, so just optimize match_id lookup
|
||||
CREATE INDEX IF NOT EXISTS idx_match_player_participation_match ON match_player_participation(match_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_match_player_participation_team ON match_player_participation(team_id);
|
||||
|
||||
-- ============================================
|
||||
-- 6. Verification queries (run these before dropping old tables)
|
||||
-- ============================================
|
||||
-- DO $$
|
||||
-- DECLARE
|
||||
-- v_old_stats INT;
|
||||
-- v_football_stats INT;
|
||||
-- v_basketball_stats INT;
|
||||
-- v_old_ai INT;
|
||||
-- v_football_ai INT;
|
||||
-- v_basketball_ai INT;
|
||||
-- BEGIN
|
||||
-- SELECT COUNT(*) INTO v_old_stats FROM match_team_stats;
|
||||
-- SELECT COUNT(*) INTO v_football_stats FROM football_team_stats;
|
||||
-- SELECT COUNT(*) INTO v_basketball_stats FROM basketball_team_stats;
|
||||
--
|
||||
-- SELECT COUNT(*) INTO v_old_ai FROM match_ai_features;
|
||||
-- SELECT COUNT(*) INTO v_football_ai FROM football_ai_features;
|
||||
-- SELECT COUNT(*) INTO v_basketball_ai FROM basketball_ai_features;
|
||||
--
|
||||
-- RAISE NOTICE '=== VERIFICATION ===';
|
||||
-- RAISE NOTICE 'match_team_stats: % = football: % + basketball: %', v_old_stats, v_football_stats, v_basketball_stats;
|
||||
-- RAISE NOTICE 'match_ai_features: % = football: % + basketball: %', v_old_ai, v_football_ai, v_basketball_ai;
|
||||
--
|
||||
-- IF v_old_stats != v_football_stats + v_basketball_stats THEN
|
||||
-- RAISE EXCEPTION 'Data mismatch in team stats!';
|
||||
-- END IF;
|
||||
--
|
||||
-- IF v_old_ai != v_football_ai + v_basketball_ai THEN
|
||||
-- RAISE EXCEPTION 'Data mismatch in AI features!';
|
||||
-- END IF;
|
||||
--
|
||||
-- RAISE NOTICE '✅ VERIFICATION PASSED - Safe to drop old tables';
|
||||
-- END $$;
|
||||
|
||||
-- ============================================
|
||||
-- 7. Drop old tables (UNCOMMENT AFTER VERIFICATION)
|
||||
-- ============================================
|
||||
-- DROP TABLE IF EXISTS match_team_stats CASCADE;
|
||||
-- DROP TABLE IF EXISTS match_ai_features CASCADE;
|
||||
|
||||
COMMIT;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
-- AlterTable: Add odds columns to live_matches
|
||||
-- Run this migration when DB is accessible
|
||||
|
||||
ALTER TABLE "live_matches"
|
||||
ADD COLUMN IF NOT EXISTS "odds" JSONB,
|
||||
ADD COLUMN IF NOT EXISTS "odds_updated_at" TIMESTAMP(3);
|
||||
|
||||
-- Create index for faster queries on odds_updated_at
|
||||
CREATE INDEX IF NOT EXISTS "live_matches_odds_updated_at_idx" ON "live_matches"("odds_updated_at");
|
||||
|
||||
-- Comment for documentation
|
||||
COMMENT ON COLUMN "live_matches"."odds" IS 'Bahis oranları JSON: {"MS": {"1": 2.10, "X": 3.40, "2": 3.20}, "AU25": {"Alt": 2.05, "Üst": 1.75}}';
|
||||
COMMENT ON COLUMN "live_matches"."odds_updated_at" IS 'Oranların son güncellenme zamanı';
|
||||
Executable
+3
@@ -0,0 +1,3 @@
|
||||
# Please do not edit this file manually
|
||||
# It should be added in your version-control system (i.e. Git)
|
||||
provider = "postgresql"
|
||||
Executable
+785
@@ -0,0 +1,785 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x", "linux-musl-openssl-3.0.x"]
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Domain Models — Sports Data
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model Country {
|
||||
id String @id
|
||||
name String @unique
|
||||
flagUrl String? @map("flag_url")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
leagues League[]
|
||||
|
||||
@@map("countries")
|
||||
}
|
||||
|
||||
model League {
|
||||
id String @id
|
||||
name String
|
||||
countryId String? @map("country_id")
|
||||
sport Sport
|
||||
competitionSlug String? @map("competition_slug")
|
||||
code String?
|
||||
logoUrl String? @map("logo_url")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
country Country? @relation(fields: [countryId], references: [id])
|
||||
liveMatches LiveMatch[]
|
||||
matches Match[]
|
||||
|
||||
@@unique([name, countryId, sport])
|
||||
@@index([sport])
|
||||
@@index([countryId])
|
||||
@@map("leagues")
|
||||
}
|
||||
|
||||
model Team {
|
||||
id String @id
|
||||
name String
|
||||
slug String?
|
||||
sport Sport
|
||||
logoUrl String? @map("logo_url")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
awayMatchesLive LiveMatch[] @relation("AwayTeamLive")
|
||||
homeMatchesLive LiveMatch[] @relation("HomeTeamLive")
|
||||
playerEvents MatchPlayerEvents[]
|
||||
playerParticipations MatchPlayerParticipation[]
|
||||
basketballPlayerStats BasketballPlayerStats[]
|
||||
footballTeamStats FootballTeamStats[]
|
||||
basketballTeamStats BasketballTeamStats[]
|
||||
awayMatches Match[] @relation("AwayTeam")
|
||||
homeMatches Match[] @relation("HomeTeam")
|
||||
eloRating TeamEloRating?
|
||||
|
||||
@@index([name])
|
||||
@@index([sport])
|
||||
@@map("teams")
|
||||
}
|
||||
|
||||
model Player {
|
||||
id String @id
|
||||
name String
|
||||
slug String? @unique
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
assistEvents MatchPlayerEvents[] @relation("AssistPlayer")
|
||||
playerEvents MatchPlayerEvents[] @relation("EventPlayer")
|
||||
substitutedOutEvents MatchPlayerEvents[] @relation("SubstitutedOut")
|
||||
participations MatchPlayerParticipation[]
|
||||
playerStats BasketballPlayerStats[]
|
||||
|
||||
@@index([name])
|
||||
@@map("players")
|
||||
}
|
||||
|
||||
model Match {
|
||||
id String @id
|
||||
leagueId String? @map("league_id")
|
||||
homeTeamId String? @map("home_team_id")
|
||||
awayTeamId String? @map("away_team_id")
|
||||
sport Sport
|
||||
matchName String? @map("match_name")
|
||||
matchSlug String? @map("match_slug")
|
||||
mstUtc BigInt @map("mst_utc")
|
||||
status String?
|
||||
state String?
|
||||
scoreHome Int? @map("score_home")
|
||||
scoreAway Int? @map("score_away")
|
||||
htScoreHome Int? @map("ht_score_home")
|
||||
htScoreAway Int? @map("ht_score_away")
|
||||
winner String?
|
||||
iddaaCode String? @map("iddaa_code")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
footballAiFeatures FootballAiFeature?
|
||||
basketballAiFeatures BasketballAiFeature?
|
||||
officials MatchOfficial[]
|
||||
playerEvents MatchPlayerEvents[]
|
||||
playerParticipations MatchPlayerParticipation[]
|
||||
basketballPlayerStats BasketballPlayerStats[]
|
||||
footballTeamStats FootballTeamStats[]
|
||||
basketballTeamStats BasketballTeamStats[]
|
||||
awayTeam Team? @relation("AwayTeam", fields: [awayTeamId], references: [id])
|
||||
homeTeam Team? @relation("HomeTeam", fields: [homeTeamId], references: [id])
|
||||
league League? @relation(fields: [leagueId], references: [id])
|
||||
oddCategories OddCategory[]
|
||||
prediction Prediction?
|
||||
couponItems UserCouponItem[]
|
||||
|
||||
@@index([awayTeamId])
|
||||
@@index([homeTeamId])
|
||||
@@index([iddaaCode])
|
||||
@@index([leagueId])
|
||||
@@index([mstUtc(sort: Desc)])
|
||||
@@index([sport])
|
||||
@@index([state])
|
||||
@@index([status, mstUtc(sort: Desc)])
|
||||
@@map("matches")
|
||||
}
|
||||
|
||||
model LiveMatch {
|
||||
id String @id
|
||||
leagueId String? @map("league_id")
|
||||
homeTeamId String? @map("home_team_id")
|
||||
awayTeamId String? @map("away_team_id")
|
||||
sport String?
|
||||
matchName String? @map("match_name")
|
||||
matchSlug String? @map("match_slug")
|
||||
mstUtc BigInt? @map("mst_utc")
|
||||
status String?
|
||||
state String?
|
||||
substate String?
|
||||
scoreHome Int? @map("score_home")
|
||||
scoreAway Int? @map("score_away")
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
odds Json?
|
||||
oddsUpdatedAt DateTime? @map("odds_updated_at")
|
||||
refereeName String? @map("referee_name")
|
||||
lineups Json?
|
||||
sidelined Json?
|
||||
awayTeam Team? @relation("AwayTeamLive", fields: [awayTeamId], references: [id])
|
||||
homeTeam Team? @relation("HomeTeamLive", fields: [homeTeamId], references: [id])
|
||||
league League? @relation(fields: [leagueId], references: [id])
|
||||
|
||||
@@index([mstUtc])
|
||||
@@index([state])
|
||||
@@map("live_matches")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Match Details — Stats, Events, Participation, Officials
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model FootballAiFeature {
|
||||
matchId String @id @map("match_id")
|
||||
homeElo Float @default(1500.0) @map("home_elo")
|
||||
awayElo Float @default(1500.0) @map("away_elo")
|
||||
homeHomeElo Float @default(1500.0) @map("home_home_elo")
|
||||
awayAwayElo Float @default(1500.0) @map("away_away_elo")
|
||||
homeFormElo Float @default(1500.0) @map("home_form_elo")
|
||||
awayFormElo Float @default(1500.0) @map("away_form_elo")
|
||||
eloDiff Float @default(0.0) @map("elo_diff")
|
||||
homeFormScore Float @default(50.0) @map("home_form_score")
|
||||
awayFormScore Float @default(50.0) @map("away_form_score")
|
||||
homeGoalsAvg5 Float @default(0.0) @map("home_goals_avg_5")
|
||||
awayGoalsAvg5 Float @default(0.0) @map("away_goals_avg_5")
|
||||
homeConcededAvg5 Float @default(0.0) @map("home_conceded_avg_5")
|
||||
awayConcededAvg5 Float @default(0.0) @map("away_conceded_avg_5")
|
||||
homeCleanSheetRate Float @default(0.0) @map("home_clean_sheet_rate")
|
||||
awayCleanSheetRate Float @default(0.0) @map("away_clean_sheet_rate")
|
||||
homeScoringRate Float @default(0.0) @map("home_scoring_rate")
|
||||
awayScoringRate Float @default(0.0) @map("away_scoring_rate")
|
||||
homeWinStreak Int @default(0) @map("home_win_streak")
|
||||
awayWinStreak Int @default(0) @map("away_win_streak")
|
||||
impliedHome Float @default(0.33) @map("implied_home")
|
||||
impliedDraw Float @default(0.33) @map("implied_draw")
|
||||
impliedAway Float @default(0.33) @map("implied_away")
|
||||
impliedOver25 Float @default(0.5) @map("implied_over25")
|
||||
impliedBttsYes Float @default(0.5) @map("implied_btts_yes")
|
||||
oddsOverround Float @default(0.0) @map("odds_overround")
|
||||
homeAvgPossession Float @default(50.0) @map("home_avg_possession")
|
||||
awayAvgPossession Float @default(50.0) @map("away_avg_possession")
|
||||
homeAvgShotsOnTarget Float @default(0.0) @map("home_avg_shots_on_target")
|
||||
awayAvgShotsOnTarget Float @default(0.0) @map("away_avg_shots_on_target")
|
||||
homeShotConversion Float @default(0.0) @map("home_shot_conversion")
|
||||
awayShotConversion Float @default(0.0) @map("away_shot_conversion")
|
||||
homeAvgCorners Float @default(0.0) @map("home_avg_corners")
|
||||
awayAvgCorners Float @default(0.0) @map("away_avg_corners")
|
||||
h2hTotal Int @default(0) @map("h2h_total")
|
||||
h2hHomeWinRate Float @default(0.0) @map("h2h_home_win_rate")
|
||||
h2hAvgGoals Float @default(0.0) @map("h2h_avg_goals")
|
||||
h2hOver25Rate Float @default(0.0) @map("h2h_over25_rate")
|
||||
h2hBttsRate Float @default(0.0) @map("h2h_btts_rate")
|
||||
refereeAvgCards Float @default(0.0) @map("referee_avg_cards")
|
||||
refereeHomeBias Float @default(0.0) @map("referee_home_bias")
|
||||
refereeAvgGoals Float @default(0.0) @map("referee_avg_goals")
|
||||
leagueAvgGoals Float @default(0.0) @map("league_avg_goals")
|
||||
leagueHomeWinPct Float @default(0.0) @map("league_home_win_pct")
|
||||
leagueOver25Pct Float @default(0.0) @map("league_over25_pct")
|
||||
missingPlayersImpact Float @default(0.0) @map("missing_players_impact")
|
||||
calculatorVer String @default("v2.0") @map("calculator_ver")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("football_ai_features")
|
||||
}
|
||||
|
||||
model BasketballAiFeature {
|
||||
matchId String @id @map("match_id")
|
||||
homeElo Float @default(1500.0) @map("home_elo")
|
||||
awayElo Float @default(1500.0) @map("away_elo")
|
||||
homeHomeElo Float @default(1500.0) @map("home_home_elo")
|
||||
awayAwayElo Float @default(1500.0) @map("away_away_elo")
|
||||
homeFormElo Float @default(1500.0) @map("home_form_elo")
|
||||
awayFormElo Float @default(1500.0) @map("away_form_elo")
|
||||
eloDiff Float @default(0.0) @map("elo_diff")
|
||||
homeFormScore Float @default(50.0) @map("home_form_score")
|
||||
awayFormScore Float @default(50.0) @map("away_form_score")
|
||||
homePtsAvg5 Float @default(0.0) @map("home_pts_avg_5")
|
||||
awayPtsAvg5 Float @default(0.0) @map("away_pts_avg_5")
|
||||
homeConcededAvg5 Float @default(0.0) @map("home_conceded_avg_5")
|
||||
awayConcededAvg5 Float @default(0.0) @map("away_conceded_avg_5")
|
||||
homeWinStreak Int @default(0) @map("home_win_streak")
|
||||
awayWinStreak Int @default(0) @map("away_win_streak")
|
||||
impliedHome Float @default(0.5) @map("implied_home")
|
||||
impliedAway Float @default(0.5) @map("implied_away")
|
||||
impliedOverTotal Float @default(0.5) @map("implied_over_total")
|
||||
impliedSpreadHome Float @default(0.5) @map("implied_spread_home")
|
||||
oddsOverround Float @default(0.0) @map("odds_overround")
|
||||
homeAvgPts Float @default(0.0) @map("home_avg_pts")
|
||||
awayAvgPts Float @default(0.0) @map("away_avg_pts")
|
||||
homeAvgRebounds Float @default(0.0) @map("home_avg_rebounds")
|
||||
awayAvgRebounds Float @default(0.0) @map("away_avg_rebounds")
|
||||
homeFgPct Float @default(0.0) @map("home_fg_pct")
|
||||
awayFgPct Float @default(0.0) @map("away_fg_pct")
|
||||
homeAvgThreePtMade Float @default(0.0) @map("home_avg_three_pt_made")
|
||||
awayAvgThreePtMade Float @default(0.0) @map("away_avg_three_pt_made")
|
||||
homeAvgTurnovers Float @default(0.0) @map("home_avg_turnovers")
|
||||
awayAvgTurnovers Float @default(0.0) @map("away_avg_turnovers")
|
||||
h2hTotal Int @default(0) @map("h2h_total")
|
||||
h2hHomeWinRate Float @default(0.0) @map("h2h_home_win_rate")
|
||||
h2hAvgPts Float @default(0.0) @map("h2h_avg_pts")
|
||||
h2hAvgMargin Float @default(0.0) @map("h2h_avg_margin")
|
||||
missingPlayersImpact Float @default(0.0) @map("missing_players_impact")
|
||||
calculatorVer String @default("v2.0") @map("calculator_ver")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("basketball_ai_features")
|
||||
}
|
||||
|
||||
model TeamEloRating {
|
||||
teamId String @id @map("team_id")
|
||||
overallElo Float @default(1500.0) @map("overall_elo")
|
||||
homeElo Float @default(1500.0) @map("home_elo")
|
||||
awayElo Float @default(1500.0) @map("away_elo")
|
||||
formElo Float @default(1500.0) @map("form_elo")
|
||||
matchesPlayed Int @default(0) @map("matches_played")
|
||||
recentForm String @default("") @map("recent_form")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("team_elo_ratings")
|
||||
}
|
||||
|
||||
model MatchPlayerEvents {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
playerId String @map("player_id")
|
||||
teamId String @map("team_id")
|
||||
eventType EventType @map("event_type")
|
||||
eventSubtype String? @map("event_subtype")
|
||||
timeMinute String @map("time_minute")
|
||||
timeSeconds Int? @map("time_seconds")
|
||||
periodId Int? @map("period_id")
|
||||
assistPlayerId String? @map("assist_player_id")
|
||||
scoreAfter String? @map("score_after")
|
||||
playerOutId String? @map("player_out_id")
|
||||
position MatchPosition?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
assistPlayer Player? @relation("AssistPlayer", fields: [assistPlayerId], references: [id])
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
player Player @relation("EventPlayer", fields: [playerId], references: [id], onDelete: Cascade)
|
||||
substitutedOut Player? @relation("SubstitutedOut", fields: [playerOutId], references: [id])
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([assistPlayerId])
|
||||
@@index([eventType])
|
||||
@@index([matchId])
|
||||
@@index([playerId])
|
||||
@@index([teamId])
|
||||
@@map("match_player_events")
|
||||
}
|
||||
|
||||
model MatchPlayerParticipation {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
playerId String @map("player_id")
|
||||
teamId String @map("team_id")
|
||||
position PlayerPosition?
|
||||
shirtNumber Int? @map("shirt_number")
|
||||
isStarting Boolean @default(true) @map("is_starting")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([matchId, playerId, teamId])
|
||||
@@index([matchId])
|
||||
@@index([playerId])
|
||||
@@index([teamId])
|
||||
@@map("match_player_participation")
|
||||
}
|
||||
|
||||
model BasketballPlayerStats {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
playerId String @map("player_id")
|
||||
teamId String @map("team_id")
|
||||
minutes String?
|
||||
points Int?
|
||||
rebounds Int?
|
||||
assists Int?
|
||||
steals Int?
|
||||
blocks Int?
|
||||
turnovers Int?
|
||||
fgMade Int? @map("fg_made")
|
||||
fgAttempted Int? @map("fg_attempted")
|
||||
threePtMade Int? @map("three_pt_made")
|
||||
threePtAttempted Int? @map("three_pt_attempted")
|
||||
ftMade Int? @map("ft_made")
|
||||
ftAttempted Int? @map("ft_attempted")
|
||||
fouls Int?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
player Player @relation(fields: [playerId], references: [id], onDelete: Cascade)
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([matchId, playerId, teamId])
|
||||
@@index([matchId])
|
||||
@@map("basketball_player_stats")
|
||||
}
|
||||
|
||||
model FootballTeamStats {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
teamId String @map("team_id")
|
||||
possessionPercentage Float? @map("possession_percentage")
|
||||
shotsOnTarget Int? @map("shots_on_target")
|
||||
shotsOffTarget Int? @map("shots_off_target")
|
||||
totalShots Int? @map("total_shots")
|
||||
totalPasses Int? @map("total_passes")
|
||||
corners Int?
|
||||
fouls Int?
|
||||
offsides Int?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([matchId, teamId])
|
||||
@@index([matchId])
|
||||
@@index([teamId])
|
||||
@@map("football_team_stats")
|
||||
}
|
||||
|
||||
model BasketballTeamStats {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
teamId String @map("team_id")
|
||||
points Int?
|
||||
rebounds Int?
|
||||
assists Int?
|
||||
fgMade Int? @map("fg_made")
|
||||
fgAttempted Int? @map("fg_attempted")
|
||||
threePtMade Int? @map("three_pt_made")
|
||||
threePtAttempted Int? @map("three_pt_attempted")
|
||||
ftMade Int? @map("ft_made")
|
||||
ftAttempted Int? @map("ft_attempted")
|
||||
steals Int?
|
||||
blocks Int?
|
||||
turnovers Int?
|
||||
fouls Int?
|
||||
q1Score Int? @map("q1_score")
|
||||
q2Score Int? @map("q2_score")
|
||||
q3Score Int? @map("q3_score")
|
||||
q4Score Int? @map("q4_score")
|
||||
otScore Int? @map("ot_score")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
team Team @relation(fields: [teamId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([matchId, teamId])
|
||||
@@index([matchId])
|
||||
@@index([teamId])
|
||||
@@map("basketball_team_stats")
|
||||
}
|
||||
|
||||
model MatchOfficial {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
name String
|
||||
roleId Int @map("role_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
role OfficialRole @relation(fields: [roleId], references: [id])
|
||||
|
||||
@@unique([matchId, name, roleId])
|
||||
@@index([matchId])
|
||||
@@index([roleId])
|
||||
@@map("match_officials")
|
||||
}
|
||||
|
||||
model OfficialRole {
|
||||
id Int @id @default(autoincrement())
|
||||
name String @unique
|
||||
officials MatchOfficial[]
|
||||
|
||||
@@map("official_roles")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Odds & Predictions
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model OddCategory {
|
||||
dbId Int @id @default(autoincrement()) @map("db_id")
|
||||
matchId String @map("match_id")
|
||||
categoryJsonId Int? @map("category_json_id")
|
||||
name String?
|
||||
sport Sport?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
selections OddSelection[]
|
||||
|
||||
@@unique([matchId, name])
|
||||
@@index([matchId])
|
||||
@@index([sport])
|
||||
@@map("odd_categories")
|
||||
}
|
||||
|
||||
model OddSelection {
|
||||
dbId Int @id @default(autoincrement()) @map("db_id")
|
||||
categoryId Int @map("odd_category_db_id")
|
||||
name String?
|
||||
oddValue String? @map("odd_value")
|
||||
position String?
|
||||
sov Float?
|
||||
state String?
|
||||
sport Sport?
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @default(now()) @map("updated_at")
|
||||
category OddCategory @relation(fields: [categoryId], references: [dbId], onDelete: Cascade)
|
||||
history OddsHistory[]
|
||||
|
||||
@@unique([categoryId, name])
|
||||
@@index([categoryId])
|
||||
@@index([sport])
|
||||
@@map("odd_selections")
|
||||
}
|
||||
|
||||
model OddsHistory {
|
||||
id BigInt @id @default(autoincrement())
|
||||
selectionId Int @map("selection_id")
|
||||
matchId String @map("match_id")
|
||||
previousValue Float @map("previous_value")
|
||||
newValue Float @map("new_value")
|
||||
bookmaker String? @default("MACKOLIK")
|
||||
changeTime DateTime @default(now()) @map("change_time")
|
||||
selection OddSelection @relation(fields: [selectionId], references: [dbId], onDelete: Cascade)
|
||||
|
||||
@@index([matchId, changeTime])
|
||||
@@index([selectionId])
|
||||
@@map("odds_history")
|
||||
}
|
||||
|
||||
model Prediction {
|
||||
matchId String @id @map("match_id")
|
||||
predictionJson Json @map("prediction_json")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("predictions")
|
||||
}
|
||||
|
||||
model AiPredictionsLog {
|
||||
id Int @id @default(autoincrement())
|
||||
matchId String @map("match_id")
|
||||
modelVersion String @map("model_version")
|
||||
recommendedBets Json? @map("recommended_bets")
|
||||
confidenceScore Float? @map("confidence_score")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
isResolved Boolean @default(false) @map("is_resolved")
|
||||
actualResult String? @map("actual_result")
|
||||
isCorrect Boolean? @map("is_correct")
|
||||
accuracyScore Float? @map("accuracy_score")
|
||||
|
||||
@@index([matchId])
|
||||
@@index([createdAt(sort: Desc)])
|
||||
@@map("ai_predictions_log")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// User Domain — Auth, Coupons, Usage
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
email String @unique
|
||||
passwordHash String @map("password_hash")
|
||||
firstName String? @map("first_name")
|
||||
lastName String? @map("last_name")
|
||||
role UserRole @default(user)
|
||||
subscriptionStatus SubscriptionStatus @default(free) @map("subscription_status")
|
||||
subscriptionExpiresAt DateTime? @map("subscription_expires_at")
|
||||
encryptedApiKey String? @map("encrypted_api_key")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
analyses Analysis[]
|
||||
refreshTokens RefreshToken[]
|
||||
usageLimit UsageLimit?
|
||||
coupons UserCoupon[]
|
||||
totoCoupons TotoCoupon[]
|
||||
|
||||
@@index([email])
|
||||
@@index([subscriptionStatus, subscriptionExpiresAt])
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
id String @id @default(uuid())
|
||||
token String @unique
|
||||
userId String @map("user_id")
|
||||
expiresAt DateTime @map("expires_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([token])
|
||||
@@index([userId])
|
||||
@@map("refresh_tokens")
|
||||
}
|
||||
|
||||
model UsageLimit {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @unique @map("user_id")
|
||||
analysisCount Int @default(0) @map("analysis_count")
|
||||
couponCount Int @default(0) @map("coupon_count")
|
||||
lastResetDate DateTime @map("last_reset_date") @db.Date
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([lastResetDate])
|
||||
@@map("usage_limits")
|
||||
}
|
||||
|
||||
model Analysis {
|
||||
id Int @id @default(autoincrement())
|
||||
userId String @map("user_id")
|
||||
matchIds String @map("match_ids")
|
||||
analysisResultJson String @map("analysis_result_json")
|
||||
isDeleted Boolean @default(false) @map("is_deleted")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([createdAt(sort: Desc)])
|
||||
@@map("analyses")
|
||||
}
|
||||
|
||||
model UserCoupon {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
strategy String
|
||||
totalOdds Float @map("total_odds")
|
||||
status String @default("PENDING")
|
||||
isPublic Boolean @default(false)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
couponItems UserCouponItem[]
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([status])
|
||||
@@map("user_coupons")
|
||||
}
|
||||
|
||||
model UserCouponItem {
|
||||
id Int @id @default(autoincrement())
|
||||
couponId String @map("coupon_id")
|
||||
matchId String @map("match_id")
|
||||
selection String
|
||||
oddAtTime Float @map("odd_at_time")
|
||||
isCorrect Boolean? @map("is_correct")
|
||||
coupon UserCoupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
|
||||
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([couponId])
|
||||
@@map("user_coupon_items")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Spor Toto Domain
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model TotoBulletin {
|
||||
id String @id @default(uuid())
|
||||
gameCycleNo Int @unique @map("game_cycle_no")
|
||||
programName String? @map("program_name")
|
||||
season String?
|
||||
status TotoBulletinStatus @default(UPCOMING)
|
||||
payinBeginDate DateTime? @map("payin_begin_date")
|
||||
payinEndDate DateTime? @map("payin_end_date")
|
||||
poolTotal Float? @map("pool_total")
|
||||
rolloverAmount Float? @map("rollover_amount")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
matches TotoBulletinMatch[]
|
||||
result TotoResult?
|
||||
coupons TotoCoupon[]
|
||||
|
||||
@@index([status])
|
||||
@@map("toto_bulletins")
|
||||
}
|
||||
|
||||
model TotoBulletinMatch {
|
||||
id Int @id @default(autoincrement())
|
||||
bulletinId String @map("bulletin_id")
|
||||
matchOrder Int @map("match_order")
|
||||
homeTeamName String @map("home_team_name")
|
||||
awayTeamName String @map("away_team_name")
|
||||
leagueName String? @map("league_name")
|
||||
kickoffTime DateTime? @map("kickoff_time")
|
||||
matchId String? @map("match_id")
|
||||
result TotoMatchResult?
|
||||
isCancelled Boolean @default(false) @map("is_cancelled")
|
||||
drawResult TotoMatchResult? @map("draw_result")
|
||||
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([bulletinId, matchOrder])
|
||||
@@index([bulletinId])
|
||||
@@index([matchId])
|
||||
@@map("toto_bulletin_matches")
|
||||
}
|
||||
|
||||
model TotoResult {
|
||||
id String @id @default(uuid())
|
||||
bulletinId String @unique @map("bulletin_id")
|
||||
winners15 Int @default(0) @map("winners_15")
|
||||
prize15 Float? @map("prize_15")
|
||||
winners14 Int @default(0) @map("winners_14")
|
||||
prize14 Float? @map("prize_14")
|
||||
winners13 Int @default(0) @map("winners_13")
|
||||
prize13 Float? @map("prize_13")
|
||||
winners12 Int @default(0) @map("winners_12")
|
||||
prize12 Float? @map("prize_12")
|
||||
rolloverNext Float? @map("rollover_next")
|
||||
poolDistributed Float? @map("pool_distributed")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("toto_results")
|
||||
}
|
||||
|
||||
model TotoCoupon {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
bulletinId String @map("bulletin_id")
|
||||
strategy String?
|
||||
columnCount Int @map("column_count")
|
||||
totalCost Float @map("total_cost")
|
||||
status String @default("PENDING")
|
||||
totalPrize Float? @map("total_prize")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
columns TotoColumn[]
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
bulletin TotoBulletin @relation(fields: [bulletinId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([userId])
|
||||
@@index([bulletinId])
|
||||
@@index([status])
|
||||
@@map("toto_coupons")
|
||||
}
|
||||
|
||||
model TotoColumn {
|
||||
id Int @id @default(autoincrement())
|
||||
couponId String @map("coupon_id")
|
||||
predictions String @db.VarChar(15)
|
||||
correctCount Int? @map("correct_count")
|
||||
prizeAmount Float? @map("prize_amount")
|
||||
coupon TotoCoupon @relation(fields: [couponId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([couponId])
|
||||
@@index([correctCount])
|
||||
@@map("toto_columns")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// System & i18n
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
model AppSetting {
|
||||
key String @id
|
||||
value String?
|
||||
updatedAt DateTime @default(now()) @updatedAt @map("updated_at")
|
||||
|
||||
@@map("app_settings")
|
||||
}
|
||||
|
||||
model Translation {
|
||||
id String @id @default(uuid())
|
||||
key String
|
||||
locale String
|
||||
value String
|
||||
namespace String @default("common")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
@@unique([key, locale, namespace])
|
||||
@@index([key])
|
||||
@@index([locale])
|
||||
@@index([namespace])
|
||||
@@map("translations")
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// Enums
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
|
||||
enum Sport {
|
||||
football
|
||||
basketball
|
||||
}
|
||||
|
||||
enum UserRole {
|
||||
user
|
||||
superadmin
|
||||
}
|
||||
|
||||
enum SubscriptionStatus {
|
||||
free
|
||||
active
|
||||
expired
|
||||
}
|
||||
|
||||
enum PlayerPosition {
|
||||
goalkeeper
|
||||
defender
|
||||
midfielder
|
||||
striker
|
||||
}
|
||||
|
||||
enum EventType {
|
||||
goal
|
||||
card
|
||||
substitute
|
||||
}
|
||||
|
||||
enum MatchPosition {
|
||||
home
|
||||
away
|
||||
}
|
||||
|
||||
enum TotoBulletinStatus {
|
||||
UPCOMING
|
||||
ACTIVE
|
||||
COMPLETED
|
||||
CANCELLED
|
||||
}
|
||||
|
||||
enum TotoMatchResult {
|
||||
HOME_WIN
|
||||
DRAW
|
||||
AWAY_WIN
|
||||
}
|
||||
Executable
+104
@@ -0,0 +1,104 @@
|
||||
import { PrismaClient, UserRole, SubscriptionStatus } from '@prisma/client';
|
||||
import * as bcrypt from 'bcrypt';
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
async function main() {
|
||||
console.log('🌱 Starting database seed...');
|
||||
|
||||
// 1. Create Superadmin User
|
||||
const superadminEmail = process.env.SUPERADMIN_EMAIL || 'admin@iddaai.com';
|
||||
const superadminPassword = process.env.SUPERADMIN_PASSWORD || 'Admin123!';
|
||||
|
||||
const existingAdmin = await prisma.user.findUnique({
|
||||
where: { email: superadminEmail },
|
||||
});
|
||||
|
||||
if (!existingAdmin) {
|
||||
const hashedPassword = await bcrypt.hash(superadminPassword, 12);
|
||||
|
||||
const admin = await prisma.user.create({
|
||||
data: {
|
||||
email: superadminEmail,
|
||||
passwordHash: hashedPassword,
|
||||
firstName: 'Super',
|
||||
lastName: 'Admin',
|
||||
role: UserRole.superadmin,
|
||||
subscriptionStatus: SubscriptionStatus.active,
|
||||
isActive: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Create usage limit for admin
|
||||
await prisma.usageLimit.create({
|
||||
data: {
|
||||
userId: admin.id,
|
||||
analysisCount: 0,
|
||||
couponCount: 0,
|
||||
lastResetDate: new Date(),
|
||||
},
|
||||
});
|
||||
|
||||
console.log(`✅ Superadmin created: ${superadminEmail}`);
|
||||
} else {
|
||||
console.log(`ℹ️ Superadmin already exists: ${superadminEmail}`);
|
||||
}
|
||||
|
||||
// 2. Create App Settings
|
||||
const defaultSettings = [
|
||||
{ key: 'ai_engine_version', value: 'v8.0' },
|
||||
{ key: 'daily_analysis_limit_free', value: '3' },
|
||||
{ key: 'daily_coupon_limit_free', value: '1' },
|
||||
{ key: 'daily_analysis_limit_premium', value: '50' },
|
||||
{ key: 'daily_coupon_limit_premium', value: '10' },
|
||||
{ key: 'maintenance_mode', value: 'false' },
|
||||
];
|
||||
|
||||
for (const setting of defaultSettings) {
|
||||
await prisma.appSetting.upsert({
|
||||
where: { key: setting.key },
|
||||
update: { value: setting.value },
|
||||
create: setting,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ App settings configured');
|
||||
|
||||
// 3. Create sample translations (optional)
|
||||
const translations = [
|
||||
{ key: 'welcome', locale: 'tr', value: 'Hoş geldiniz', namespace: 'common' },
|
||||
{ key: 'welcome', locale: 'en', value: 'Welcome', namespace: 'common' },
|
||||
{ key: 'login_success', locale: 'tr', value: 'Giriş başarılı', namespace: 'auth' },
|
||||
{ key: 'login_success', locale: 'en', value: 'Login successful', namespace: 'auth' },
|
||||
{ key: 'prediction_generated', locale: 'tr', value: 'Tahmin oluşturuldu', namespace: 'prediction' },
|
||||
{ key: 'prediction_generated', locale: 'en', value: 'Prediction generated', namespace: 'prediction' },
|
||||
];
|
||||
|
||||
for (const t of translations) {
|
||||
await prisma.translation.upsert({
|
||||
where: {
|
||||
key_locale_namespace: {
|
||||
key: t.key,
|
||||
locale: t.locale,
|
||||
namespace: t.namespace,
|
||||
},
|
||||
},
|
||||
update: { value: t.value },
|
||||
create: t,
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ Translations seeded');
|
||||
|
||||
console.log('🎉 Database seed completed!');
|
||||
}
|
||||
|
||||
main()
|
||||
.then(async () => {
|
||||
await prisma.$disconnect();
|
||||
})
|
||||
.catch(async (e) => {
|
||||
console.error('❌ Seed error:', e);
|
||||
await prisma.$disconnect();
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user