feat: Wave 2 — entities, security guards, API key encryption (15 files)
All checks were successful
Test Asgard Runner / test (push) Successful in 2s

Entities:
- Create 5 new TypeORM entities: webstore_config, webstore_categories,
  webstore_items, webstore_transactions, module_store (all verified against live DB)
- Fix wipe-profile entity: remove incorrect default {} for pre/post wipe configs

Security:
- Add @RequirePermission guards to 7 controllers (36 endpoints total):
  team, webstore, notifications, alerts, analytics, settings, schedules
- Encrypt panel API key with AES-256-GCM in setup service (was plaintext)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-21 13:28:48 -05:00
parent 208622000c
commit e1a3ea3b78
15 changed files with 268 additions and 5 deletions

View File

@@ -0,0 +1,52 @@
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';
@Entity('module_store')
export class ModuleStore {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', unique: true })
module_slug: string;
@Column({ type: 'varchar' })
module_name: string;
@Column({ type: 'text', nullable: true })
description: string | null;
@Column({ type: 'text', nullable: true })
long_description: string | null;
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
price: number;
@Column({ type: 'varchar', default: 'one_time' })
price_type: string;
@Column({ type: 'decimal', precision: 10, scale: 2, nullable: true })
monthly_price: number | null;
@Column({ type: 'varchar', default: '1.0.0' })
version: string;
@Column({ type: 'text', nullable: true })
download_path: string | null;
@Column({ type: 'text', nullable: true })
thumbnail_url: string | null;
@Column({ type: 'text', array: true, nullable: true, default: () => "'{}'" })
screenshots: string[] | null;
@Column({ type: 'varchar', nullable: true })
category: string | null;
@Column({ type: 'boolean', default: true })
is_active: boolean;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
created_at: Date;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
updated_at: Date;
}

View File

@@ -0,0 +1,24 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { License } from './license.entity';
@Entity('webstore_categories')
export class WebstoreCategory {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
license_id: string;
@Column({ type: 'varchar' })
category_name: string;
@Column({ type: 'integer', default: 0 })
display_order: number;
@Column({ type: 'boolean', default: true })
is_active: boolean;
@ManyToOne(() => License, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'license_id' })
license: License;
}

View File

@@ -0,0 +1,39 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { License } from './license.entity';
@Entity('webstore_config')
export class WebstoreConfig {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
license_id: string;
@Column({ type: 'boolean', default: false })
is_active: boolean;
@Column({ type: 'text', nullable: true })
paypal_client_id: string | null;
@Column({ type: 'text', nullable: true })
paypal_secret: string | null;
@Column({ type: 'varchar', default: 'sandbox' })
paypal_mode: string;
@Column({ type: 'varchar', nullable: true })
store_name: string | null;
@Column({ type: 'text', nullable: true })
store_description: string | null;
@Column({ type: 'varchar', default: 'USD' })
currency: string;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
created_at: Date;
@ManyToOne(() => License, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'license_id' })
license: License;
}

View File

@@ -0,0 +1,47 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { License } from './license.entity';
import { WebstoreCategory } from './webstore-category.entity';
@Entity('webstore_items')
export class WebstoreItem {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
license_id: string;
@Column({ type: 'uuid' })
category_id: string;
@Column({ type: 'varchar' })
item_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', default: 'kit' })
item_type: string;
@Column({ type: 'jsonb' })
delivery_config: Record<string, any>;
@Column({ type: 'boolean', default: true })
is_active: boolean;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
created_at: Date;
@ManyToOne(() => License, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'license_id' })
license: License;
@ManyToOne(() => WebstoreCategory, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'category_id' })
category: WebstoreCategory;
}

View File

@@ -0,0 +1,47 @@
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn } from 'typeorm';
import { License } from './license.entity';
import { WebstoreItem } from './webstore-item.entity';
@Entity('webstore_transactions')
export class WebstoreTransaction {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid' })
license_id: string;
@Column({ type: 'uuid' })
item_id: string;
@Column({ type: 'varchar' })
buyer_steam_id: string;
@Column({ type: 'varchar', nullable: true })
buyer_name: string | null;
@Column({ type: 'decimal', precision: 10, scale: 2 })
amount: number;
@Column({ type: 'varchar', default: 'USD' })
currency: string;
@Column({ type: 'varchar', nullable: true })
paypal_transaction_id: string | null;
@Column({ type: 'varchar', default: 'pending' })
status: string;
@Column({ type: 'timestamptz', nullable: true })
delivered_at: Date | null;
@Column({ type: 'timestamptz', default: () => 'NOW()' })
created_at: Date;
@ManyToOne(() => License, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'license_id' })
license: License;
@ManyToOne(() => WebstoreItem, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'item_id' })
item: WebstoreItem;
}

View File

@@ -15,10 +15,10 @@ export class WipeProfile {
@Column({ type: 'text', nullable: true })
description: string | null;
@Column({ type: 'jsonb', default: {} })
@Column({ type: 'jsonb' })
pre_wipe_config: Record<string, any>;
@Column({ type: 'jsonb', default: {} })
@Column({ type: 'jsonb' })
post_wipe_config: Record<string, any>;
@Column({ type: 'timestamptz', default: () => 'NOW()' })