diff --git a/apps/api/src/app/build-app.js b/apps/api/src/app/build-app.js index ff5de63..11fb7ee 100644 --- a/apps/api/src/app/build-app.js +++ b/apps/api/src/app/build-app.js @@ -4,6 +4,7 @@ import multipart from '@fastify/multipart'; import { registerAuthRoutes } from '../modules/auth/routes/auth-routes.js'; import { registerAdminInventoryRoutes } from '../modules/inventory/routes/admin-inventory-routes.js'; +import { registerAdminLookupRoutes } from '../modules/inventory/routes/admin-lookup-routes.js'; import { registerAdminImageRoutes } from '../modules/inventory/routes/admin-image-routes.js'; import { registerPublicInventoryRoutes } from '../modules/inventory/routes/public-inventory-routes.js'; import { registerPublicImageRoutes } from '../modules/inventory/routes/public-image-routes.js'; @@ -48,6 +49,10 @@ export function buildApp(dependencies) { ...dependencies, prefix: '/api/v1/admin', }); + app.register(registerAdminLookupRoutes, { + ...dependencies, + prefix: '/api/v1/admin', + }); app.register(registerAdminImageRoutes, { ...dependencies, prefix: '/api/v1/admin', diff --git a/apps/api/src/app/server.js b/apps/api/src/app/server.js index 1469cff..19b1dd3 100644 --- a/apps/api/src/app/server.js +++ b/apps/api/src/app/server.js @@ -18,6 +18,7 @@ import { createPasswordResetRepository } from '../modules/auth/repositories/pass import { createRedisLoginRateLimiter } from '../modules/auth/services/login-rate-limiter.js'; import { createPasswordRecoveryService } from '../modules/auth/services/password-recovery-service.js'; import { createInventoryItemRepository } from '../modules/inventory/repositories/inventory-item-repository.js'; +import { createCatalogLookupRepository } from '../modules/inventory/repositories/catalog-lookup-repository.js'; import { createInventoryItemService } from '../modules/inventory/services/inventory-item-service.js'; import { createPublicInventoryService } from '../modules/inventory/services/public-inventory-service.js'; import { createPublicImageService } from '../modules/inventory/services/public-image-service.js'; @@ -51,6 +52,7 @@ async function startServer() { const redis = await createRedisConnection(); const productRepository = createProductRepository(database.db); const inventoryItemRepository = createInventoryItemRepository(database.db); + const catalogLookupRepository = createCatalogLookupRepository(database.db); const inventoryImageRepository = createInventoryImageRepository(database.db); const imageDerivativeJobRepository = createImageDerivativeJobRepository(database.db); const listingRepository = createListingRepository(database.db); @@ -102,6 +104,7 @@ async function startServer() { inventoryItemRepository, productRepository, }), + catalogLookupRepository, publicImageService, publicInventoryService: createPublicInventoryService({ inventoryItemRepository, publicImageService }), inventoryImageService: createInventoryImageService({ diff --git a/apps/api/src/modules/inventory/repositories/catalog-lookup-repository.js b/apps/api/src/modules/inventory/repositories/catalog-lookup-repository.js new file mode 100644 index 0000000..eb24381 --- /dev/null +++ b/apps/api/src/modules/inventory/repositories/catalog-lookup-repository.js @@ -0,0 +1,20 @@ +import { asc, eq } from 'drizzle-orm'; + +import { categories, conditionProfiles, conditions } from '../../../infrastructure/database/schema.js'; + +export function createCatalogLookupRepository(db) { + return { + async listCategories() { + return db.select({ id: categories.id, name: categories.name }).from(categories).where(eq(categories.active, true)).orderBy(asc(categories.sort), asc(categories.name)); + }, + + async listConditions() { + return db + .select({ id: conditions.id, name: conditions.name, code: conditions.code, categoryId: conditionProfiles.categoryId }) + .from(conditions) + .innerJoin(conditionProfiles, eq(conditionProfiles.id, conditions.conditionProfileId)) + .where(eq(conditions.active, true)) + .orderBy(asc(conditions.qualityRank), asc(conditions.name)); + }, + }; +} \ No newline at end of file diff --git a/apps/api/src/modules/inventory/routes/admin-lookup-routes.js b/apps/api/src/modules/inventory/routes/admin-lookup-routes.js new file mode 100644 index 0000000..faf52b5 --- /dev/null +++ b/apps/api/src/modules/inventory/routes/admin-lookup-routes.js @@ -0,0 +1,16 @@ +export async function registerAdminLookupRoutes(app, options) { + const { authenticateAdmin, catalogLookupRepository } = options; + + app.addHook('preHandler', async (request) => { + const admin = await authenticateAdmin(request); + if (!admin?.sellerId) { + const error = new Error('Authentication is required.'); + error.code = 'UNAUTHORIZED'; + error.statusCode = 401; + throw error; + } + }); + + app.get('/lookups/categories', async () => ({ data: catalogLookupRepository.listCategories() })); + app.get('/lookups/conditions', async () => ({ data: catalogLookupRepository.listConditions() })); +} \ No newline at end of file diff --git a/apps/web/src/pages/AdminHomePage.vue b/apps/web/src/pages/AdminHomePage.vue index e602985..783ee08 100644 --- a/apps/web/src/pages/AdminHomePage.vue +++ b/apps/web/src/pages/AdminHomePage.vue @@ -3,11 +3,13 @@ import { onMounted, ref } from 'vue'; import { RouterLink } from 'vue-router'; import { useAuth } from '@/composables/useAuth.js'; -import { createAdminInventory, createAdminProduct, listAdminInventory, listAdminProducts } from '@/services/admin-catalog-service.js'; +import { createAdminInventory, createAdminProduct, listAdminCategories, listAdminConditions, listAdminInventory, listAdminProducts } from '@/services/admin-catalog-service.js'; const { principal } = useAuth(); const products = ref([]); const inventory = ref([]); +const categories = ref([]); +const conditions = ref([]); const isLoading = ref(true); const isSaving = ref(false); const error = ref(null); @@ -23,7 +25,7 @@ function csrfToken() { async function load() { isLoading.value = true; try { - [products.value, inventory.value] = await Promise.all([listAdminProducts(), listAdminInventory()]); + [products.value, inventory.value, categories.value, conditions.value] = await Promise.all([listAdminProducts(), listAdminInventory(), listAdminCategories(), listAdminConditions()]); } catch (requestError) { error.value = requestError; } finally { isLoading.value = false; } } @@ -63,16 +65,25 @@ onMounted(load);