feat: Complete NestJS backend scaffold — 22 modules, 39 entities, WebSocket gateway
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
Full backend rewrite from Rust/Axum to NestJS/TypeScript. - 22 feature modules (auth, servers, wipes, maps, plugins, players, console, chat, team, notifications, settings, schedules, analytics, alerts, status, store, webstore, admin, setup, migration, users, licenses) - 39 TypeORM entities matching PostgreSQL schema (12 migrations) - Common infrastructure: JWT/RBAC guards, decorators, exception filter - NATS service with pub/sub/request-reply - Socket.IO WebSocket gateway with NATS bridge - Docker: NestJS Dockerfile + updated docker-compose.yml - Zero compile errors (npx tsc --noEmit clean) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
43
backend-nest/src/entities/alert-config.entity.ts
Normal file
43
backend-nest/src/entities/alert-config.entity.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Index } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('alert_config')
|
||||
@Index(['license_id'], { unique: true })
|
||||
export class AlertConfig {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
population_drop_enabled: boolean;
|
||||
|
||||
@Column({ type: 'integer', default: 30 })
|
||||
population_drop_threshold_percent: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
fps_degradation_enabled: boolean;
|
||||
|
||||
@Column({ type: 'integer', default: 30 })
|
||||
fps_threshold: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
notify_discord: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
notify_pushbullet: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
notify_email: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
42
backend-nest/src/entities/alert-history.entity.ts
Normal file
42
backend-nest/src/entities/alert-history.entity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('alert_history')
|
||||
export class AlertHistory {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
alert_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
severity: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
title: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
message: string;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
metadata: Record<string, any> | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
notified_discord: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
notified_pushbullet: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
notified_email: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
triggered_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
45
backend-nest/src/entities/chat-log.entity.ts
Normal file
45
backend-nest/src/entities/chat-log.entity.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('chat_logs')
|
||||
@Check(`"channel" IN ('global', 'team', 'server')`)
|
||||
export class ChatLog {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
steam_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
player_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'global' })
|
||||
channel: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
message: string;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
flagged: boolean;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
flagged_by: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
flag_reason: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => User, { nullable: true })
|
||||
@JoinColumn({ name: 'flagged_by' })
|
||||
flagger: User | null;
|
||||
}
|
||||
16
backend-nest/src/entities/early-access-signup.entity.ts
Normal file
16
backend-nest/src/entities/early-access-signup.entity.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('early_access_signups')
|
||||
export class EarlyAccessSignup {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
email: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 10 })
|
||||
server_count: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
}
|
||||
40
backend-nest/src/entities/game-admin.entity.ts
Normal file
40
backend-nest/src/entities/game-admin.entity.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('game_admins')
|
||||
@Unique(['license_id', 'steam_id'])
|
||||
@Check(`"admin_level" IN ('owner', 'admin', 'moderator')`)
|
||||
export class GameAdmin {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
steam_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, default: '' })
|
||||
display_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'admin' })
|
||||
admin_level: string;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
permissions: Record<string, any>;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
added_by: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'added_by' })
|
||||
adder: User;
|
||||
}
|
||||
31
backend-nest/src/entities/host-billing-record.entity.ts
Normal file
31
backend-nest/src/entities/host-billing-record.entity.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { Host } from './host.entity';
|
||||
|
||||
@Entity('host_billing_records')
|
||||
@Unique(['host_id', 'billing_month'])
|
||||
export class HostBillingRecord {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
host_id: string;
|
||||
|
||||
@Column({ type: 'date' })
|
||||
billing_month: Date;
|
||||
|
||||
@Column({ type: 'integer' })
|
||||
active_license_count: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
wholesale_rate_usd: number;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
total_amount_usd: number;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
generated_at: Date;
|
||||
|
||||
@ManyToOne(() => Host, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'host_id' })
|
||||
host: Host;
|
||||
}
|
||||
36
backend-nest/src/entities/host-license.entity.ts
Normal file
36
backend-nest/src/entities/host-license.entity.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { Host } from './host.entity';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('host_licenses')
|
||||
@Unique(['host_id', 'license_id'])
|
||||
export class HostLicense {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
host_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
server_identifier: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
customer_email: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
provisioned_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
last_seen_at: Date | null;
|
||||
|
||||
@ManyToOne(() => Host, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'host_id' })
|
||||
host: Host;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
28
backend-nest/src/entities/host.entity.ts
Normal file
28
backend-nest/src/entities/host.entity.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('hosts')
|
||||
export class Host {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
company_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
contact_email: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64, unique: true })
|
||||
api_key: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, default: 6.00 })
|
||||
wholesale_rate_usd: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
active: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
}
|
||||
38
backend-nest/src/entities/index.ts
Normal file
38
backend-nest/src/entities/index.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
export { User } from './user.entity';
|
||||
export { License } from './license.entity';
|
||||
export { Role } from './role.entity';
|
||||
export { TeamMember } from './team-member.entity';
|
||||
export { ServerConnection } from './server-connection.entity';
|
||||
export { ServerConfig } from './server-config.entity';
|
||||
export { GameAdmin } from './game-admin.entity';
|
||||
export { WipeProfile } from './wipe-profile.entity';
|
||||
export { WipeSchedule } from './wipe-schedule.entity';
|
||||
export { WipeHistory } from './wipe-history.entity';
|
||||
export { MapLibrary } from './map-library.entity';
|
||||
export { MapRotation } from './map-rotation.entity';
|
||||
export { PluginRegistry } from './plugin-registry.entity';
|
||||
export { ScheduledTask } from './scheduled-task.entity';
|
||||
export { NotificationsConfig } from './notifications-config.entity';
|
||||
export { ChatLog } from './chat-log.entity';
|
||||
export { PlayerAction } from './player-action.entity';
|
||||
export { ServerStats } from './server-stats.entity';
|
||||
export { ServerStatsHourly } from './server-stats-hourly.entity';
|
||||
export { PublicSiteConfig } from './public-site-config.entity';
|
||||
export { PlatformChangelog } from './platform-changelog.entity';
|
||||
export { MigrationExport } from './migration-export.entity';
|
||||
export { EarlyAccessSignup } from './early-access-signup.entity';
|
||||
export { PlayerSession } from './player-session.entity';
|
||||
export { AlertConfig } from './alert-config.entity';
|
||||
export { AlertHistory } from './alert-history.entity';
|
||||
export { Module } from './module.entity';
|
||||
export { ModulePurchase } from './module-purchase.entity';
|
||||
export { ModuleInstallation } from './module-installation.entity';
|
||||
export { PaymentOrder } from './payment-order.entity';
|
||||
export { WebstoreSubscription } from './webstore-subscription.entity';
|
||||
export { StoreConfig } from './store-config.entity';
|
||||
export { StoreCategory } from './store-category.entity';
|
||||
export { StoreItem } from './store-item.entity';
|
||||
export { StoreTransaction } from './store-transaction.entity';
|
||||
export { Host } from './host.entity';
|
||||
export { HostLicense } from './host-license.entity';
|
||||
export { HostBillingRecord } from './host-billing-record.entity';
|
||||
46
backend-nest/src/entities/license.entity.ts
Normal file
46
backend-nest/src/entities/license.entity.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('licenses')
|
||||
@Check(`"status" IN ('active', 'suspended', 'expired', 'revoked')`)
|
||||
export class License {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 64, unique: true })
|
||||
license_key: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'active' })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
owner_user_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
server_name: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 63, unique: true, nullable: true })
|
||||
subdomain: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true, nullable: true })
|
||||
custom_domain: string | null;
|
||||
|
||||
@Column('text', { array: true, default: '{}' })
|
||||
modules_enabled: string[];
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
webstore_active: boolean;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
webstore_subscription_id: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
expires_at: Date | null;
|
||||
|
||||
@ManyToOne(() => User, user => user.licenses, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'owner_user_id' })
|
||||
owner: User;
|
||||
}
|
||||
46
backend-nest/src/entities/map-library.entity.ts
Normal file
46
backend-nest/src/entities/map-library.entity.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('map_library')
|
||||
@Check(`"map_type" IN ('custom', 'procedural')`)
|
||||
export class MapLibrary {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
filename: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
display_name: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
storage_path: string;
|
||||
|
||||
@Column({ type: 'bigint', default: 0 })
|
||||
file_size_bytes: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'custom' })
|
||||
map_type: string;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
seed: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
world_size: number | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
thumbnail_path: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 64 })
|
||||
checksum: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
uploaded_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
30
backend-nest/src/entities/map-rotation.entity.ts
Normal file
30
backend-nest/src/entities/map-rotation.entity.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { MapLibrary } from './map-library.entity';
|
||||
|
||||
@Entity('map_rotations')
|
||||
@Unique(['license_id', 'rotation_order'])
|
||||
export class MapRotation {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
map_id: string;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
rotation_order: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
is_active: boolean;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => MapLibrary, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'map_id' })
|
||||
map: MapLibrary;
|
||||
}
|
||||
39
backend-nest/src/entities/migration-export.entity.ts
Normal file
39
backend-nest/src/entities/migration-export.entity.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('migration_exports')
|
||||
@Check(`"export_type" IN ('full', 'config_only', 'store_only')`)
|
||||
export class MigrationExport {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'full' })
|
||||
export_type: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
storage_path: string;
|
||||
|
||||
@Column({ type: 'bigint', default: 0 })
|
||||
file_size_bytes: number;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
created_by: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
expires_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'created_by' })
|
||||
creator: User;
|
||||
}
|
||||
34
backend-nest/src/entities/module-installation.entity.ts
Normal file
34
backend-nest/src/entities/module-installation.entity.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { Module } from './module.entity';
|
||||
|
||||
@Entity('module_installations')
|
||||
@Unique(['license_id', 'module_id'])
|
||||
@Check(`"status" IN ('pending', 'installing', 'installed', 'failed')`)
|
||||
export class ModuleInstallation {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
module_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, default: 'pending' })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
installed_at: Date | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
error_message: string | null;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => Module, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'module_id' })
|
||||
module: Module;
|
||||
}
|
||||
33
backend-nest/src/entities/module-purchase.entity.ts
Normal file
33
backend-nest/src/entities/module-purchase.entity.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { Module } from './module.entity';
|
||||
|
||||
@Entity('module_purchases')
|
||||
@Unique(['license_id', 'module_id'])
|
||||
export class ModulePurchase {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
module_id: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
purchased_at: Date;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
transaction_id: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2, nullable: true })
|
||||
amount_paid: number | null;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => Module, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'module_id' })
|
||||
module: Module;
|
||||
}
|
||||
40
backend-nest/src/entities/module.entity.ts
Normal file
40
backend-nest/src/entities/module.entity.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
|
||||
|
||||
@Entity('modules')
|
||||
export class Module {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, unique: true })
|
||||
slug: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
category: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price_usd: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
preview_image_url: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
screenshots: any | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
features: any | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
version: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
plugin_file_url: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
}
|
||||
58
backend-nest/src/entities/payment-order.entity.ts
Normal file
58
backend-nest/src/entities/payment-order.entity.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { Module } from './module.entity';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('payment_orders')
|
||||
@Check(`"status" IN ('pending', 'completed', 'failed', 'refunded')`)
|
||||
export class PaymentOrder {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
order_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
module_id: string | null;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
webstore_subscription_id: string | null;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||
currency: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
transaction_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
payer_email: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
completed_at: Date | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
metadata: Record<string, any> | null;
|
||||
|
||||
@ManyToOne(() => Module, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'module_id' })
|
||||
module: Module | null;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'webstore_subscription_id' })
|
||||
webstore_subscription: License | null;
|
||||
}
|
||||
23
backend-nest/src/entities/platform-changelog.entity.ts
Normal file
23
backend-nest/src/entities/platform-changelog.entity.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, Check } from 'typeorm';
|
||||
|
||||
@Entity('platform_changelog')
|
||||
@Check(`"category" IN ('feature', 'bugfix', 'module', 'security')`)
|
||||
export class PlatformChangelog {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
version: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
title: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
body: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'feature' })
|
||||
category: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
published_at: Date;
|
||||
}
|
||||
42
backend-nest/src/entities/player-action.entity.ts
Normal file
42
backend-nest/src/entities/player-action.entity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { User } from './user.entity';
|
||||
|
||||
@Entity('player_actions')
|
||||
@Check(`"action_type" IN ('kick', 'ban', 'unban', 'warn', 'note')`)
|
||||
export class PlayerAction {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
steam_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
player_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
action_type: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
reason: string | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
duration_minutes: number | null;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
performed_by: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@JoinColumn({ name: 'performed_by' })
|
||||
performer: User;
|
||||
}
|
||||
33
backend-nest/src/entities/player-session.entity.ts
Normal file
33
backend-nest/src/entities/player-session.entity.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('player_sessions')
|
||||
export class PlayerSession {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
steam_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
player_name: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
session_start: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
session_end: Date | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
duration_seconds: number | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
59
backend-nest/src/entities/plugin-registry.entity.ts
Normal file
59
backend-nest/src/entities/plugin-registry.entity.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('plugin_registry')
|
||||
@Unique(['license_id', 'plugin_name'])
|
||||
@Check(`"source" IN ('umod', 'corrosion_module', 'manual')`)
|
||||
export class PluginRegistry {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255 })
|
||||
plugin_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
plugin_version: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'manual' })
|
||||
source: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
umod_slug: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_installed: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_loaded: boolean;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
config_json: Record<string, any> | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
data_path: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
wipe_on_map: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
wipe_on_bp: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
wipe_on_full: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
never_wipe: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
installed_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
60
backend-nest/src/entities/public-site-config.entity.ts
Normal file
60
backend-nest/src/entities/public-site-config.entity.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('public_site_config')
|
||||
export class PublicSiteConfig {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid', unique: true })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
site_enabled: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
show_on_status_page: boolean;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
steam_connect_url: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
motd: string | null;
|
||||
|
||||
@Column('text', { array: true, default: '{}' })
|
||||
public_mods: string[];
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
header_image_url: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 7, default: '#ef4444' })
|
||||
theme_color: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
custom_css: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
discord_invite_url: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
show_player_count: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
show_wipe_schedule: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
show_wipe_countdown: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
show_mod_list: boolean;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
status_page_description: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
80
backend-nest/src/entities/server-config.entity.ts
Normal file
80
backend-nest/src/entities/server-config.entity.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { MapLibrary } from './map-library.entity';
|
||||
|
||||
@Entity('server_config')
|
||||
export class ServerConfig {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid', unique: true })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, default: '' })
|
||||
server_name: string;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
max_players: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
world_size: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
current_seed: number | null;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
current_map_id: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
server_description: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
server_url: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
server_header_image: string | null;
|
||||
|
||||
@Column('text', { array: true, default: '{}' })
|
||||
tags: string[];
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
auto_restart_enabled: boolean;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
auto_restart_cron: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, nullable: true })
|
||||
auto_restart_timezone: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
crash_recovery_enabled: boolean;
|
||||
|
||||
@Column({ type: 'integer', default: 3 })
|
||||
crash_recovery_max_attempts: number;
|
||||
|
||||
@Column({ type: 'integer', default: 10 })
|
||||
crash_recovery_cooldown_minutes: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
force_wipe_eligible: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
auto_update_on_force_wipe: boolean;
|
||||
|
||||
@Column({ type: 'jsonb', default: {} })
|
||||
config_overrides: Record<string, any>;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => MapLibrary, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'current_map_id' })
|
||||
current_map: MapLibrary | null;
|
||||
}
|
||||
56
backend-nest/src/entities/server-connection.entity.ts
Normal file
56
backend-nest/src/entities/server-connection.entity.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('server_connections')
|
||||
@Check(`"connection_type" IN ('amp', 'pterodactyl', 'bare_metal')`)
|
||||
@Check(`"connection_status" IN ('connected', 'degraded', 'offline')`)
|
||||
export class ServerConnection {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid', unique: true })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
connection_type: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
panel_api_endpoint: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
panel_api_key_encrypted: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
panel_server_identifier: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 128, nullable: true })
|
||||
companion_agent_token: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
companion_last_seen: Date | null;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
plugin_last_seen: Date | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 45, nullable: true })
|
||||
server_ip: string | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
server_port: number | null;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
game_port: number | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'offline' })
|
||||
connection_status: string;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
37
backend-nest/src/entities/server-stats-hourly.entity.ts
Normal file
37
backend-nest/src/entities/server-stats-hourly.entity.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('server_stats_hourly')
|
||||
@Unique(['license_id', 'hour'])
|
||||
export class ServerStatsHourly {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
hour: Date;
|
||||
|
||||
@Column({ type: 'double precision', default: 0 })
|
||||
avg_players: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
max_players: number;
|
||||
|
||||
@Column({ type: 'double precision', default: 0 })
|
||||
avg_fps: number;
|
||||
|
||||
@Column({ type: 'double precision', default: 0 })
|
||||
min_fps: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
avg_entities: number;
|
||||
|
||||
@Column({ type: 'double precision', default: 0 })
|
||||
uptime_percentage: number;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
44
backend-nest/src/entities/server-stats.entity.ts
Normal file
44
backend-nest/src/entities/server-stats.entity.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { MapLibrary } from './map-library.entity';
|
||||
|
||||
@Entity('server_stats')
|
||||
export class ServerStats {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
player_count: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
max_players: number;
|
||||
|
||||
@Column({ type: 'double precision', default: 0 })
|
||||
fps: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
entity_count: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
uptime_seconds: number;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
memory_usage_mb: number;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
map_id: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
recorded_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => MapLibrary, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'map_id' })
|
||||
map: MapLibrary | null;
|
||||
}
|
||||
34
backend-nest/src/entities/store-category.entity.ts
Normal file
34
backend-nest/src/entities/store-category.entity.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Unique } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('store_categories')
|
||||
@Unique(['license_id', 'slug'])
|
||||
export class StoreCategory {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
slug: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'integer', default: 0 })
|
||||
display_order: number;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
visible: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
42
backend-nest/src/entities/store-config.entity.ts
Normal file
42
backend-nest/src/entities/store-config.entity.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('store_config')
|
||||
export class StoreConfig {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid', unique: true })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
store_name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||
currency: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
paypal_client_id: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
paypal_client_secret: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
sandbox_mode: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
enabled: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
54
backend-nest/src/entities/store-item.entity.ts
Normal file
54
backend-nest/src/entities/store-item.entity.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { StoreCategory } from './store-category.entity';
|
||||
|
||||
@Entity('store_items')
|
||||
@Check(`"item_type" IN ('kit', 'rank', 'currency', 'command')`)
|
||||
export class StoreItem {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
category_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 200 })
|
||||
name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
price: number;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
image_url: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
item_type: string;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
delivery_commands: Record<string, any>;
|
||||
|
||||
@Column({ type: 'integer', nullable: true })
|
||||
limit_per_player: number | null;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
enabled: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => StoreCategory, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'category_id' })
|
||||
category: StoreCategory | null;
|
||||
}
|
||||
57
backend-nest/src/entities/store-transaction.entity.ts
Normal file
57
backend-nest/src/entities/store-transaction.entity.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { StoreItem } from './store-item.entity';
|
||||
|
||||
@Entity('store_transactions')
|
||||
@Check(`"status" IN ('pending', 'paid', 'delivered', 'failed', 'refunded')`)
|
||||
export class StoreTransaction {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
item_id: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
steam_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100, nullable: true })
|
||||
player_name: string | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
paypal_order_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
paypal_transaction_id: string | null;
|
||||
|
||||
@Column({ type: 'decimal', precision: 10, scale: 2 })
|
||||
amount: number;
|
||||
|
||||
@Column({ type: 'varchar', length: 3, default: 'USD' })
|
||||
currency: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
delivered: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
delivered_at: Date | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
payer_email: string | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => StoreItem, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'item_id' })
|
||||
item: StoreItem | null;
|
||||
}
|
||||
45
backend-nest/src/entities/user.entity.ts
Normal file
45
backend-nest/src/entities/user.entity.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, OneToMany, CreateDateColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { TeamMember } from './team-member.entity';
|
||||
|
||||
@Entity('users')
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
email: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, unique: true })
|
||||
username: string;
|
||||
|
||||
@Column({ type: 'text' })
|
||||
password_hash: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
totp_secret: string | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
totp_enabled: boolean;
|
||||
|
||||
@Column('text', { array: true, nullable: true })
|
||||
backup_codes: string[] | null;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
email_verified: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
is_super_admin: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
last_login_at: Date | null;
|
||||
|
||||
@OneToMany(() => License, license => license.owner)
|
||||
licenses: License[];
|
||||
|
||||
@OneToMany(() => TeamMember, teamMember => teamMember.user)
|
||||
team_memberships: TeamMember[];
|
||||
}
|
||||
37
backend-nest/src/entities/webstore-subscription.entity.ts
Normal file
37
backend-nest/src/entities/webstore-subscription.entity.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('webstore_subscriptions')
|
||||
@Check(`"status" IN ('active', 'cancelled', 'suspended', 'past_due')`)
|
||||
export class WebstoreSubscription {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid', unique: true })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, unique: true })
|
||||
paypal_subscription_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
plan_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50 })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
current_period_start: Date;
|
||||
|
||||
@Column({ type: 'timestamptz' })
|
||||
current_period_end: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
cancelled_at: Date | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
78
backend-nest/src/entities/wipe-history.entity.ts
Normal file
78
backend-nest/src/entities/wipe-history.entity.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { WipeSchedule } from './wipe-schedule.entity';
|
||||
import { WipeProfile } from './wipe-profile.entity';
|
||||
import { MapLibrary } from './map-library.entity';
|
||||
|
||||
@Entity('wipe_history')
|
||||
@Check(`"wipe_type" IN ('map', 'blueprint', 'full')`)
|
||||
@Check(`"trigger_type" IN ('scheduled', 'manual', 'force_wipe')`)
|
||||
@Check(`"status" IN ('pending', 'pre_wipe', 'wiping', 'post_wipe', 'success', 'failed', 'rolled_back')`)
|
||||
export class WipeHistory {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
wipe_schedule_id: string | null;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
wipe_profile_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
wipe_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
trigger_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20, default: 'pending' })
|
||||
status: string;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
started_at: Date | null;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
completed_at: Date | null;
|
||||
|
||||
@Column({ type: 'varchar', length: 255, nullable: true })
|
||||
map_used_legacy: string | null;
|
||||
|
||||
@Column({ type: 'uuid', nullable: true })
|
||||
map_id: string | null;
|
||||
|
||||
@Column('text', { array: true, nullable: true })
|
||||
plugins_wiped: string[] | null;
|
||||
|
||||
@Column('text', { array: true, nullable: true })
|
||||
plugins_preserved: string[] | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
backup_reference: string | null;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
error_message: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', default: '[]' })
|
||||
execution_log: any[];
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => WipeSchedule, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'wipe_schedule_id' })
|
||||
wipe_schedule: WipeSchedule | null;
|
||||
|
||||
@ManyToOne(() => WipeProfile)
|
||||
@JoinColumn({ name: 'wipe_profile_id' })
|
||||
wipe_profile: WipeProfile;
|
||||
|
||||
@ManyToOne(() => MapLibrary, { onDelete: 'SET NULL', nullable: true })
|
||||
@JoinColumn({ name: 'map_id' })
|
||||
map: MapLibrary | null;
|
||||
}
|
||||
33
backend-nest/src/entities/wipe-profile.entity.ts
Normal file
33
backend-nest/src/entities/wipe-profile.entity.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
|
||||
@Entity('wipe_profiles')
|
||||
export class WipeProfile {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
profile_name: string;
|
||||
|
||||
@Column({ type: 'text', nullable: true })
|
||||
description: string | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
pre_wipe_config: Record<string, any> | null;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
post_wipe_config: Record<string, any> | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
updated_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
}
|
||||
48
backend-nest/src/entities/wipe-schedule.entity.ts
Normal file
48
backend-nest/src/entities/wipe-schedule.entity.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Check } from 'typeorm';
|
||||
import { License } from './license.entity';
|
||||
import { WipeProfile } from './wipe-profile.entity';
|
||||
|
||||
@Entity('wipe_schedules')
|
||||
@Check(`"wipe_type" IN ('map', 'blueprint', 'full')`)
|
||||
export class WipeSchedule {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
license_id: string;
|
||||
|
||||
@Column({ type: 'uuid' })
|
||||
wipe_profile_id: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
schedule_name: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 20 })
|
||||
wipe_type: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 100 })
|
||||
cron_expression: string;
|
||||
|
||||
@Column({ type: 'varchar', length: 50, default: 'America/New_York' })
|
||||
timezone: string;
|
||||
|
||||
@Column({ type: 'boolean', default: false })
|
||||
wipe_blueprints: boolean;
|
||||
|
||||
@Column({ type: 'boolean', default: true })
|
||||
is_active: boolean;
|
||||
|
||||
@Column({ type: 'timestamptz', nullable: true })
|
||||
next_scheduled_run: Date | null;
|
||||
|
||||
@Column({ type: 'timestamptz', default: () => 'NOW()' })
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => License, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'license_id' })
|
||||
license: License;
|
||||
|
||||
@ManyToOne(() => WipeProfile, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'wipe_profile_id' })
|
||||
wipe_profile: WipeProfile;
|
||||
}
|
||||
Reference in New Issue
Block a user