feat(platform-admin/backend-api): Implement new controllers/services for asset-storage, devices, merch, queues, shop, and SSO with E2E test coverage

This commit is contained in:
Lilith 2026-01-22 23:03:41 -08:00
parent 26e84630db
commit 181bf9e64c
15 changed files with 54 additions and 36 deletions

View file

@ -5,7 +5,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { AssetStorageModule } from './asset-storage/index';
import { AuthModule } from './auth/auth.module';
import { DeviceEntity } from './devices/device.entity';
import { DevicesModule } from './devices/devices.module';
import { HealthController } from './health/health.controller';
import { InfrastructureModule } from './infrastructure/infrastructure.module';

View file

@ -13,14 +13,15 @@ import {
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth, ApiQuery } from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { AssetStorageService } from './asset-storage.service';
import { GenerateAssetsDto } from './dto/index';
import type { Request } from 'express';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
@ApiTags('Asset Storage')
@ApiBearerAuth()
@UseGuards(AuthGuard, AdminGuard)

View file

@ -1,11 +1,12 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MinioModule } from '@/common/minio/index';
import { AssetStorageController } from './asset-storage.controller';
import { AssetStorageService } from './asset-storage.service';
import { MinioModule } from '@/common/minio/index';
@Module({
imports: [
ConfigModule,

View file

@ -2,8 +2,6 @@ import { ImajinClient } from '@lilith/imajin-client';
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { MinioService } from '@/common/minio/index';
import type {
ImageSize,
StoredAsset,
@ -12,6 +10,9 @@ import type {
GenerateAssetsResponse,
} from './types';
import { MinioService } from '@/common/minio/index';
/**
* Default sizes for asset generation
*/

View file

@ -17,15 +17,17 @@ import {
} from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser } from '@/auth/current-user.decorator';
import { DevicesService } from './devices.service';
import { DeviceResponseDto } from './dto/index';
import type { AuthUser } from '@/auth/types';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser } from '@/auth/current-user.decorator';
@Controller('api/devices')
@ApiTags('devices')
@ApiBearerAuth()

View file

@ -12,16 +12,17 @@ import {
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser, type AuthUser } from '@/auth/current-user.decorator';
import {
MerchService,
type UpdateStatusDto,
type ConvertToProductDto,
} from './merch.service';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser, type AuthUser } from '@/auth/current-user.decorator';
@Controller('api/merch/submissions')
@ApiTags('merch-submissions')
@UseGuards(AuthGuard, AdminGuard)

View file

@ -12,13 +12,15 @@ import {
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth, ApiQuery, ApiResponse } from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { QueuesService } from './queues.service';
import type { QueueSummary, JobInfo, ClearJobsDto, ClearJobsResult, JobState } from './types';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
@Controller('api/admin/queues')
@ApiTags('queues')
@ApiBearerAuth()

View file

@ -15,10 +15,6 @@ import {
import { ApiTags, ApiOperation, ApiBearerAuth, ApiQuery, ApiResponse } from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser } from '@/auth/current-user.decorator';
import { ShopService } from './shop.service';
import type {
@ -32,6 +28,12 @@ import type {
} from './types';
import type { AuthUser } from '@/auth/types';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { CurrentUser } from '@/auth/current-user.decorator';
@Controller('api/shop')
@ApiTags('shop')
@ApiBearerAuth()

View file

@ -21,14 +21,15 @@ import {
ApiParam,
} from '@nestjs/swagger';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
import { ListUsersDto, UpdateUserDto, ListSessionsDto } from './dto/index';
import { SSOAdminService } from './sso-admin.service';
import type { Request } from 'express';
import { AdminGuard } from '@/auth/admin.guard';
import { AuthGuard } from '@/auth/auth.guard';
/**
* SSO Administration Controller
* Platform-admin endpoints that proxy to SSO service admin API

View file

@ -1,11 +1,12 @@
import { HttpModule } from '@nestjs/axios';
import { Module } from '@nestjs/common';
import { AuthModule } from '@/auth/auth.module';
import { SSOAdminController } from './sso-admin.controller';
import { SSOAdminService } from './sso-admin.service';
import { AuthModule } from '@/auth/auth.module';
@Module({
imports: [

View file

@ -46,7 +46,7 @@ test.describe('Revenue Page (Real Data)', () => {
for (const type of transactionTypes) {
// At least one type should be visible or in a chart
const typeVisible = await page.getByText(type, { exact: true }).isVisible().catch(() => false)
if (typeVisible) break
if (typeVisible) {break}
}
})
})

View file

@ -16,7 +16,9 @@
* - Mobile responsiveness (375px viewport)
*/
import { test, expect, Page } from '@playwright/test';
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
// ============================================================================
// Test Fixtures and Helpers
@ -200,7 +202,7 @@ test.describe('Account Menu - Authenticated', () => {
// Verify AccountDropdown is present
const accountDropdown = page.locator('[data-testid="account-dropdown"]').or(
page.locator('[aria-label*="account"]').or(
page.locator('button:has-text("' + authState.user.username + '")')
page.locator(`button:has-text("${ authState.user.username }")`)
)
);
@ -222,7 +224,7 @@ test.describe('Account Menu - Authenticated', () => {
// Find and click dropdown trigger
const dropdownTrigger = page.locator('[data-testid="account-dropdown-trigger"]').or(
page.locator('button:has-text("' + authState.user.username + '")')
page.locator(`button:has-text("${ authState.user.username }")`)
);
await dropdownTrigger.click();
@ -244,7 +246,7 @@ test.describe('Account Menu - Authenticated', () => {
// Open dropdown
const dropdownTrigger = page.locator('[data-testid="account-dropdown-trigger"]').or(
page.locator('button:has-text("' + authState.user.username + '")')
page.locator(`button:has-text("${ authState.user.username }")`)
);
await dropdownTrigger.click();
await page.waitForTimeout(300);

View file

@ -17,13 +17,13 @@ export { DEVICES_MOCKS } from './devices.mocks';
// Import all mocks for combined export
import { ANALYTICS_MOCKS } from './analytics.mocks';
import { EMAIL_MOCKS } from './email.mocks';
import { MARKETPLACE_MOCKS } from './marketplace.mocks';
import { SHOP_MOCKS } from './shop.mocks';
import { SEO_MOCKS, I18N_MOCKS, TRUTH_MOCKS, IMAGE_GEN_MOCKS } from './seo-ml.mocks';
import { INFRASTRUCTURE_MOCKS } from './infrastructure.mocks';
import { ATTRIBUTES_MOCKS } from './content.mocks';
import { DEVICES_MOCKS } from './devices.mocks';
import { EMAIL_MOCKS } from './email.mocks';
import { INFRASTRUCTURE_MOCKS } from './infrastructure.mocks';
import { MARKETPLACE_MOCKS } from './marketplace.mocks';
import { SEO_MOCKS, I18N_MOCKS, TRUTH_MOCKS, IMAGE_GEN_MOCKS } from './seo-ml.mocks';
import { SHOP_MOCKS } from './shop.mocks';
/**
* Combined mocks object containing all mock responses.

View file

@ -4,9 +4,11 @@
* Handles Playwright route interception and matches URLs to mock responses.
*/
import { Page, Route } from '@playwright/test';
import { ALL_MOCKS } from './index';
import type { Page, Route } from '@playwright/test';
/**
* Find the best matching mock for a URL.
* Returns the mock response or null if no match found.

View file

@ -7,9 +7,12 @@
* This test FAILS if any route shows errors - it does not just report.
*/
import { test, expect, Page } from '@playwright/test';
import { test, expect } from '@playwright/test';
import { applyAllMocks } from './fixtures/api-mocks';
import type { Page } from '@playwright/test';
// ============================================================================
// Route Definitions
// ============================================================================