Skip to content

Commit f313428

Browse files
feat: add qr code generation endpoint
1 parent 9146406 commit f313428

File tree

2 files changed

+111
-46
lines changed

2 files changed

+111
-46
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { beanInformationFormSchema } from "@/lib/beanconqueror/validations/bean-information-form-schema";
2+
import { ZodIssue } from "zod";
3+
import { createUrlFromFormSchema } from "@/lib/beanconqueror/proto/proto-helpers";
4+
import { NextResponse } from "next/server";
5+
import { getBeanLink } from "@/lib/beanlink";
6+
7+
export async function GET() {
8+
return NextResponse.json({
9+
example: {
10+
"coffeeName": "Ethiopian Yirgacheffe",
11+
"roaster": "Sample Roastery",
12+
"roastingDate": "2025-07-26T00:00:00.000Z",
13+
"beanRoastingType": "ESPRESSO",
14+
"degreeOfRoast": [3],
15+
"roast": "CITY_ROAST",
16+
"beanMix": "SINGLE_ORIGIN",
17+
"weight": "250",
18+
"cost": "15",
19+
"flavourProfile": "Floral, citrus, tea-like",
20+
"cuppingPoints": "87",
21+
"decaffeinated": false,
22+
"website": "https://sampleroastery.com",
23+
"eanArticle": "1234567890123",
24+
"notes": "A light and bright Ethiopian coffee",
25+
"varietyInformation": [
26+
{
27+
"country": "Ethiopia",
28+
"region": "Yirgacheffe",
29+
"farm": "Halo Beriti",
30+
"farmer": "Various Smallholders",
31+
"elevation": "2000m",
32+
"variety": "Heirloom",
33+
"processing": "Washed",
34+
"harvested": "2025",
35+
"percentage": "100",
36+
"certification": "Organic",
37+
"purchasePrice": "5",
38+
"fobPrice": "4"
39+
}
40+
]
41+
}
42+
})
43+
}
44+
45+
export async function POST(
46+
req: Request,
47+
) {
48+
const json = await req.json()
49+
const body = beanInformationFormSchema.safeParse(json)
50+
51+
if (!body.success) {
52+
const { errors } = body.error
53+
54+
return NextResponse.json({
55+
error: { message: "Invalid request data", errors }
56+
}, { status: 400 })
57+
}
58+
59+
const beanconquerorUrl = createUrlFromFormSchema(body.data)
60+
const beanLink = await getBeanLink(beanconquerorUrl)
61+
62+
return NextResponse.json({ qr: beanLink })
63+
}
Lines changed: 48 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,65 +1,67 @@
1-
import {z} from "zod";
1+
import { z } from "zod";
22

3-
import {beanconqueror} from "@/lib/beanconqueror/proto/generated/beanconqueror";
3+
import { beanconqueror } from "@/lib/beanconqueror/proto/generated/beanconqueror";
44

55
import BeanRoastingType = beanconqueror.BeanRoastingType;
66
import Roast = beanconqueror.Roast;
77
import BeanMix = beanconqueror.BeanMix;
88

9-
function zodEnumFromObjKeys<K extends string> ( obj: Record<K, unknown> ): z.ZodEnum<[ K, ...K[] ]> {
10-
const [ firstKey, ...otherKeys ] = Object.keys( obj ) as K[];
11-
return z.enum( [ firstKey, ...otherKeys ] );
9+
function zodEnumFromObjKeys<K extends string>(obj: Record<K, unknown>): z.ZodEnum<[K, ...K[]]> {
10+
const [firstKey, ...otherKeys] = Object.keys(obj) as K[];
11+
return z.enum([firstKey, ...otherKeys]);
1212
}
1313

1414
export const varietyInformationShape = z.object({
15-
country: z.optional(z.string()),
16-
region: z.optional(z.string()),
17-
farm: z.optional(z.string()),
18-
farmer: z.optional(z.string()),
19-
elevation: z.optional(z.string()),
20-
variety: z.optional(z.string()),
21-
processing: z.optional(z.string()),
22-
harvested: z.optional(z.string()),
23-
percentage: z.optional(z.string()),
24-
certification: z.optional(z.string()),
25-
purchasePrice: z.optional(z.string()),
26-
fobPrice: z.optional(z.string()),
15+
country: z.optional(z.string()),
16+
region: z.optional(z.string()),
17+
farm: z.optional(z.string()),
18+
farmer: z.optional(z.string()),
19+
elevation: z.optional(z.string()),
20+
variety: z.optional(z.string()),
21+
processing: z.optional(z.string()),
22+
harvested: z.optional(z.string()),
23+
percentage: z.optional(z.string()),
24+
certification: z.optional(z.string()),
25+
purchasePrice: z.optional(z.string()),
26+
fobPrice: z.optional(z.string()),
2727
});
2828

2929
export type varietyInformationType = z.infer<typeof varietyInformationShape>
3030

3131
export const beanInformationFormSchema = z.object({
32-
coffeeName: z.string(),
33-
roaster: z.optional(z.string()),
34-
roastingDate: z.optional(z.date()),
35-
beanRoastingType: z.optional(zodEnumFromObjKeys(BeanRoastingType)),
36-
degreeOfRoast: z.optional(z.array(z.number().min(0).max(5))),
37-
roast: z.optional(zodEnumFromObjKeys(Roast)),
38-
beanMix: z.optional(zodEnumFromObjKeys(BeanMix)),
39-
weight: z.optional(z.string()), // Note: string instead of decimal because of proto
40-
cost: z.optional(z.string().min(0)), // Note: string instead of decimal because of proto
41-
flavourProfile: z.optional(z.string()),
42-
cuppingPoints: z.optional(z.string()),
43-
decaffeinated: z.optional(z.boolean()),
44-
website: z.optional(z.string()),
45-
eanArticle: z.optional(z.string()),
46-
notes: z.optional(z.string()),
47-
varietyInformation: z.optional(z.array(varietyInformationShape).min(0))
32+
coffeeName: z.string(),
33+
roaster: z.optional(z.string()),
34+
roastingDate: z.optional(
35+
z.preprocess((val) => (typeof val === "string" ? new Date(val) : val), z.date())
36+
),
37+
beanRoastingType: z.optional(zodEnumFromObjKeys(BeanRoastingType)),
38+
degreeOfRoast: z.optional(z.array(z.number().min(0).max(5))),
39+
roast: z.optional(zodEnumFromObjKeys(Roast)),
40+
beanMix: z.optional(zodEnumFromObjKeys(BeanMix)),
41+
weight: z.optional(z.string()), // Note: string instead of decimal because of proto
42+
cost: z.optional(z.string().min(0)), // Note: string instead of decimal because of proto
43+
flavourProfile: z.optional(z.string()),
44+
cuppingPoints: z.optional(z.string()),
45+
decaffeinated: z.optional(z.boolean()),
46+
website: z.optional(z.string()),
47+
eanArticle: z.optional(z.string()),
48+
notes: z.optional(z.string()),
49+
varietyInformation: z.optional(z.array(varietyInformationShape).min(0))
4850
});
4951

5052
export type beanInformationFormSchema = z.infer<typeof beanInformationFormSchema>;
5153

5254
export const defaultVarietyInformation = {
53-
country: "",
54-
region: "",
55-
farm: "",
56-
farmer: "",
57-
elevation: "",
58-
variety: "",
59-
processing: "",
60-
harvested: "",
61-
percentage: "",
62-
certification: "",
63-
purchasePrice: "",
64-
fobPrice: "",
65-
};
55+
country: "",
56+
region: "",
57+
farm: "",
58+
farmer: "",
59+
elevation: "",
60+
variety: "",
61+
processing: "",
62+
harvested: "",
63+
percentage: "",
64+
certification: "",
65+
purchasePrice: "",
66+
fobPrice: "",
67+
};

0 commit comments

Comments
 (0)