mirror of
https://github.com/taogaetz/chefbible.git
synced 2025-12-06 19:50:12 -05:00
Compare commits
No commits in common. "de38a482e1e1ede0f6ee38799a2444212760f5c4" and "7a6bd4b92c46643e87082ed7c0bd1d05b8b0942a" have entirely different histories.
de38a482e1
...
7a6bd4b92c
@ -155,3 +155,7 @@ src/
|
|||||||
3. Make your changes
|
3. Make your changes
|
||||||
4. Run tests and linting: `npm run check`
|
4. Run tests and linting: `npm run check`
|
||||||
5. Submit a pull request
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Add your license here]
|
||||||
|
|||||||
@ -1,7 +1,8 @@
|
|||||||
version: '3.8'
|
version: '3.8'
|
||||||
|
|
||||||
services:
|
services:
|
||||||
chefbible:
|
chefbible:
|
||||||
build: .
|
image: git.redbackpack.ca/taogaetz/chefbible:latest
|
||||||
container_name: chefbible
|
container_name: chefbible
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
ports:
|
ports:
|
||||||
@ -14,15 +15,10 @@ services:
|
|||||||
- ORIGIN=${ORIGIN}
|
- ORIGIN=${ORIGIN}
|
||||||
- ACCESS_PIN=${ACCESS_PIN}
|
- ACCESS_PIN=${ACCESS_PIN}
|
||||||
volumes:
|
volumes:
|
||||||
- chefbible-data:/app/data
|
- /home/chefbible/data:/app/data
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:3000/health" ]
|
test: [ "CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1:3000/health" ]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
||||||
volumes:
|
|
||||||
chefbible-data:
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
@ -25,7 +25,6 @@ model Recipe {
|
|||||||
photoUrl String?
|
photoUrl String?
|
||||||
time String @default("Medium") // Quick, Medium, Long
|
time String @default("Medium") // Quick, Medium, Long
|
||||||
station String @default("Pans") // Garde Manger, Pans, Grill
|
station String @default("Pans") // Garde Manger, Pans, Grill
|
||||||
type String @default("Dish") // Ingredient or Dish
|
|
||||||
hidden Boolean @default(false) // Hidden from non-chefs
|
hidden Boolean @default(false) // Hidden from non-chefs
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
|||||||
@ -111,9 +111,6 @@
|
|||||||
<span class="badge badge-sm badge-success">
|
<span class="badge badge-sm badge-success">
|
||||||
{recipe.station || 'Pans'}
|
{recipe.station || 'Pans'}
|
||||||
</span>
|
</span>
|
||||||
<span class="badge badge-outline badge-sm">
|
|
||||||
{recipe.type || 'Dish'}
|
|
||||||
</span>
|
|
||||||
{#if authenticated}
|
{#if authenticated}
|
||||||
<button
|
<button
|
||||||
class="btn btn-outline btn-xs btn-primary"
|
class="btn btn-outline btn-xs btn-primary"
|
||||||
|
|||||||
@ -54,96 +54,18 @@
|
|||||||
|
|
||||||
{#if data.authenticated}
|
{#if data.authenticated}
|
||||||
<section class="mx-auto max-w-7xl p-10 lg:p-6">
|
<section class="mx-auto max-w-7xl p-10 lg:p-6">
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
<a
|
{#each data.recipes as recipe}
|
||||||
href="/ingredients"
|
<RecipeCard {recipe} authenticated={data.authenticated} />
|
||||||
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
|
{/each}
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{:else}
|
{:else}
|
||||||
<section class="mx-auto max-w-7xl p-10 lg:p-6">
|
<section class="mx-auto max-w-7xl p-10 lg:p-6">
|
||||||
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2">
|
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||||
<a
|
{#each data.recipes as recipe}
|
||||||
href="/ingredients"
|
<RecipeCard {recipe} authenticated={data.authenticated} />
|
||||||
class="card border border-base-200 bg-white p-8 shadow-lg transition-shadow hover:shadow-xl"
|
{/each}
|
||||||
>
|
|
||||||
<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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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>
|
|
||||||
@ -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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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>
|
|
||||||
@ -514,14 +514,6 @@
|
|||||||
<option value="Grill" selected={data.recipe?.station === 'Grill'}>Grill</option>
|
<option value="Grill" selected={data.recipe?.station === 'Grill'}>Grill</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
|
|
||||||
<label class="form-control min-w-0">
|
<label class="form-control min-w-0">
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export const POST: RequestHandler = async ({ request, params }) => {
|
|||||||
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
||||||
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
|
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 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 hidden = formData.get('hidden') === 'on'; // Checkbox returns 'on' when checked
|
||||||
const photo = formData.get('photo') as File | null;
|
const photo = formData.get('photo') as File | null;
|
||||||
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | 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
|
photoUrl: photoUrl || existingRecipe.photoUrl, // Keep existing photo if no new one uploaded
|
||||||
time,
|
time,
|
||||||
station,
|
station,
|
||||||
type,
|
|
||||||
hidden
|
hidden
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -469,14 +469,6 @@
|
|||||||
<option>Grill</option>
|
<option>Grill</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
|
|
||||||
<label class="form-control min-w-0">
|
<label class="form-control min-w-0">
|
||||||
|
|||||||
@ -24,7 +24,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
const instructions = (formData.get('instructions') as string | null)?.trim() || null;
|
||||||
const time = ((formData.get('time') as string | null)?.trim() || 'Medium') as string;
|
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 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 hidden = formData.get('hidden') === 'on'; // Checkbox returns 'on' when checked
|
||||||
const photo = formData.get('photo') as File | null;
|
const photo = formData.get('photo') as File | null;
|
||||||
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
|
const parsedIngredientsRaw = formData.get('parsedIngredients') as string | null;
|
||||||
@ -92,7 +91,6 @@ export const POST: RequestHandler = async ({ request }) => {
|
|||||||
photoUrl,
|
photoUrl,
|
||||||
time,
|
time,
|
||||||
station,
|
station,
|
||||||
type,
|
|
||||||
hidden
|
hidden
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user