diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644
index 0000000..c0e01ca
--- /dev/null
+++ b/.idea/sqldialects.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/package.json b/package.json
index f70ca99..186e93a 100644
--- a/package.json
+++ b/package.json
@@ -42,7 +42,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"cmdk": "^1.0.0",
- "drizzle-orm": "^0.30.6",
+ "drizzle-orm": "^0.30.9",
"jsdom": "^23.0.1",
"lucide-react": "^0.368.0",
"next": "^14.0.3",
@@ -75,7 +75,7 @@
"@typescript-eslint/parser": "^7.4.0",
"autoprefixer": "^10.4.14",
"dotenv-cli": "^7.3.0",
- "drizzle-kit": "^0.20.14",
+ "drizzle-kit": "^0.20.17",
"eslint": "^8.57.0",
"eslint-config-next": "13.0.0",
"eslint-config-prettier": "^8.8.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c8a5674..d56cc0e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,4 +1,4 @@
-lockfileVersion: '6.1'
+lockfileVersion: '6.0'
settings:
autoInstallPeers: true
@@ -72,8 +72,8 @@ dependencies:
specifier: ^1.0.0
version: 1.0.0(@types/react-dom@18.2.15)(@types/react@18.2.37)(react-dom@18.2.0)(react@18.2.0)
drizzle-orm:
- specifier: ^0.30.6
- version: 0.30.6(@planetscale/database@1.11.0)(@types/better-sqlite3@7.6.8)(@types/react@18.2.37)(better-sqlite3@9.2.2)(mysql2@3.9.7)(react@18.2.0)
+ specifier: ^0.30.9
+ version: 0.30.9(@planetscale/database@1.11.0)(@types/better-sqlite3@7.6.8)(@types/react@18.2.37)(better-sqlite3@9.2.2)(mysql2@3.9.7)(react@18.2.0)
jsdom:
specifier: ^23.0.1
version: 23.0.1
@@ -167,8 +167,8 @@ devDependencies:
specifier: ^7.3.0
version: 7.3.0
drizzle-kit:
- specifier: ^0.20.14
- version: 0.20.14
+ specifier: ^0.20.17
+ version: 0.20.17
eslint:
specifier: ^8.57.0
version: 8.57.0
@@ -404,12 +404,6 @@ packages:
'@babel/helper-validator-identifier': 7.22.20
to-fast-properties: 2.0.0
- /@drizzle-team/studio@0.0.39:
- resolution: {integrity: sha512-c5Hkm7MmQC2n5qAsKShjQrHoqlfGslB8+qWzsGGZ+2dHMRTNG60UuzalF0h0rvBax5uzPXuGkYLGaQ+TUX3yMw==}
- dependencies:
- superjson: 2.2.1
- dev: true
-
/@ericcornelissen/bash-parser@0.5.2:
resolution: {integrity: sha512-4pIMTa1nEFfMXitv7oaNEWOdM+zpOZavesa5GaiWTgda6Zk32CFGxjUp/iIaN0PwgUW1yTq/fztSjbpE8SLGZQ==}
engines: {node: '>=4'}
@@ -896,6 +890,21 @@ packages:
resolution: {integrity: sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==}
dev: false
+ /@hono/node-server@1.11.0:
+ resolution: {integrity: sha512-TLIJq9TMtD1NEG1mVoqNUn1Ita0qSaB5XboZErjFBcO/GJYXwWY4dVdTi9G0lbxtu0x+hJXDItcLaFHb7rlFTw==}
+ engines: {node: '>=18.14.1'}
+ dev: true
+
+ /@hono/zod-validator@0.2.1(hono@4.2.7)(zod@3.22.4):
+ resolution: {integrity: sha512-HFoxln7Q6JsE64qz2WBS28SD33UB2alp3aRKmcWnNLDzEL1BLsWfbdX6e1HIiUprHYTIXf5y7ax8eYidKUwyaA==}
+ peerDependencies:
+ hono: '>=3.9.0'
+ zod: ^3.19.1
+ dependencies:
+ hono: 4.2.7
+ zod: 3.22.4
+ dev: true
+
/@hookform/resolvers@3.3.3(react-hook-form@7.49.2):
resolution: {integrity: sha512-bOMxKkSD3zWcS11TKoUQ8O0ZqKslFohvUsPKSrdCHiuEuMjRo/u3cq9YRJD/+xtNGYup++XD2LkjhegP5XENiw==}
peerDependencies:
@@ -3246,12 +3255,13 @@ packages:
wordwrap: 1.0.0
dev: true
- /drizzle-kit@0.20.14:
- resolution: {integrity: sha512-0fHv3YIEaUcSVPSGyaaBfOi9bmpajjhbJNdPsRMIUvYdLVxBu9eGjH8mRc3Qk7HVmEidFc/lhG1YyJhoXrn5yA==}
+ /drizzle-kit@0.20.17:
+ resolution: {integrity: sha512-mLVDS4nXmO09wFVlzGrdshWnAL+U9eQGC5zRs6hTN6Q9arwQGWU2XnZ17I8BM8Quau8CQRx3Ms6VPgRWJFVp7Q==}
hasBin: true
dependencies:
- '@drizzle-team/studio': 0.0.39
'@esbuild-kit/esm-loader': 2.6.5
+ '@hono/node-server': 1.11.0
+ '@hono/zod-validator': 0.2.1(hono@4.2.7)(zod@3.22.4)
camelcase: 7.0.1
chalk: 5.3.0
commander: 9.5.0
@@ -3260,16 +3270,18 @@ packages:
esbuild-register: 3.5.0(esbuild@0.19.12)
glob: 8.1.0
hanji: 0.0.5
+ hono: 4.2.7
json-diff: 0.9.0
minimatch: 7.4.6
semver: 7.6.0
+ superjson: 2.2.1
zod: 3.22.4
transitivePeerDependencies:
- supports-color
dev: true
- /drizzle-orm@0.30.6(@planetscale/database@1.11.0)(@types/better-sqlite3@7.6.8)(@types/react@18.2.37)(better-sqlite3@9.2.2)(mysql2@3.9.7)(react@18.2.0):
- resolution: {integrity: sha512-8RgNUmY7J03GRuRgBV5SaJNbYgLVPjdSWNS/bRkIMIHt2TFCA439lJsNpqYX8asyKMqkw8ceBiamUnCIXZIt9w==}
+ /drizzle-orm@0.30.9(@planetscale/database@1.11.0)(@types/better-sqlite3@7.6.8)(@types/react@18.2.37)(better-sqlite3@9.2.2)(mysql2@3.9.7)(react@18.2.0):
+ resolution: {integrity: sha512-VOiCFsexErmgqvNCOmbzmqDCZzZsHoz6SkWAjTFxsTr1AllKDbDJ2+GgedLXsXMDgpg/ljDG1zItIFeZtiO2LA==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
'@cloudflare/workers-types': '>=3'
@@ -3283,7 +3295,7 @@ packages:
'@types/pg': '*'
'@types/react': '>=18'
'@types/sql.js': '*'
- '@vercel/postgres': '*'
+ '@vercel/postgres': '>=0.8.0'
'@xata.io/client': '*'
better-sqlite3: '>=7'
bun-types: '*'
@@ -4486,6 +4498,11 @@ packages:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
dev: true
+ /hono@4.2.7:
+ resolution: {integrity: sha512-k1xHi86tJnRIVvqhFMBDGFKJ8r5O+bEsT4P59ZK59r0F300Xd910/r237inVfuT/VmE86RQQffX4OYNda6dLXw==}
+ engines: {node: '>=16.0.0'}
+ dev: true
+
/hosted-git-info@4.1.0:
resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==}
engines: {node: '>=10'}
diff --git a/src/app/(dashboard)/recipes/[id]/recipeForm.tsx b/src/app/(dashboard)/recipes/[id]/recipeForm.tsx
index ef1db0b..6b0dd96 100644
--- a/src/app/(dashboard)/recipes/[id]/recipeForm.tsx
+++ b/src/app/(dashboard)/recipes/[id]/recipeForm.tsx
@@ -13,6 +13,14 @@ import { Controller, FormProvider, useForm } from "react-hook-form"
import { toast } from "sonner"
import { Button } from "~/components/ui/button"
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormLabel,
+ FormMessage,
+} from "~/components/ui/form"
+import { Input } from "~/components/ui/input"
import { IngredientTable } from "~/components/ingredient-table"
import { RecipeTitleInput } from "~/components/recipe-title-input"
@@ -52,9 +60,10 @@ function RecipeFormInner({
}
}),
recipeName: recipe.name,
- method: recipe.steps || undefined,
- imageUrl: recipe.imageUrl || undefined,
+ method: recipe.steps ?? undefined,
+ imageUrl: recipe.imageUrl ?? undefined,
recipeBuddyRecipeId: recipe.id,
+ servings: recipe.servings ?? undefined,
},
})
@@ -81,6 +90,19 @@ function RecipeFormInner({
name="recipeName"
control={form.control}
/>
+ (
+
+ Servings
+
+
+
+
+
+ )}
+ name="servings"
+ control={form.control}
+ />
{
+ if (a < 1) return 1
+ return a
+ })
+
export const CreateRecipeInGrocyCommandSchema = z.object({
recipeBuddyRecipeId: z.number(),
recipeName: z.string().trim().min(1),
ingredients: IngredientSchema.array(),
method: z.string().optional(),
imageUrl: z.string().url().optional(),
+ servings: numberLikeToNumberAtLeastOne.optional(),
})
export type CreateRecipeInGrocyCommand = z.infer<
diff --git a/src/server/api/modules/recipes/service/schemas.ts b/src/server/api/modules/recipes/service/schemas.ts
index b47707f..91bfcd8 100644
--- a/src/server/api/modules/recipes/service/schemas.ts
+++ b/src/server/api/modules/recipes/service/schemas.ts
@@ -18,5 +18,23 @@ export const RecipeImageUrlSchema = z
})
export const JsonLdRecipeSchema = z.object({
- "@type": z.string(),
+ "@type": z.union([z.string(), z.tuple([z.string()]).transform((a) => a[0])]),
+})
+
+export const ExtractNumberSchema = z.coerce.string().transform((val, ctx) => {
+ const numberRegex = /\d+/g
+
+ const regexResult = numberRegex.exec(val)
+
+ if (!regexResult) {
+ ctx.addIssue({
+ message: "No numbers found in servings",
+ code: "custom",
+ })
+ return z.NEVER
+ }
+
+ const [first] = regexResult
+
+ return parseInt(first)
})
diff --git a/src/server/api/modules/recipes/service/scraper.ts b/src/server/api/modules/recipes/service/scraper.ts
index 1503e85..c95f1b6 100644
--- a/src/server/api/modules/recipes/service/scraper.ts
+++ b/src/server/api/modules/recipes/service/scraper.ts
@@ -1,5 +1,6 @@
import { TRPCError } from "@trpc/server"
import {
+ ExtractNumberSchema,
JsonLdRecipeSchema,
RecipeImageUrlSchema,
RecipeStepSchema,
@@ -62,6 +63,7 @@ function getSchemaRecipeFromNodeList(nodeList: NodeList) {
}
if (Array.isArray(parsedNodeContent)) {
+ console.log("its an array")
for (const metadataObject of parsedNodeContent) {
if (jsonObjectIsRecipe(metadataObject)) {
return metadataObject
@@ -106,11 +108,14 @@ export async function hydrateRecipe(url: string) {
(a) => ({ scrapedName: a })
)
+ const servings = ExtractNumberSchema.safeParse(recipeData.recipeYield)
+
const recipe: InsertRecipe = {
name: recipeData.name,
url,
steps: steps.data.join("\n"),
imageUrl: image.success ? image.data : undefined,
+ servings: servings.success ? servings.data : undefined,
}
return { recipe, ingredients: ings }
diff --git a/src/server/db/drizzle/0001_worthless_aqueduct.sql b/src/server/db/drizzle/0001_worthless_aqueduct.sql
new file mode 100644
index 0000000..5ec8e6b
--- /dev/null
+++ b/src/server/db/drizzle/0001_worthless_aqueduct.sql
@@ -0,0 +1 @@
+ALTER TABLE `recipe-buddy_recipe` ADD `servings` integer;
diff --git a/src/server/db/drizzle/meta/0001_snapshot.json b/src/server/db/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..001cfb6
--- /dev/null
+++ b/src/server/db/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,153 @@
+{
+ "version": "5",
+ "dialect": "sqlite",
+ "id": "ce10c78c-b332-4765-b5d1-932f1db6cf33",
+ "prevId": "6d9f7961-4666-469a-bc84-92afae7f2c60",
+ "tables": {
+ "recipe-buddy_ingredient": {
+ "name": "recipe-buddy_ingredient",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "scrapedName": {
+ "name": "scrapedName",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "recipeId": {
+ "name": "recipeId",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "recipe-buddy_ingredient_recipeId_recipe-buddy_recipe_id_fk": {
+ "name": "recipe-buddy_ingredient_recipeId_recipe-buddy_recipe_id_fk",
+ "tableFrom": "recipe-buddy_ingredient",
+ "tableTo": "recipe-buddy_recipe",
+ "columnsFrom": ["recipeId"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "recipe-buddy_recipe": {
+ "name": "recipe-buddy_recipe",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "url": {
+ "name": "url",
+ "type": "text(512)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "steps": {
+ "name": "steps",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "imageUrl": {
+ "name": "imageUrl",
+ "type": "text(256)",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ },
+ "servings": {
+ "name": "servings",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "autoincrement": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ },
+ "recipe-buddy_user": {
+ "name": "recipe-buddy_user",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "integer",
+ "primaryKey": true,
+ "notNull": true,
+ "autoincrement": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "username": {
+ "name": "username",
+ "type": "text(255)",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ },
+ "passwordHash": {
+ "name": "passwordHash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "autoincrement": false
+ }
+ },
+ "indexes": {
+ "recipe-buddy_user_username_unique": {
+ "name": "recipe-buddy_user_username_unique",
+ "columns": ["username"],
+ "isUnique": true
+ },
+ "username_idx": {
+ "name": "username_idx",
+ "columns": ["name"],
+ "isUnique": false
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {}
+ }
+ },
+ "enums": {},
+ "_meta": {
+ "schemas": {},
+ "tables": {},
+ "columns": {}
+ }
+}
diff --git a/src/server/db/drizzle/meta/_journal.json b/src/server/db/drizzle/meta/_journal.json
index be297f9..4bd91b6 100644
--- a/src/server/db/drizzle/meta/_journal.json
+++ b/src/server/db/drizzle/meta/_journal.json
@@ -8,6 +8,13 @@
"when": 1711809186824,
"tag": "0000_rare_sauron",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "5",
+ "when": 1713943610110,
+ "tag": "0001_worthless_aqueduct",
+ "breakpoints": true
}
]
}
diff --git a/src/server/db/migrate.ts b/src/server/db/migrate.ts
index 020152f..4969304 100644
--- a/src/server/db/migrate.ts
+++ b/src/server/db/migrate.ts
@@ -1,7 +1,14 @@
+import { dirname, join } from "path"
+import { fileURLToPath } from "url"
import { migrate } from "drizzle-orm/better-sqlite3/migrator"
import { db, sqlite } from "./index"
-await migrate(db, { migrationsFolder: "./drizzle" })
+const __filename = fileURLToPath(import.meta.url) // get the resolved path to the file
+const __dirname = dirname(__filename)
+
+const migrationsPath = join(__dirname, "drizzle")
+
+await migrate(db, { migrationsFolder: migrationsPath })
await sqlite.close()
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts
index 40515e6..96b5c3d 100644
--- a/src/server/db/schema.ts
+++ b/src/server/db/schema.ts
@@ -14,6 +14,7 @@ export const recipes = sqLiteTable("recipe", {
url: text("url", { length: 512 }).notNull(),
steps: text("steps"),
imageUrl: text("imageUrl", { length: 256 }),
+ servings: integer("servings", { mode: "number" }),
})
export const recipeRelations = relations(recipes, ({ many }) => ({