Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 91af671c4e |
@@ -62,6 +62,10 @@ jobs:
|
|||||||
name: Build & Push Docker Image
|
name: Build & Push Docker Image
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: [lint-typecheck, test]
|
needs: [lint-typecheck, test]
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
id-token: write
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
@@ -79,12 +83,12 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
- name: Log in to Gitea Container Registry
|
- name: Log in to GitHub Container Registry
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@v3
|
||||||
with:
|
with:
|
||||||
registry: git.farh.net
|
registry: ghcr.io
|
||||||
username: ${{ gitea.actor }}
|
username: ${{ github.actor }}
|
||||||
password: ${{ secrets.REGISTRY_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Build and push API image
|
- name: Build and push API image
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
@@ -93,7 +97,7 @@ jobs:
|
|||||||
file: Dockerfile
|
file: Dockerfile
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
git.farh.net/groombook/api:${{ steps.version.outputs.tag }}
|
ghcr.io/groombook/api:${{ steps.version.outputs.tag }}
|
||||||
${{ github.ref == 'refs/heads/main' && 'git.farh.net/groombook/api:latest' || '' }}
|
${{ github.ref == 'refs/heads/main' && 'ghcr.io/groombook/api:latest' || '' }}
|
||||||
cache-from: type=registry,ref=git.farh.net/groombook/cache:api
|
cache-from: type=gha
|
||||||
cache-to: type=registry,ref=git.farh.net/groombook/cache:api,mode=max
|
cache-to: type=gha,mode=max
|
||||||
@@ -142,8 +142,6 @@ export const pets = pgTable(
|
|||||||
cutStyle: text("cut_style"),
|
cutStyle: text("cut_style"),
|
||||||
shampooPreference: text("shampoo_preference"),
|
shampooPreference: text("shampoo_preference"),
|
||||||
specialCareNotes: text("special_care_notes"),
|
specialCareNotes: text("special_care_notes"),
|
||||||
coatType: text("coat_type"),
|
|
||||||
petSizeCategory: text("pet_size_category"),
|
|
||||||
customFields: jsonb("custom_fields").$type<Record<string, string>>().notNull().default({}),
|
customFields: jsonb("custom_fields").$type<Record<string, string>>().notNull().default({}),
|
||||||
photoKey: text("photo_key"),
|
photoKey: text("photo_key"),
|
||||||
photoUploadedAt: timestamp("photo_uploaded_at"),
|
photoUploadedAt: timestamp("photo_uploaded_at"),
|
||||||
|
|||||||
@@ -39,12 +39,6 @@ export interface Pet {
|
|||||||
cutStyle: string | null;
|
cutStyle: string | null;
|
||||||
shampooPreference: string | null;
|
shampooPreference: string | null;
|
||||||
specialCareNotes: string | null;
|
specialCareNotes: string | null;
|
||||||
coatType: string | null;
|
|
||||||
petSizeCategory: string | null;
|
|
||||||
preferredCuts: string[];
|
|
||||||
medicalAlerts: MedicalAlert[];
|
|
||||||
temperamentScore?: number;
|
|
||||||
temperamentFlags?: string[];
|
|
||||||
customFields: Record<string, string>;
|
customFields: Record<string, string>;
|
||||||
photoKey?: string;
|
photoKey?: string;
|
||||||
photoUploadedAt?: string;
|
photoUploadedAt?: string;
|
||||||
@@ -214,14 +208,3 @@ export interface PaginatedList<T> {
|
|||||||
page: number;
|
page: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AlertSeverity = "low" | "medium" | "high";
|
|
||||||
|
|
||||||
export interface MedicalAlert {
|
|
||||||
id: string;
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
severity: AlertSeverity;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type CoatType = "smooth" | "double" | "curly" | "wire" | "long" | "hairless";
|
|
||||||
|
|||||||
+23
-2
@@ -1,7 +1,7 @@
|
|||||||
import type { MiddlewareHandler } from "hono";
|
import type { MiddlewareHandler } from "hono";
|
||||||
import { and, eq, getDb, sql, staff } from "@groombook/db";
|
import { and, eq, getDb, sql, staff, staffRoleEnum } from "@groombook/db";
|
||||||
|
|
||||||
export type StaffRole = "groomer" | "receptionist" | "manager";
|
type StaffRole = typeof staffRoleEnum.enumValues[number];
|
||||||
export type StaffRow = typeof staff.$inferSelect;
|
export type StaffRow = typeof staff.$inferSelect;
|
||||||
|
|
||||||
export interface AppEnv {
|
export interface AppEnv {
|
||||||
@@ -110,6 +110,27 @@ export const resolveStaffMiddleware: MiddlewareHandler<AppEnv> = async (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-create staff record for authenticated OAuth users with no existing staff record
|
||||||
|
// This allows new OAuth users to access the app (defaults to receptionist role)
|
||||||
|
if (jwt.email && jwt.name) {
|
||||||
|
const [newStaff] = await db
|
||||||
|
.insert(staff)
|
||||||
|
.values({
|
||||||
|
email: jwt.email,
|
||||||
|
name: jwt.name,
|
||||||
|
userId: jwt.sub,
|
||||||
|
role: "receptionist",
|
||||||
|
active: true,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
if (newStaff) {
|
||||||
|
c.set("staff", newStaff);
|
||||||
|
await next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return c.json(
|
return c.json(
|
||||||
{ error: "Forbidden: no staff record found for authenticated user" },
|
{ error: "Forbidden: no staff record found for authenticated user" },
|
||||||
403
|
403
|
||||||
|
|||||||
@@ -112,8 +112,6 @@ const bookingSchema = z.object({
|
|||||||
petName: z.string().min(1).max(200),
|
petName: z.string().min(1).max(200),
|
||||||
petSpecies: z.string().min(1).max(100),
|
petSpecies: z.string().min(1).max(100),
|
||||||
petBreed: z.string().max(100).optional(),
|
petBreed: z.string().max(100).optional(),
|
||||||
petSizeCategory: z.string().max(50).optional(),
|
|
||||||
petCoatType: z.string().max(50).optional(),
|
|
||||||
notes: z.string().max(2000).optional(),
|
notes: z.string().max(2000).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -193,8 +191,6 @@ bookRouter.post(
|
|||||||
name: body.petName,
|
name: body.petName,
|
||||||
species: body.petSpecies,
|
species: body.petSpecies,
|
||||||
breed: body.petBreed ?? null,
|
breed: body.petBreed ?? null,
|
||||||
coatType: body.petCoatType ?? null,
|
|
||||||
petSizeCategory: body.petSizeCategory ?? null,
|
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
const pet = petInserted[0];
|
const pet = petInserted[0];
|
||||||
|
|||||||
Reference in New Issue
Block a user