Add paginated users/roles, user status, frontend permissions, profile pictures, identifier file storage

- Users page: paginated, searchable, sortable with inline roles (no N+1)
- Roles page: paginated, searchable, sortable + /roles/all for dropdowns
- User is_active field with migration, PATCH toggle, auth check (disabled=401)
- Frontend permission checks: auth store loads permissions, sidebar/buttons conditional
- Profile pictures via file storage for users and members, avatar component
- Identifier images use file storage API instead of base64
- Fix TypeScript errors across admin UI
- 64 API tests passing (10 new)
This commit is contained in:
Ryan Moon
2026-03-29 08:16:34 -05:00
parent 92371ff228
commit b9f78639e2
48 changed files with 1689 additions and 643 deletions

View File

@@ -1,4 +1,4 @@
import { eq, and, count } from 'drizzle-orm'
import { eq, and, count, type Column } from 'drizzle-orm'
import type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'
import { products, inventoryUnits, priceHistory } from '../db/schema/inventory.js'
import { ValidationError } from '../lib/errors.js'
@@ -18,7 +18,7 @@ import {
import { UnitStatusService, ItemConditionService } from './lookup.service.js'
export const ProductService = {
async create(db: PostgresJsDatabase, companyId: string, input: ProductCreateInput) {
async create(db: PostgresJsDatabase<any>, companyId: string, input: ProductCreateInput) {
const [product] = await db
.insert(products)
.values({
@@ -32,7 +32,7 @@ export const ProductService = {
return product
},
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
const [product] = await db
.select()
.from(products)
@@ -41,7 +41,7 @@ export const ProductService = {
return product ?? null
},
async list(db: PostgresJsDatabase, companyId: string, params: PaginationInput) {
async list(db: PostgresJsDatabase<any>, companyId: string, params: PaginationInput) {
const baseWhere = and(eq(products.companyId, companyId), eq(products.isActive, true))
const searchCondition = params.q
@@ -50,7 +50,7 @@ export const ProductService = {
const where = searchCondition ? and(baseWhere, searchCondition) : baseWhere
const sortableColumns: Record<string, typeof products.name> = {
const sortableColumns: Record<string, Column> = {
name: products.name,
sku: products.sku,
brand: products.brand,
@@ -71,7 +71,7 @@ export const ProductService = {
},
async update(
db: PostgresJsDatabase,
db: PostgresJsDatabase<any>,
companyId: string,
id: string,
input: ProductUpdateInput,
@@ -106,7 +106,7 @@ export const ProductService = {
return product ?? null
},
async softDelete(db: PostgresJsDatabase, companyId: string, id: string) {
async softDelete(db: PostgresJsDatabase<any>, companyId: string, id: string) {
const [product] = await db
.update(products)
.set({ isActive: false, updatedAt: new Date() })
@@ -117,7 +117,7 @@ export const ProductService = {
}
export const InventoryUnitService = {
async create(db: PostgresJsDatabase, companyId: string, input: InventoryUnitCreateInput) {
async create(db: PostgresJsDatabase<any>, companyId: string, input: InventoryUnitCreateInput) {
if (input.condition) {
const valid = await ItemConditionService.validateSlug(db, companyId, input.condition)
if (!valid) throw new ValidationError(`Invalid condition: "${input.condition}"`)
@@ -144,7 +144,7 @@ export const InventoryUnitService = {
return unit
},
async getById(db: PostgresJsDatabase, companyId: string, id: string) {
async getById(db: PostgresJsDatabase<any>, companyId: string, id: string) {
const [unit] = await db
.select()
.from(inventoryUnits)
@@ -154,7 +154,7 @@ export const InventoryUnitService = {
},
async listByProduct(
db: PostgresJsDatabase,
db: PostgresJsDatabase<any>,
companyId: string,
productId: string,
params: PaginationInput,
@@ -164,7 +164,7 @@ export const InventoryUnitService = {
eq(inventoryUnits.productId, productId),
)
const sortableColumns: Record<string, typeof inventoryUnits.serialNumber> = {
const sortableColumns: Record<string, Column> = {
serial_number: inventoryUnits.serialNumber,
status: inventoryUnits.status,
condition: inventoryUnits.condition,
@@ -184,7 +184,7 @@ export const InventoryUnitService = {
},
async update(
db: PostgresJsDatabase,
db: PostgresJsDatabase<any>,
companyId: string,
id: string,
input: InventoryUnitUpdateInput,