diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..1112106 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,34 @@ +name: build + +on: + workflow_call: + +jobs: + build: + runs-on: prox-1 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check if Bun is installed + id: check-bun + run: | + if command -v bun &> /dev/null; then + echo "bun-exists=true" >> $GITHUB_OUTPUT + echo "Bun is already installed: $(bun --version)" + else + echo "bun-exists=false" >> $GITHUB_OUTPUT + echo "Bun is not installed" + fi + + - name: Setup Bun + if: steps.check-bun.outputs.bun-exists == 'false' + uses: oven-sh/setup-bun@v1 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Build project + run: bun --bun run build diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..e80d24f --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,17 @@ +name: deploy + +on: + workflow_call: + +jobs: + deploy: + runs-on: prox-1 + steps: + - name: Restart NextJS service + run: sudo systemctl restart nextjs.service + + - name: Reload systemd daemon + run: sudo systemctl daemon-reload + + - name: Check service status + run: sudo systemctl status nextjs.service --no-pager diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..27dc6d0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,14 @@ +name: main + +on: + push: + branches: + - main + +jobs: + build: + uses: ./.github/workflows/build.yml + + deploy: + needs: build + uses: ./.github/workflows/deploy.yml diff --git a/drizzle.config.ts b/drizzle.config.ts index d04dd9f..2c4f334 100644 --- a/drizzle.config.ts +++ b/drizzle.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from "drizzle-kit"; export default defineConfig({ - schema: "./src/db/schema.ts", + schema: "./src/db/schema/schema.ts", out: "./migrations", dialect: "sqlite", dbCredentials: { diff --git a/migrations/0002_real_korath.sql b/migrations/0002_real_korath.sql new file mode 100644 index 0000000..c6a26e7 --- /dev/null +++ b/migrations/0002_real_korath.sql @@ -0,0 +1 @@ +DROP INDEX `user_steam_id_unique`; \ No newline at end of file diff --git a/migrations/meta/0002_snapshot.json b/migrations/meta/0002_snapshot.json new file mode 100644 index 0000000..dcda644 --- /dev/null +++ b/migrations/meta/0002_snapshot.json @@ -0,0 +1,281 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5d600618-0105-4104-b150-c63015ae55c7", + "prevId": "eae7a237-8469-4a6a-acac-1910adfc98ff", + "tables": { + "user": { + "name": "user", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "steam_id": { + "name": "steam_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ticket_id": { + "name": "ticket_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "ticket_type": { + "name": "ticket_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "user_ticket_id_unique": { + "name": "user_ticket_id_unique", + "columns": [ + "ticket_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "team": { + "name": "team", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "member": { + "name": "member", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "user_team_unique": { + "name": "user_team_unique", + "columns": [ + "user_id", + "team_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "member_user_id_user_id_fk": { + "name": "member_user_id_user_id_fk", + "tableFrom": "member", + "tableTo": "user", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "member_team_id_team_id_fk": { + "name": "member_team_id_team_id_fk", + "tableFrom": "member", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tournament_team": { + "name": "tournament_team", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "tournament_id": { + "name": "tournament_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "team_id": { + "name": "team_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "registration_date": { + "name": "registration_date", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tournament_team_unique": { + "name": "tournament_team_unique", + "columns": [ + "tournament_id", + "team_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "tournament_team_tournament_id_tournament_id_fk": { + "name": "tournament_team_tournament_id_tournament_id_fk", + "tableFrom": "tournament_team", + "tableTo": "tournament", + "columnsFrom": [ + "tournament_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tournament_team_team_id_team_id_fk": { + "name": "tournament_team_team_id_team_id_fk", + "tableFrom": "tournament_team", + "tableTo": "team", + "columnsFrom": [ + "team_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tournament": { + "name": "tournament", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index d1887de..0807fac 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -15,6 +15,13 @@ "when": 1752632968857, "tag": "0001_cool_ma_gnuci", "breakpoints": true + }, + { + "idx": 2, + "version": "6", + "when": 1754400044471, + "tag": "0002_real_korath", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index e612cf3..3b9c9c3 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -17,8 +17,6 @@ const workSans = Work_Sans({ subsets: ["latin"], }); -// Commented out for now, because it doesn't work having client components in the layout file - export const metadata: Metadata = { title: "TipiLAN 2025", description: "TipiLAN 2025 – Eesti suurim tudengite korraldatud LAN!", diff --git a/src/db/drizzle.ts b/src/db/drizzle.ts index 45e6d66..462bf11 100644 --- a/src/db/drizzle.ts +++ b/src/db/drizzle.ts @@ -1,6 +1,6 @@ import { drizzle } from "drizzle-orm/bun-sqlite"; import { Database } from "bun:sqlite"; -import * as schema from "./schema"; +import * as schema from "./schema/schema"; const sqlite = new Database("data/tipilan.db"); export const db = drizzle(sqlite, { schema }); diff --git a/src/db/schema.ts b/src/db/schema.ts deleted file mode 100644 index 5783e90..0000000 --- a/src/db/schema.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { - sqliteTable, - text, - integer, - uniqueIndex, -} from "drizzle-orm/sqlite-core"; -import { relations } from "drizzle-orm"; -import { createId } from "@paralleldrive/cuid2"; - -// Roles enum equivalent -export const RoleEnum = { - VISITOR: "VISITOR", - PARTICIPANT: "PARTICIPANT", - TEAMMATE: "TEAMMATE", - CAPTAIN: "CAPTAIN", - ADMIN: "ADMIN", -} as const; - -export type Role = (typeof RoleEnum)[keyof typeof RoleEnum]; - -// User table -export const users = sqliteTable("user", { - id: text("id") - .primaryKey() - .notNull() - .$defaultFn(() => createId()), - email: text("email").notNull(), - steamId: text("steam_id").unique(), - firstName: text("first_name").notNull(), - lastName: text("last_name").notNull(), - ticketId: text("ticket_id").unique(), - ticketType: text("ticket_type"), -}); - -// Team table -export const teams = sqliteTable("team", { - id: text("id") - .primaryKey() - .notNull() - .$defaultFn(() => createId()), - name: text("name").notNull(), -}); - -// Member table (join table for User and Team with role) -export const members = sqliteTable( - "member", - { - id: text("id") - .primaryKey() - .notNull() - .$defaultFn(() => createId()), - userId: text("user_id") - .notNull() - .references(() => users.id, { onDelete: "cascade" }), - teamId: text("team_id").references(() => teams.id, { onDelete: "cascade" }), - role: text("role", { - enum: Object.values(RoleEnum) as [string, ...string[]], - }).notNull(), - }, - (table) => { - return { - userTeamUnique: uniqueIndex("user_team_unique").on( - table.userId, - table.teamId, - ), - }; - }, -); - -// Tournament table -export const tournaments = sqliteTable("tournament", { - id: text("id") - .primaryKey() - .notNull() - .$defaultFn(() => createId()), - name: text("name").notNull(), -}); - -// TournamentTeam join table -export const tournamentTeams = sqliteTable( - "tournament_team", - { - id: text("id") - .primaryKey() - .notNull() - .$defaultFn(() => createId()), - tournamentId: text("tournament_id") - .notNull() - .references(() => tournaments.id, { onDelete: "cascade" }), - teamId: text("team_id") - .notNull() - .references(() => teams.id, { onDelete: "cascade" }), - registrationDate: integer("registration_date", { mode: "timestamp" }) - .notNull() - .$defaultFn(() => new Date()), - }, - (table) => { - return { - tournamentTeamUnique: uniqueIndex("tournament_team_unique").on( - table.tournamentId, - table.teamId, - ), - }; - }, -); - -// Relations -export const usersRelations = relations(users, ({ many }) => ({ - members: many(members), -})); - -export const teamsRelations = relations(teams, ({ many }) => ({ - members: many(members), - tournamentTeams: many(tournamentTeams), -})); - -export const membersRelations = relations(members, ({ one }) => ({ - user: one(users, { fields: [members.userId], references: [users.id] }), - team: one(teams, { fields: [members.teamId], references: [teams.id] }), -})); - -export const tournamentsRelations = relations(tournaments, ({ many }) => ({ - tournamentTeams: many(tournamentTeams), -})); - -export const tournamentTeamsRelations = relations( - tournamentTeams, - ({ one }) => ({ - tournament: one(tournaments, { - fields: [tournamentTeams.tournamentId], - references: [tournaments.id], - }), - team: one(teams, { - fields: [tournamentTeams.teamId], - references: [teams.id], - }), - }), -); diff --git a/src/db/schema/index.ts b/src/db/schema/index.ts new file mode 100644 index 0000000..773aee5 --- /dev/null +++ b/src/db/schema/index.ts @@ -0,0 +1,14 @@ +// Export all types +export * from "./types"; + +// Export all tables +export * from "./users"; +export * from "./teams"; +export * from "./members"; +export * from "./tournaments"; + +// Export session table +// export * from ./session"; + +// Export all relations +export * from "./relations"; diff --git a/src/db/schema/members.ts b/src/db/schema/members.ts new file mode 100644 index 0000000..2422365 --- /dev/null +++ b/src/db/schema/members.ts @@ -0,0 +1,31 @@ +import { sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core"; +import { createId } from "@paralleldrive/cuid2"; +import { users } from "./users"; +import { teams } from "./teams"; +import { RoleEnum } from "./types"; + +// Member table (join table for User and Team with role) +export const members = sqliteTable( + "member", + { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + userId: text("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + teamId: text("team_id").references(() => teams.id, { onDelete: "cascade" }), + role: text("role", { + enum: Object.values(RoleEnum) as [string, ...string[]], + }).notNull(), + }, + (table) => { + return { + userTeamUnique: uniqueIndex("user_team_unique").on( + table.userId, + table.teamId, + ), + }; + }, +); diff --git a/src/db/schema/relations.ts b/src/db/schema/relations.ts new file mode 100644 index 0000000..0121974 --- /dev/null +++ b/src/db/schema/relations.ts @@ -0,0 +1,42 @@ +import { relations } from "drizzle-orm"; +import { users } from "./users"; +import { teams } from "./teams"; +import { members } from "./members"; +import { tournaments, tournamentTeams } from "./tournaments"; + +// User relations +export const usersRelations = relations(users, ({ many }) => ({ + members: many(members), +})); + +// Team relations +export const teamsRelations = relations(teams, ({ many }) => ({ + members: many(members), + tournamentTeams: many(tournamentTeams), +})); + +// Member relations +export const membersRelations = relations(members, ({ one }) => ({ + user: one(users, { fields: [members.userId], references: [users.id] }), + team: one(teams, { fields: [members.teamId], references: [teams.id] }), +})); + +// Tournament relations +export const tournamentsRelations = relations(tournaments, ({ many }) => ({ + tournamentTeams: many(tournamentTeams), +})); + +// Tournament team relations +export const tournamentTeamsRelations = relations( + tournamentTeams, + ({ one }) => ({ + tournament: one(tournaments, { + fields: [tournamentTeams.tournamentId], + references: [tournaments.id], + }), + team: one(teams, { + fields: [tournamentTeams.teamId], + references: [teams.id], + }), + }), +); diff --git a/src/db/schema/schema.ts b/src/db/schema/schema.ts new file mode 100644 index 0000000..ed4cf78 --- /dev/null +++ b/src/db/schema/schema.ts @@ -0,0 +1,2 @@ +// Re-export all schema components from modular files +export * from "./index"; diff --git a/src/db/schema/teams.ts b/src/db/schema/teams.ts new file mode 100644 index 0000000..3e5c500 --- /dev/null +++ b/src/db/schema/teams.ts @@ -0,0 +1,11 @@ +import { sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { createId } from "@paralleldrive/cuid2"; + +// Team table +export const teams = sqliteTable("team", { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + name: text("name").notNull(), +}); diff --git a/src/db/schema/tournaments.ts b/src/db/schema/tournaments.ts new file mode 100644 index 0000000..16c28c1 --- /dev/null +++ b/src/db/schema/tournaments.ts @@ -0,0 +1,46 @@ +import { + sqliteTable, + text, + integer, + uniqueIndex, +} from "drizzle-orm/sqlite-core"; + +import { createId } from "@paralleldrive/cuid2"; +import { teams } from "./teams"; + +// Tournament table +export const tournaments = sqliteTable("tournament", { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + name: text("name").notNull(), +}); + +// TournamentTeam join table +export const tournamentTeams = sqliteTable( + "tournament_team", + { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + tournamentId: text("tournament_id") + .notNull() + .references(() => tournaments.id, { onDelete: "cascade" }), + teamId: text("team_id") + .notNull() + .references(() => teams.id, { onDelete: "cascade" }), + registrationDate: integer("registration_date", { mode: "timestamp" }) + .notNull() + .$defaultFn(() => new Date()), + }, + (table) => { + return { + tournamentTeamUnique: uniqueIndex("tournament_team_unique").on( + table.tournamentId, + table.teamId, + ), + }; + }, +); diff --git a/src/db/schema/types.ts b/src/db/schema/types.ts new file mode 100644 index 0000000..ee99a64 --- /dev/null +++ b/src/db/schema/types.ts @@ -0,0 +1,10 @@ +// Roles enum equivalent +export const RoleEnum = { + VISITOR: "VISITOR", + PARTICIPANT: "PARTICIPANT", + TEAMMATE: "TEAMMATE", + CAPTAIN: "CAPTAIN", + ADMIN: "ADMIN", +} as const; + +export type Role = (typeof RoleEnum)[keyof typeof RoleEnum]; diff --git a/src/db/schema/users.ts b/src/db/schema/users.ts new file mode 100644 index 0000000..9c752a3 --- /dev/null +++ b/src/db/schema/users.ts @@ -0,0 +1,18 @@ +import { sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { createId } from "@paralleldrive/cuid2"; + +// User table +export const users = sqliteTable("user", { + id: text("id") + .primaryKey() + .notNull() + .$defaultFn(() => createId()), + email: text("email").notNull(), + + // Other information + steamId: text("steam_id"), + firstName: text("first_name").notNull(), + lastName: text("last_name").notNull(), + ticketId: text("ticket_id").unique(), + ticketType: text("ticket_type"), +}); diff --git a/src/lib/fienta.ts b/src/lib/fienta.ts index 1997b5a..70ee537 100644 --- a/src/lib/fienta.ts +++ b/src/lib/fienta.ts @@ -1,7 +1,10 @@ import { db } from "@/db/drizzle"; -import { users, teams, members, tournamentTeams } from "@/db/schema"; +import { users } from "@/db/schema/users"; +import { teams } from "@/db/schema/teams"; +import { members } from "@/db/schema/members"; +import { tournamentTeams } from "@/db/schema/tournaments"; import { eq, and, isNull } from "drizzle-orm"; -import { RoleEnum, type Role } from "@/db/schema"; +import { RoleEnum, type Role } from "@/db/schema/types"; // Types based on the Fienta API response export interface FientaApiResponse { diff --git a/src/types/database.ts b/src/types/database.ts index 7ab8b82..c7e950b 100644 --- a/src/types/database.ts +++ b/src/types/database.ts @@ -1,5 +1,7 @@ import type { InferSelectModel } from "drizzle-orm"; -import { users, teams, members } from "@/db/schema"; +import { users } from "@/db/schema/users"; +import { teams } from "@/db/schema/teams"; +import { members } from "@/db/schema/members"; // Base types from schema export type User = InferSelectModel;