Compare commits

..

No commits in common. "de38a482e1e1ede0f6ee38799a2444212760f5c4" and "7a6bd4b92c46643e87082ed7c0bd1d05b8b0942a" have entirely different histories.

14 changed files with 15 additions and 262 deletions

View File

@ -155,3 +155,7 @@ src/
3. Make your changes
4. Run tests and linting: `npm run check`
5. Submit a pull request
## License
[Add your license here]

View File

@ -1,7 +1,8 @@
version: '3.8'
services:
chefbible:
build: .
image: git.redbackpack.ca/taogaetz/chefbible:latest
container_name: chefbible
restart: unless-stopped
ports:
@ -14,15 +15,10 @@ services:
- ORIGIN=${ORIGIN}
- ACCESS_PIN=${ACCESS_PIN}
volumes:
- chefbible-data:/app/data
- /home/chefbible/data:/app/data
healthcheck:
test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:3000/health" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
volumes:
chefbible-data:

View File

@ -1,21 +0,0 @@
-- RedefineTables
PRAGMA defer_foreign_keys=ON;
PRAGMA foreign_keys=OFF;
CREATE TABLE "new_Recipe" (
"id" TEXT NOT NULL PRIMARY KEY,
"name" TEXT NOT NULL,
"description" TEXT,
"instructions" TEXT,
"photoUrl" TEXT,
"time" TEXT NOT NULL DEFAULT 'Medium',
"station" TEXT NOT NULL DEFAULT 'Pans',
"type" TEXT NOT NULL DEFAULT 'Dish',
"hidden" BOOLEAN NOT NULL DEFAULT false,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);
INSERT INTO "new_Recipe" ("createdAt", "description", "hidden", "id", "instructions", "name", "photoUrl", "station", "time", "updatedAt") SELECT "createdAt", "description", "hidden", "id", "instructions", "name", "photoUrl", "station", "time", "updatedAt" FROM "Recipe";
DROP TABLE "Recipe";
ALTER TABLE "new_Recipe" RENAME TO "Recipe";
PRAGMA foreign_keys=ON;
PRAGMA defer_foreign_keys=OFF;

View File

@ -25,7 +25,6 @@ model Recipe {
photoUrl String?
time String @default("Medium") // Quick, Medium, Long
station String @default("Pans") // Garde Manger, Pans, Grill
type String @default("Dish") // Ingredient or Dish
hidden Boolean @default(false) // Hidden from non-chefs
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

View File

@ -111,9 +111,6 @@
<span class="badge badge-sm badge-success">
{recipe.station || 'Pans'}
</span>
<span class="badge badge-outline badge-sm">
{recipe.type || 'Dish'}
</span>
{#if authenticated}
<button
class="btn btn-outline btn-xs btn-primary"

View File

@ -54,96 +54,18 @@
{#if data.authenticated}
<section class="mx-auto max-w-7xl p-10 lg:p-6">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<a
href="/ingredients"
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
>
<div class="flex flex-col items-center gap-4 text-center">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="text-primary"
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<h2 class="text-2xl font-bold">Ingredients</h2>
<p class="text-base-content/70">View all ingredient recipes</p>
</div>
</a>
<a
href="/dishes"
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
>
<div class="flex flex-col items-center gap-4 text-center">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="text-secondary"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 6v6l4 2" />
</svg>
<h2 class="text-2xl font-bold">Dishes</h2>
<p class="text-base-content/70">View all dish recipes</p>
</div>
</a>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each data.recipes as recipe}
<RecipeCard {recipe} authenticated={data.authenticated} />
{/each}
</div>
</section>
{:else}
<section class="mx-auto max-w-7xl p-10 lg:p-6">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
<a
href="/ingredients"
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
>
<div class="flex flex-col items-center gap-4 text-center">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="text-primary"
>
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5" />
</svg>
<h2 class="text-2xl font-bold">Ingredients</h2>
<p class="text-base-content/70">View all ingredient recipes</p>
</div>
</a>
<a
href="/dishes"
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
>
<div class="flex flex-col items-center gap-4 text-center">
<svg
width="48"
height="48"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
class="text-secondary"
>
<circle cx="12" cy="12" r="10" />
<path d="M12 6v6l4 2" />
</svg>
<h2 class="text-2xl font-bold">Dishes</h2>
<p class="text-base-content/70">View all dish recipes</p>
</div>
</a>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each data.recipes as recipe}
<RecipeCard {recipe} authenticated={data.authenticated} />
{/each}
</div>
</section>
{/if}

View File

@ -1,29 +0,0 @@
import type { PageServerLoad } from './$types';
import prisma from '$lib/server/prisma';
export const load: PageServerLoad = async ({ locals }) => {
// Get dish recipes only
// If not authenticated, filter out hidden recipes
const recipes = await prisma.recipe.findMany({
where: {
type: 'Dish',
...(locals.authenticated ? {} : { hidden: false })
},
include: {
ingredients: {
include: {
ingredient: true
}
}
},
orderBy: {
createdAt: 'desc'
}
});
return {
recipes,
authenticated: locals.authenticated,
hasAccess: locals.hasAccess
};
};

View File

@ -1,33 +0,0 @@
<script lang="ts">
import type { PageProps } from './$types';
import RecipeCard from '$lib/components/RecipeCard.svelte';
let { data }: PageProps = $props();
</script>
<svelte:head>
<title>Dishes - Chef Bible</title>
<meta name="description" content="Dish recipes" />
</svelte:head>
<main class="m-0 min-h-screen bg-gradient-to-br from-base-200 to-base-300 p-0">
<section class="mx-auto max-w-7xl p-10 lg:p-6">
<div class="mb-4">
<h1 class="text-sm text-base-content/60">
<a href="/" class="hover:underline">Home</a> / Dishes
</h1>
</div>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each data.recipes as recipe}
<RecipeCard {recipe} authenticated={data.authenticated} />
{/each}
</div>
{#if data.recipes.length === 0}
<div class="mt-12 text-center">
<p class="text-base-content/60">No dish recipes found.</p>
</div>
{/if}
</section>
</main>

View File

@ -1,29 +0,0 @@
import type { PageServerLoad } from './$types';
import prisma from '$lib/server/prisma';
export const load: PageServerLoad = async ({ locals }) => {
// Get ingredient recipes only
// If not authenticated, filter out hidden recipes
const recipes = await prisma.recipe.findMany({
where: {
type: 'Ingredient',
...(locals.authenticated ? {} : { hidden: false })
},
include: {
ingredients: {
include: {
ingredient: true
}
}
},
orderBy: {
createdAt: 'desc'
}
});
return {
recipes,
authenticated: locals.authenticated,
hasAccess: locals.hasAccess
};
};

View File

@ -1,33 +0,0 @@
<script lang="ts">
import type { PageProps } from './$types';
import RecipeCard from '$lib/components/RecipeCard.svelte';
let { data }: PageProps = $props();
</script>
<svelte:head>
<title>Ingredients - Chef Bible</title>
<meta name="description" content="Ingredient recipes" />
</svelte:head>
<main class="m-0 min-h-screen bg-gradient-to-br from-base-200 to-base-300 p-0">
<section class="mx-auto max-w-7xl p-10 lg:p-6">
<div class="mb-4">
<h1 class="text-sm text-base-content/60">
<a href="/" class="hover:underline">Home</a> / Ingredients
</h1>
</div>
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
{#each data.recipes as recipe}
<RecipeCard {recipe} authenticated={data.authenticated} />
{/each}
</div>
{#if data.recipes.length === 0}
<div class="mt-12 text-center">
<p class="text-base-content/60">No ingredient recipes found.</p>
</div>
{/if}
</section>
</main>

View File

@ -514,14 +514,6 @@
<option value="Grill" selected={data.recipe?.station === 'Grill'}>Grill</option>
</select>
</label>
<label class="form-control min-w-0">
<span class="label"><span class="label-text font-bold">Type</span></span>
<select name="type" class="select-bordered select w-full min-w-0">
<option value="Dish" selected={data.recipe?.type === 'Dish'}>Dish</option>
<option value="Ingredient" selected={data.recipe?.type === 'Ingredient'}>Ingredient</option>
</select>
</label>
</div>
<label class="form-control min-w-0">

View File

@ -24,7 +24,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
const type = ((formData.get('type') as string | null)?.trim() || 'Dish') as string;
const hidden = formData.get('hidden') === 'on'; // Checkbox returns 'on' when checked
const photo = formData.get('photo') as File | null;
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
@ -102,7 +101,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
photoUrl: photoUrl || existingRecipe.photoUrl, // Keep existing photo if no new one uploaded
time,
station,
type,
hidden
}
});

View File

@ -469,14 +469,6 @@
<option>Grill</option>
</select>
</label>
<label class="form-control">
<span class="label"><span class="label-text font-bold">Type</span></span>
<select name="type" class="select-bordered select w-full min-w-0">
<option value="Dish" selected>Dish</option>
<option value="Ingredient">Ingredient</option>
</select>
</label>
</div>
<label class="form-control min-w-0">

View File

@ -24,7 +24,6 @@ export const POST: RequestHandler = async ({ request }) => {
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
const station = ((formData.get('station') as string | null)?.trim() || 'Pans') as string;
const type = ((formData.get('type') as string | null)?.trim() || 'Dish') as string;
const hidden = formData.get('hidden') === 'on'; // Checkbox returns 'on' when checked
const photo = formData.get('photo') as File | null;
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
@ -92,7 +91,6 @@ export const POST: RequestHandler = async ({ request }) => {
photoUrl,
time,
station,
type,
hidden
}
});