sync
This commit is contained in:
51
biome.json
Normal file
51
biome.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.3.8/schema.json",
|
||||
"vcs": {
|
||||
"enabled": true,
|
||||
"clientKind": "git",
|
||||
"useIgnoreFile": true,
|
||||
"defaultBranch": "main"
|
||||
},
|
||||
"files": {
|
||||
"ignoreUnknown": false
|
||||
},
|
||||
"formatter": {
|
||||
"enabled": true,
|
||||
"indentStyle": "tab"
|
||||
},
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"suspicious": {
|
||||
"noConsole": {
|
||||
"level":"on",
|
||||
"options": {
|
||||
"allow": ["error", "info", "warn"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"correctness": {
|
||||
"useJsxKeyInIterable": "error"
|
||||
}
|
||||
}
|
||||
},
|
||||
"assist": {
|
||||
"enabled": true,
|
||||
"actions": {
|
||||
"source": {
|
||||
"recommended": true,
|
||||
"organizeImports": "on"
|
||||
}
|
||||
}
|
||||
},
|
||||
"javascript": {
|
||||
"formatter": {
|
||||
"quoteStyle": "double"
|
||||
}
|
||||
},
|
||||
"json": {
|
||||
"formatter": {
|
||||
"enabled": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
import {defineConfig} from 'drizzle-kit'
|
||||
|
||||
if(!process.env.DATABASE_URL){
|
||||
throw new Error('DATABASE_URL is not defined')
|
||||
if(!process.env.DATABASE_HOST){
|
||||
throw new Error('Data base info missing ')
|
||||
}
|
||||
|
||||
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
|
||||
|
||||
export default defineConfig({
|
||||
dialect:'postgresql',
|
||||
schema: "./src/db/schema",
|
||||
out: './migrations',
|
||||
dbCredentials: {url: process.env.DATABASE_URL}
|
||||
dbCredentials: {url: dbURL}
|
||||
})
|
||||
|
||||
13
migrations/0000_slim_shiva.sql
Normal file
13
migrations/0000_slim_shiva.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
CREATE TYPE "public"."match_status" AS ENUM('scheduled', 'live', 'finished');--> statement-breakpoint
|
||||
CREATE TABLE "matches" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"sport" text NOT NULL,
|
||||
"home_team" text NOT NULL,
|
||||
"away_team" text NOT NULL,
|
||||
"status" "match_status" DEFAULT 'scheduled' NOT NULL,
|
||||
"start_time" timestamp,
|
||||
"end_time" timestamp,
|
||||
"home_score" integer DEFAULT 0 NOT NULL,
|
||||
"away_score" integer DEFAULT 0 NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL
|
||||
);
|
||||
3
migrations/0001_dark_ben_grimm.sql
Normal file
3
migrations/0001_dark_ben_grimm.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
CREATE TABLE "commentary" (
|
||||
"id" serial PRIMARY KEY NOT NULL
|
||||
);
|
||||
12
migrations/0002_careless_franklin_richards.sql
Normal file
12
migrations/0002_careless_franklin_richards.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
ALTER TABLE "commentary" ADD COLUMN "match_id" integer NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "minute" integer;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "sequence" integer;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "period" text;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "event_type" text;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "actor" text;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "team" text;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "message" text NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "metadata" jsonb;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "tags" text[];--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD COLUMN "created_at" timestamp DEFAULT now() NOT NULL;--> statement-breakpoint
|
||||
ALTER TABLE "commentary" ADD CONSTRAINT "commentary_match_id_matches_id_fk" FOREIGN KEY ("match_id") REFERENCES "public"."matches"("id") ON DELETE no action ON UPDATE no action;
|
||||
107
migrations/meta/0000_snapshot.json
Normal file
107
migrations/meta/0000_snapshot.json
Normal file
@@ -0,0 +1,107 @@
|
||||
{
|
||||
"id": "cc319841-30d4-4928-ba5c-fe3352ba1bf8",
|
||||
"prevId": "00000000-0000-0000-0000-000000000000",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.matches": {
|
||||
"name": "matches",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"sport": {
|
||||
"name": "sport",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"home_team": {
|
||||
"name": "home_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"away_team": {
|
||||
"name": "away_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "match_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'scheduled'"
|
||||
},
|
||||
"start_time": {
|
||||
"name": "start_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"end_time": {
|
||||
"name": "end_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"home_score": {
|
||||
"name": "home_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"away_score": {
|
||||
"name": "away_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.match_status": {
|
||||
"name": "match_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"scheduled",
|
||||
"live",
|
||||
"finished"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
126
migrations/meta/0001_snapshot.json
Normal file
126
migrations/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"id": "919e2530-f5a0-4aab-8a61-b4a3bacad982",
|
||||
"prevId": "cc319841-30d4-4928-ba5c-fe3352ba1bf8",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.commentary": {
|
||||
"name": "commentary",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.matches": {
|
||||
"name": "matches",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"sport": {
|
||||
"name": "sport",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"home_team": {
|
||||
"name": "home_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"away_team": {
|
||||
"name": "away_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "match_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'scheduled'"
|
||||
},
|
||||
"start_time": {
|
||||
"name": "start_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"end_time": {
|
||||
"name": "end_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"home_score": {
|
||||
"name": "home_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"away_score": {
|
||||
"name": "away_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.match_status": {
|
||||
"name": "match_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"scheduled",
|
||||
"live",
|
||||
"finished"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
207
migrations/meta/0002_snapshot.json
Normal file
207
migrations/meta/0002_snapshot.json
Normal file
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"id": "0e98917e-e04a-4424-aa92-9c66371c9960",
|
||||
"prevId": "919e2530-f5a0-4aab-8a61-b4a3bacad982",
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"tables": {
|
||||
"public.commentary": {
|
||||
"name": "commentary",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"match_id": {
|
||||
"name": "match_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"minute": {
|
||||
"name": "minute",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"sequence": {
|
||||
"name": "sequence",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"period": {
|
||||
"name": "period",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"event_type": {
|
||||
"name": "event_type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"actor": {
|
||||
"name": "actor",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"team": {
|
||||
"name": "team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"message": {
|
||||
"name": "message",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"metadata": {
|
||||
"name": "metadata",
|
||||
"type": "jsonb",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"tags": {
|
||||
"name": "tags",
|
||||
"type": "text[]",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"commentary_match_id_matches_id_fk": {
|
||||
"name": "commentary_match_id_matches_id_fk",
|
||||
"tableFrom": "commentary",
|
||||
"tableTo": "matches",
|
||||
"columnsFrom": [
|
||||
"match_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "no action",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
},
|
||||
"public.matches": {
|
||||
"name": "matches",
|
||||
"schema": "",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "serial",
|
||||
"primaryKey": true,
|
||||
"notNull": true
|
||||
},
|
||||
"sport": {
|
||||
"name": "sport",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"home_team": {
|
||||
"name": "home_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"away_team": {
|
||||
"name": "away_team",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "match_status",
|
||||
"typeSchema": "public",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "'scheduled'"
|
||||
},
|
||||
"start_time": {
|
||||
"name": "start_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"end_time": {
|
||||
"name": "end_time",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": false
|
||||
},
|
||||
"home_score": {
|
||||
"name": "home_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"away_score": {
|
||||
"name": "away_score",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "timestamp",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"default": "now()"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"policies": {},
|
||||
"checkConstraints": {},
|
||||
"isRLSEnabled": false
|
||||
}
|
||||
},
|
||||
"enums": {
|
||||
"public.match_status": {
|
||||
"name": "match_status",
|
||||
"schema": "public",
|
||||
"values": [
|
||||
"scheduled",
|
||||
"live",
|
||||
"finished"
|
||||
]
|
||||
}
|
||||
},
|
||||
"schemas": {},
|
||||
"sequences": {},
|
||||
"roles": {},
|
||||
"policies": {},
|
||||
"views": {},
|
||||
"_meta": {
|
||||
"columns": {},
|
||||
"schemas": {},
|
||||
"tables": {}
|
||||
}
|
||||
}
|
||||
27
migrations/meta/_journal.json
Normal file
27
migrations/meta/_journal.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"version": "7",
|
||||
"dialect": "postgresql",
|
||||
"entries": [
|
||||
{
|
||||
"idx": 0,
|
||||
"version": "7",
|
||||
"when": 1770045471965,
|
||||
"tag": "0000_slim_shiva",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1770045929269,
|
||||
"tag": "0001_dark_ben_grimm",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"version": "7",
|
||||
"when": 1770060258034,
|
||||
"tag": "0002_careless_franklin_richards",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
190
package-lock.json
generated
190
package-lock.json
generated
@@ -13,9 +13,12 @@
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"express": "^5.2.1",
|
||||
"pg": "^8.18.0",
|
||||
"wscat": "^6.1.0"
|
||||
"postgres": "^3.4.8",
|
||||
"wscat": "^6.1.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
@@ -24,6 +27,169 @@
|
||||
"typescript": "^5.9.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/biome": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-2.3.13.tgz",
|
||||
"integrity": "sha512-Fw7UsV0UAtWIBIm0M7g5CRerpu1eKyKAXIazzxhbXYUyMkwNrkX/KLkGI7b+uVDQ5cLUMfOC9vR60q9IDYDstA==",
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"bin": {
|
||||
"biome": "bin/biome"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/biome"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@biomejs/cli-darwin-arm64": "2.3.13",
|
||||
"@biomejs/cli-darwin-x64": "2.3.13",
|
||||
"@biomejs/cli-linux-arm64": "2.3.13",
|
||||
"@biomejs/cli-linux-arm64-musl": "2.3.13",
|
||||
"@biomejs/cli-linux-x64": "2.3.13",
|
||||
"@biomejs/cli-linux-x64-musl": "2.3.13",
|
||||
"@biomejs/cli-win32-arm64": "2.3.13",
|
||||
"@biomejs/cli-win32-x64": "2.3.13"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-arm64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.13.tgz",
|
||||
"integrity": "sha512-0OCwP0/BoKzyJHnFdaTk/i7hIP9JHH9oJJq6hrSCPmJPo8JWcJhprK4gQlhFzrwdTBAW4Bjt/RmCf3ZZe59gwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-darwin-x64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.13.tgz",
|
||||
"integrity": "sha512-AGr8OoemT/ejynbIu56qeil2+F2WLkIjn2d8jGK1JkchxnMUhYOfnqc9sVzcRxpG9Ycvw4weQ5sprRvtb7Yhcw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.13.tgz",
|
||||
"integrity": "sha512-xvOiFkrDNu607MPMBUQ6huHmBG1PZLOrqhtK6pXJW3GjfVqJg0Z/qpTdhXfcqWdSZHcT+Nct2fOgewZvytESkw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-arm64-musl": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.13.tgz",
|
||||
"integrity": "sha512-TUdDCSY+Eo/EHjhJz7P2GnWwfqet+lFxBZzGHldrvULr59AgahamLs/N85SC4+bdF86EhqDuuw9rYLvLFWWlXA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.13.tgz",
|
||||
"integrity": "sha512-s+YsZlgiXNq8XkgHs6xdvKDFOj/bwTEevqEY6rC2I3cBHbxXYU1LOZstH3Ffw9hE5tE1sqT7U23C00MzkXztMw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-linux-x64-musl": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.13.tgz",
|
||||
"integrity": "sha512-0bdwFVSbbM//Sds6OjtnmQGp4eUjOTt6kHvR/1P0ieR9GcTUAlPNvPC3DiavTqq302W34Ae2T6u5VVNGuQtGlQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-arm64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.13.tgz",
|
||||
"integrity": "sha512-QweDxY89fq0VvrxME+wS/BXKmqMrOTZlN9SqQ79kQSIc3FrEwvW/PvUegQF6XIVaekncDykB5dzPqjbwSKs9DA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@biomejs/cli-win32-x64": {
|
||||
"version": "2.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.13.tgz",
|
||||
"integrity": "sha512-trDw2ogdM2lyav9WFQsdsfdVy1dvZALymRpgmWsvSez0BJzBjulhOT/t+wyKeh3pZWvwP3VMs1SoOKwO3wecMQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=14.21.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@dotenvx/dotenvx": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/@dotenvx/dotenvx/-/dotenvx-1.52.0.tgz",
|
||||
@@ -2171,6 +2337,19 @@
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres": {
|
||||
"version": "3.4.8",
|
||||
"resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.8.tgz",
|
||||
"integrity": "sha512-d+JFcLM17njZaOLkv6SCev7uoLaBtfK86vMUXhW1Z4glPWh4jozno9APvW/XKFJ3CCxVoC7OL38BqRydtu5nGg==",
|
||||
"license": "Unlicense",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/porsager"
|
||||
}
|
||||
},
|
||||
"node_modules/postgres-array": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
|
||||
@@ -3138,6 +3317,15 @@
|
||||
"engines": {
|
||||
"node": ">=0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
package.json
10
package.json
@@ -6,7 +6,10 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "dotenvx run -f .env -- node --watch src/index.js",
|
||||
"start": "dotenvx run -f .env -- node src/index.js"
|
||||
"start": "dotenvx run -f .env -- node src/index.js",
|
||||
"db:generate": "drizzle-kit generate",
|
||||
"db:migrate": "drizzle-kit push",
|
||||
"db:studio": "drizzle-kit studio"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -20,9 +23,12 @@
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"express": "^5.2.1",
|
||||
"pg": "^8.18.0",
|
||||
"wscat": "^6.1.0"
|
||||
"postgres": "^3.4.8",
|
||||
"wscat": "^6.1.0",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.13",
|
||||
"@types/node": "^25.1.0",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
|
||||
25
src/db/db.js
25
src/db/db.js
@@ -1,12 +1,21 @@
|
||||
import { drizzle } from "drizzle-orm/node-postgres";
|
||||
import pg from 'pg'
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
|
||||
if(!process.env.DATABASE_URL){
|
||||
throw new Error('DATABASE_URL is not defined')
|
||||
|
||||
if(!process.env.DATABASE_HOST){
|
||||
throw new Error('Data base info missing ')
|
||||
}
|
||||
|
||||
export const pool = new pg.Pool({
|
||||
connectionString: process.env.DATABASE_URL
|
||||
})
|
||||
const dbURL = `postgres://${process.env.DATABASE_USER}:${process.env.DATABASE_PASSWORD}@${process.env.DATABASE_HOST}:${process.env.DATABASE_PORT}/${process.env.DATABASE_DB}`;
|
||||
|
||||
export const db = drizzle(pool)
|
||||
const queryClient = postgres(dbURL, {
|
||||
max: 10,
|
||||
idle_timeout: 60,
|
||||
connect_timeout: 30,
|
||||
max_lifetime: 1000 * 6 * 5,
|
||||
onnotice: (n) => {
|
||||
console.info("PG notice: ", n.message);
|
||||
},
|
||||
});
|
||||
|
||||
export const db = drizzle({ client: queryClient })
|
||||
20
src/db/schema/commentary.js
Normal file
20
src/db/schema/commentary.js
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
|
||||
import { serial, pgTable, text, integer, jsonb, timestamp} from "drizzle-orm/pg-core";
|
||||
import {matches} from './matches'
|
||||
|
||||
|
||||
export const commentary = pgTable('commentary', {
|
||||
id: serial('id').primaryKey(),
|
||||
matchId: integer('match_id').notNull().references(()=> matches.id),
|
||||
minute: integer('minute'),
|
||||
sequence: integer('sequence'),
|
||||
period: text("period"),
|
||||
eventType: text('event_type'),
|
||||
actor: text('actor'),
|
||||
team: text('team'),
|
||||
message: text('message').notNull(),
|
||||
metadata: jsonb('metadata'),
|
||||
tags: text('tags').array(),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow()
|
||||
})
|
||||
@@ -1,9 +1,17 @@
|
||||
import { pgEnum, serial } from "drizzle-orm/pg-core";
|
||||
|
||||
import { pgEnum, serial , integer, pgTable, text,timestamp,} from "drizzle-orm/pg-core";
|
||||
|
||||
export const matchStatusEnum = pgEnum('match_status',['scheduled','live', 'finished'])
|
||||
|
||||
export const matches = pgTable('matches', {
|
||||
id: serial('id').primaryKey(),
|
||||
sport: text('sport').noNull(),
|
||||
homeTeam: text('home_team').notNull()
|
||||
sport: text('sport').notNull(),
|
||||
homeTeam: text('home_team').notNull(),
|
||||
awayTeam: text("away_team").notNull(),
|
||||
status: matchStatusEnum('status').notNull().default('scheduled'),
|
||||
startTime: timestamp('start_time'),
|
||||
endTime: timestamp('end_time'),
|
||||
homeScore: integer('home_score').notNull().default(0),
|
||||
awayScore: integer('away_score').notNull().default(0),
|
||||
createdAt: timestamp('created_at').notNull().defaultNow()
|
||||
})
|
||||
@@ -1,17 +1,22 @@
|
||||
import express from 'express'
|
||||
|
||||
// routes
|
||||
import { matchRouter } from './routes/matches.route.js'
|
||||
|
||||
const app = express()
|
||||
|
||||
const port = process.env.PORT || 8081
|
||||
|
||||
app.use(express.json())
|
||||
|
||||
app.get('/',(req,res)=>{
|
||||
app.get('/',(_,res)=>{
|
||||
res.send('Hello from express server!')
|
||||
})
|
||||
|
||||
app.use('/matches', matchRouter)
|
||||
|
||||
app.listen(port, ()=>{
|
||||
console.log(`Listening on port ${port}`)
|
||||
console.info(`Listening on port ${port}`)
|
||||
})
|
||||
|
||||
|
||||
|
||||
43
src/routes/matches.route.js
Normal file
43
src/routes/matches.route.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Router } from "express";
|
||||
import { createMatchSchema, listMatchesQuerySchema } from "../../validation/matches.js";
|
||||
import { db } from "../db/db.js";
|
||||
import { matches } from "../db/schema/matches.js";
|
||||
import { getMatchStatus } from "../utils/match-status.utlis.js";
|
||||
|
||||
export const matchRouter = Router()
|
||||
|
||||
|
||||
matchRouter.get('/', (_,res)=>{
|
||||
const parsed = listMatchesQuerySchema.safeParse(req.query)
|
||||
if(!parsed.success){
|
||||
return res.status(400).json({error: 'Invalid payload', details: JSON.stringify(parsed.error)})
|
||||
}
|
||||
|
||||
return res.status(200).json({message: 'Matches List'})
|
||||
})
|
||||
|
||||
matchRouter.post('/', async (req,res)=>{
|
||||
const parsed = createMatchSchema.safeParse(req.body)
|
||||
|
||||
const {data: {startTime, endTime, homeScore, awayScore}} = parsed
|
||||
|
||||
if(!parsed.success){
|
||||
return res.status(400).json({error: 'Invalid payload', details: JSON.stringify(parsed.error)})
|
||||
}
|
||||
|
||||
try {
|
||||
const [event] = await db.insert(matches).values({
|
||||
...parsed.data,
|
||||
startTime: new Date(startTime),
|
||||
endTime: new Date(endTime),
|
||||
homeScore: homeScore ?? 0,
|
||||
awayScore: awayScore ?? 0,
|
||||
status: getMatchStatus(startTime, endTime)
|
||||
}).returning()
|
||||
|
||||
res.status(201).json({data: event})
|
||||
} catch (e) {
|
||||
|
||||
return res.status(500).json({error: 'Failed to create match.', details: JSON.stringify(e)})
|
||||
}
|
||||
})
|
||||
35
src/utils/match-status.utlis.js
Normal file
35
src/utils/match-status.utlis.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { MATCH_STATUS } from "../../validation/matches.js"
|
||||
|
||||
export const getMatchStatus =(startTime, endTime, now = new Date())=>{
|
||||
const start = new Date(startTime)
|
||||
const end = new Date(endTime)
|
||||
|
||||
if(Number.isNaN(start.getTime()) || Number.isNaN(end.getTime())){
|
||||
return null
|
||||
}
|
||||
|
||||
if (now < start){
|
||||
return MATCH_STATUS.SCHEDULED
|
||||
}
|
||||
|
||||
if (now >= end){
|
||||
return MATCH_STATUS.FINISHED
|
||||
}
|
||||
|
||||
return MATCH_STATUS.LIVE
|
||||
}
|
||||
|
||||
export const syncMatchStatus = async(match, updateStatus)=>{
|
||||
const nextStatus = getMatchStatus(match.startTime, match.endTime)
|
||||
|
||||
if(!nextStatus){
|
||||
return match.status
|
||||
}
|
||||
|
||||
if(match.status !== nextStatus){
|
||||
await updateStatus(nextStatus)
|
||||
match.status = nextStatus
|
||||
}
|
||||
|
||||
return match.status
|
||||
}
|
||||
40
validation/matches.js
Normal file
40
validation/matches.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
export const MATCH_STATUS = {
|
||||
SCHEDULED: 'scheduled',
|
||||
LIVE: 'live',
|
||||
FINISHED: 'finished',
|
||||
};
|
||||
|
||||
export const listMatchesQuerySchema = z.object({
|
||||
limit: z.coerce.number().int().positive().max(100).optional(),
|
||||
});
|
||||
|
||||
export const matchIdParamSchema = z.object({
|
||||
id: z.coerce.number().int().positive(),
|
||||
});
|
||||
|
||||
export const createMatchSchema = z.object({
|
||||
sport: z.string().min(1),
|
||||
homeTeam: z.string().min(1),
|
||||
awayTeam: z.string().min(1),
|
||||
startTime: z.iso.datetime(),
|
||||
endTime: z.iso.datetime(),
|
||||
homeScore: z.coerce.number().int().nonnegative().optional(),
|
||||
awayScore: z.coerce.number().int().nonnegative().optional(),
|
||||
}).superRefine((data, ctx) => {
|
||||
const start = new Date(data.startTime);
|
||||
const end = new Date(data.endTime);
|
||||
if (end <= start) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: "endTime must be chronologically after startTime",
|
||||
path: ["endTime"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const updateScoreSchema = z.object({
|
||||
homeScore: z.coerce.number().int().nonnegative(),
|
||||
awayScore: z.coerce.number().int().nonnegative(),
|
||||
});
|
||||
Reference in New Issue
Block a user