feat: establish auth listing and image vertical slices

Includes MFA-capable authentication, session and CSRF security, password recovery, listing lifecycle and ownership rules, inventory image metadata, local ImageStorage, migrations, tests, and implementation status documentation. Verification: npm test (62 passing) and integration suite (14 passing).
This commit is contained in:
2026-09-08 17:32:03 -04:00
commit c728016dec
128 changed files with 9788 additions and 0 deletions
@@ -0,0 +1,285 @@
import {
boolean,
check,
index,
integer,
jsonb,
pgEnum,
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
} from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
export const userRole = pgEnum('user_role', ['ADMIN']);
export const inventoryStatus = pgEnum('inventory_status', [
'ACTIVE',
'SOLD',
'ARCHIVED',
]);
export const listingStatus = pgEnum('listing_status', [
'DRAFT',
'AVAILABLE',
'PENDING',
'CLOSED',
'ARCHIVED',
]);
const timestamps = {
createdAt: timestamp('created_at', { withTimezone: true })
.defaultNow()
.notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true })
.defaultNow()
.notNull(),
};
export const users = pgTable(
'users',
{
id: uuid('id').defaultRandom().primaryKey(),
email: text('email').notNull(),
passwordHash: text('password_hash').notNull(),
role: userRole('role').default('ADMIN').notNull(),
...timestamps,
},
(table) => [uniqueIndex('users_email_unique').on(table.email)],
);
export const sellers = pgTable(
'sellers',
{
id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
displayName: text('display_name').notNull(),
...timestamps,
},
(table) => [uniqueIndex('sellers_user_id_unique').on(table.userId)],
);
export const sessions = pgTable(
'sessions',
{
id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
sessionTokenHash: text('session_token_hash').notNull(),
csrfTokenHash: text('csrf_token_hash').notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
lastActivityAt: timestamp('last_activity_at', { withTimezone: true })
.defaultNow()
.notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
revokedAt: timestamp('revoked_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true })
.defaultNow()
.notNull(),
},
(table) => [
uniqueIndex('sessions_token_hash_unique').on(table.sessionTokenHash),
index('sessions_user_id_index').on(table.userId),
index('sessions_expires_at_index').on(table.expiresAt),
],
);
export const securityAuditEvents = pgTable(
'security_audit_events',
{
id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id').references(() => users.id),
event: text('event').notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
metadata: jsonb('metadata').default({}).notNull(),
createdAt: timestamp('created_at', { withTimezone: true })
.defaultNow()
.notNull(),
},
(table) => [
index('security_audit_events_user_id_index').on(table.userId),
index('security_audit_events_event_created_at_index').on(
table.event,
table.createdAt,
),
],
);
export const passwordResetTokens = pgTable(
'password_reset_tokens',
{
id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
tokenHash: text('token_hash').notNull(),
expiresAt: timestamp('expires_at', { withTimezone: true }).notNull(),
usedAt: timestamp('used_at', { withTimezone: true }),
createdAt: timestamp('created_at', { withTimezone: true })
.defaultNow()
.notNull(),
},
(table) => [
uniqueIndex('password_reset_tokens_token_hash_unique').on(table.tokenHash),
index('password_reset_tokens_user_id_index').on(table.userId),
],
);
export const mfaMethods = pgTable(
'mfa_methods',
{
id: uuid('id').defaultRandom().primaryKey(),
userId: uuid('user_id')
.notNull()
.references(() => users.id),
type: text('type').notNull(),
secretCiphertext: text('secret_ciphertext').notNull(),
name: text('name').notNull(),
isEnabled: boolean('is_enabled').default(false).notNull(),
createdAt: timestamp('created_at', { withTimezone: true })
.defaultNow()
.notNull(),
lastUsedAt: timestamp('last_used_at', { withTimezone: true }),
disabledAt: timestamp('disabled_at', { withTimezone: true }),
},
(table) => [index('mfa_methods_user_id_index').on(table.userId)],
);
export const categories = pgTable(
'categories',
{
id: uuid('id').defaultRandom().primaryKey(),
name: text('name').notNull(),
slug: text('slug').notNull(),
active: boolean('active').default(true).notNull(),
sort: integer('sort').default(0).notNull(),
...timestamps,
},
(table) => [
uniqueIndex('categories_name_unique').on(table.name),
uniqueIndex('categories_slug_unique').on(table.slug),
],
);
export const conditionProfiles = pgTable('condition_profiles', {
id: uuid('id').defaultRandom().primaryKey(),
categoryId: uuid('category_id')
.notNull()
.references(() => categories.id),
name: text('name').notNull(),
active: boolean('active').default(true).notNull(),
sort: integer('sort').default(0).notNull(),
...timestamps,
});
export const conditions = pgTable(
'conditions',
{
id: uuid('id').defaultRandom().primaryKey(),
conditionProfileId: uuid('condition_profile_id')
.notNull()
.references(() => conditionProfiles.id),
name: text('name').notNull(),
code: text('code').notNull(),
qualityRank: integer('quality_rank').notNull(),
description: text('description'),
active: boolean('active').default(true).notNull(),
sort: integer('sort').default(0).notNull(),
...timestamps,
},
(table) => [
uniqueIndex('conditions_profile_code_unique').on(
table.conditionProfileId,
table.code,
),
],
);
export const products = pgTable(
'products',
{
id: uuid('id').defaultRandom().primaryKey(),
sellerId: uuid('seller_id')
.notNull()
.references(() => sellers.id),
categoryId: uuid('category_id')
.notNull()
.references(() => categories.id),
name: text('name').notNull(),
slug: text('slug').notNull(),
description: text('description'),
brand: text('brand'),
setName: text('set_name'),
cardNumber: text('card_number'),
eventName: text('event_name'),
releaseYear: integer('release_year'),
...timestamps,
},
(table) => [
uniqueIndex('products_seller_slug_unique').on(table.sellerId, table.slug),
check(
'products_release_year_valid',
sql`${table.releaseYear} IS NULL OR ${table.releaseYear} BETWEEN 1996 AND 9999`,
),
],
);
export const inventoryItems = pgTable('inventory_items', {
id: uuid('id').defaultRandom().primaryKey(),
productId: uuid('product_id')
.notNull()
.references(() => products.id),
conditionId: uuid('condition_id')
.notNull()
.references(() => conditions.id),
status: inventoryStatus('status').default('ACTIVE').notNull(),
notes: text('notes'),
...timestamps,
});
export const inventoryImages = pgTable(
'inventory_images',
{
id: uuid('id').defaultRandom().primaryKey(),
inventoryItemId: uuid('inventory_item_id')
.notNull()
.references(() => inventoryItems.id),
storageKey: text('storage_key').notNull(),
originalFilename: text('original_filename').notNull(),
mimeType: text('mime_type').notNull(),
fileSize: integer('file_size').notNull(),
width: integer('width').notNull(),
height: integer('height').notNull(),
altText: text('alt_text'),
sortOrder: integer('sort_order').default(0).notNull(),
isPrimary: boolean('is_primary').default(false).notNull(),
processingStatus: text('processing_status').default('PENDING').notNull(),
...timestamps,
},
(table) => [index('inventory_images_inventory_item_id_index').on(table.inventoryItemId)],
);
export const listings = pgTable('listings', {
id: uuid('id').defaultRandom().primaryKey(),
inventoryItemId: uuid('inventory_item_id')
.notNull()
.references(() => inventoryItems.id),
productName: text('product_name').notNull(),
categoryId: uuid('category_id').notNull(),
conditionId: uuid('condition_id')
.notNull()
.references(() => conditions.id),
askingPriceMinor: integer('asking_price_minor').notNull(),
imageCount: integer('image_count').notNull(),
status: listingStatus('status').default('DRAFT').notNull(),
notes: text('notes'),
...timestamps,
}, (table) => [
index('listings_inventory_item_id_index').on(table.inventoryItemId),
index('listings_status_index').on(table.status),
]);