@purveyors/cli 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/catalog.d.ts +4 -56
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +14 -81
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +82 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/inventory.d.ts +2 -25
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +109 -149
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/roast.d.ts +2 -41
- package/dist/commands/roast.d.ts.map +1 -1
- package/dist/commands/roast.js +445 -126
- package/dist/commands/roast.js.map +1 -1
- package/dist/commands/sales.d.ts +2 -10
- package/dist/commands/sales.d.ts.map +1 -1
- package/dist/commands/sales.js +100 -112
- package/dist/commands/sales.js.map +1 -1
- package/dist/commands/tasting.d.ts +4 -38
- package/dist/commands/tasting.d.ts.map +1 -1
- package/dist/commands/tasting.js +93 -116
- package/dist/commands/tasting.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai.d.ts +36 -0
- package/dist/lib/ai.d.ts.map +1 -0
- package/dist/lib/ai.js +38 -0
- package/dist/lib/ai.js.map +1 -0
- package/dist/lib/artisan/db.d.ts +37 -0
- package/dist/lib/artisan/db.d.ts.map +1 -0
- package/dist/lib/artisan/db.js +51 -0
- package/dist/lib/artisan/db.js.map +1 -0
- package/dist/lib/artisan/import.d.ts +16 -0
- package/dist/lib/artisan/import.d.ts.map +1 -0
- package/dist/lib/artisan/import.js +447 -0
- package/dist/lib/artisan/import.js.map +1 -0
- package/dist/lib/artisan/index.d.ts +9 -0
- package/dist/lib/artisan/index.d.ts.map +1 -0
- package/dist/lib/artisan/index.js +7 -0
- package/dist/lib/artisan/index.js.map +1 -0
- package/dist/lib/artisan/parser.d.ts +19 -0
- package/dist/lib/artisan/parser.d.ts.map +1 -0
- package/dist/lib/artisan/parser.js +376 -0
- package/dist/lib/artisan/parser.js.map +1 -0
- package/dist/lib/artisan/temperature.d.ts +52 -0
- package/dist/lib/artisan/temperature.d.ts.map +1 -0
- package/dist/lib/artisan/temperature.js +101 -0
- package/dist/lib/artisan/temperature.js.map +1 -0
- package/dist/lib/artisan/types.d.ts +195 -0
- package/dist/lib/artisan/types.d.ts.map +1 -0
- package/dist/lib/artisan/types.js +35 -0
- package/dist/lib/artisan/types.js.map +1 -0
- package/dist/lib/artisan/validator.d.ts +14 -0
- package/dist/lib/artisan/validator.d.ts.map +1 -0
- package/dist/lib/artisan/validator.js +228 -0
- package/dist/lib/artisan/validator.js.map +1 -0
- package/dist/lib/catalog.d.ts +87 -0
- package/dist/lib/catalog.d.ts.map +1 -0
- package/dist/lib/catalog.js +111 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +59 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/interactive/forms.d.ts +33 -0
- package/dist/lib/interactive/forms.d.ts.map +1 -0
- package/dist/lib/interactive/forms.js +139 -0
- package/dist/lib/interactive/forms.js.map +1 -0
- package/dist/lib/interactive/watch.d.ts +66 -0
- package/dist/lib/interactive/watch.d.ts.map +1 -0
- package/dist/lib/interactive/watch.js +494 -0
- package/dist/lib/interactive/watch.js.map +1 -0
- package/dist/lib/inventory.d.ts +80 -0
- package/dist/lib/inventory.d.ts.map +1 -0
- package/dist/lib/inventory.js +205 -0
- package/dist/lib/inventory.js.map +1 -0
- package/dist/lib/roast.d.ts +127 -0
- package/dist/lib/roast.d.ts.map +1 -0
- package/dist/lib/roast.js +284 -0
- package/dist/lib/roast.js.map +1 -0
- package/dist/lib/sales.d.ts +53 -0
- package/dist/lib/sales.d.ts.map +1 -0
- package/dist/lib/sales.js +155 -0
- package/dist/lib/sales.js.map +1 -0
- package/dist/lib/tasting.d.ts +76 -0
- package/dist/lib/tasting.d.ts.map +1 -0
- package/dist/lib/tasting.js +136 -0
- package/dist/lib/tasting.js.map +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
export interface Sale {
|
|
4
|
+
id: number;
|
|
5
|
+
roast_id: number | null;
|
|
6
|
+
oz_sold: number | null;
|
|
7
|
+
sale_price: number | null;
|
|
8
|
+
buyer: string | null;
|
|
9
|
+
sell_date: string | null;
|
|
10
|
+
user: string;
|
|
11
|
+
last_updated: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const SALE_SELECT = "id, roast_id, oz_sold, sale_price, buyer, sell_date, user, last_updated";
|
|
14
|
+
export declare const listSalesSchema: z.ZodObject<{
|
|
15
|
+
limit: z.ZodDefault<z.ZodNumber>;
|
|
16
|
+
}, z.core.$strip>;
|
|
17
|
+
export type ListSalesInput = z.input<typeof listSalesSchema>;
|
|
18
|
+
export declare const recordSaleSchema: z.ZodObject<{
|
|
19
|
+
roastId: z.ZodNumber;
|
|
20
|
+
oz: z.ZodNumber;
|
|
21
|
+
price: z.ZodNumber;
|
|
22
|
+
buyer: z.ZodOptional<z.ZodString>;
|
|
23
|
+
sellDate: z.ZodOptional<z.ZodString>;
|
|
24
|
+
}, z.core.$strip>;
|
|
25
|
+
export type RecordSaleInput = z.input<typeof recordSaleSchema>;
|
|
26
|
+
export declare const updateSaleSchema: z.ZodObject<{
|
|
27
|
+
oz: z.ZodOptional<z.ZodNumber>;
|
|
28
|
+
price: z.ZodOptional<z.ZodNumber>;
|
|
29
|
+
buyer: z.ZodOptional<z.ZodString>;
|
|
30
|
+
sellDate: z.ZodOptional<z.ZodString>;
|
|
31
|
+
}, z.core.$strip>;
|
|
32
|
+
export type UpdateSaleInput = z.input<typeof updateSaleSchema>;
|
|
33
|
+
export declare const deleteSaleSchema: z.ZodObject<{
|
|
34
|
+
id: z.ZodNumber;
|
|
35
|
+
}, z.core.$strip>;
|
|
36
|
+
export type DeleteSaleInput = z.input<typeof deleteSaleSchema>;
|
|
37
|
+
/**
|
|
38
|
+
* List sales for a user (newest first, with roast profile join).
|
|
39
|
+
*/
|
|
40
|
+
export declare function listSales(supabase: SupabaseClient, userId: string, opts?: ListSalesInput): Promise<Sale[]>;
|
|
41
|
+
/**
|
|
42
|
+
* Record a new sale (roastId must belong to userId).
|
|
43
|
+
*/
|
|
44
|
+
export declare function recordSale(supabase: SupabaseClient, userId: string, input: RecordSaleInput): Promise<Sale>;
|
|
45
|
+
/**
|
|
46
|
+
* Update an existing sale (must belong to userId).
|
|
47
|
+
*/
|
|
48
|
+
export declare function updateSale(supabase: SupabaseClient, userId: string, id: number, input: UpdateSaleInput): Promise<Sale>;
|
|
49
|
+
/**
|
|
50
|
+
* Delete a sale (must belong to userId).
|
|
51
|
+
*/
|
|
52
|
+
export declare function deleteSale(supabase: SupabaseClient, userId: string, id: number): Promise<void>;
|
|
53
|
+
//# sourceMappingURL=sales.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sales.d.ts","sourceRoot":"","sources":["../../src/lib/sales.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAK5D,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAID,eAAO,MAAM,WAAW,4EACmD,CAAC;AAI5E,eAAO,MAAM,eAAe;;iBAE1B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAE7D,eAAO,MAAM,gBAAgB;;;;;;iBAM3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D,eAAO,MAAM,gBAAgB;;;;;iBASzB,CAAC;AAEL,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D,eAAO,MAAM,gBAAgB;;iBAE3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI/D;;GAEG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,IAAI,GAAE,cAAmB,GACxB,OAAO,CAAC,IAAI,EAAE,CAAC,CAajB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CA6Cf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,IAAI,CAAC,CA+Cf;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError, PrvrsError } from './errors.js';
|
|
3
|
+
// ─── Shared select columns ────────────────────────────────────────────────────
|
|
4
|
+
export const SALE_SELECT = 'id, roast_id, oz_sold, sale_price, buyer, sell_date, user, last_updated';
|
|
5
|
+
// ─── Zod schemas ──────────────────────────────────────────────────────────────
|
|
6
|
+
export const listSalesSchema = z.object({
|
|
7
|
+
limit: z.number().int().min(1).default(20),
|
|
8
|
+
});
|
|
9
|
+
export const recordSaleSchema = z.object({
|
|
10
|
+
roastId: z.number().int().positive(),
|
|
11
|
+
oz: z.number().positive(),
|
|
12
|
+
price: z.number().min(0),
|
|
13
|
+
buyer: z.string().optional(),
|
|
14
|
+
sellDate: z.string().optional(),
|
|
15
|
+
});
|
|
16
|
+
export const updateSaleSchema = z
|
|
17
|
+
.object({
|
|
18
|
+
oz: z.number().positive().optional(),
|
|
19
|
+
price: z.number().min(0).optional(),
|
|
20
|
+
buyer: z.string().optional(),
|
|
21
|
+
sellDate: z.string().optional(),
|
|
22
|
+
})
|
|
23
|
+
.refine((v) => Object.keys(v).some((k) => v[k] !== undefined), {
|
|
24
|
+
message: 'No update fields provided. Pass at least one of: oz, price, buyer, sellDate.',
|
|
25
|
+
});
|
|
26
|
+
export const deleteSaleSchema = z.object({
|
|
27
|
+
id: z.number().int().positive(),
|
|
28
|
+
});
|
|
29
|
+
// ─── Pure lib functions ───────────────────────────────────────────────────────
|
|
30
|
+
/**
|
|
31
|
+
* List sales for a user (newest first, with roast profile join).
|
|
32
|
+
*/
|
|
33
|
+
export async function listSales(supabase, userId, opts = {}) {
|
|
34
|
+
const parsed = listSalesSchema.parse(opts);
|
|
35
|
+
const { data, error } = await supabase
|
|
36
|
+
.from('sales')
|
|
37
|
+
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
38
|
+
.eq('user', userId)
|
|
39
|
+
.order('sell_date', { ascending: false })
|
|
40
|
+
.limit(parsed.limit);
|
|
41
|
+
if (error)
|
|
42
|
+
throw error;
|
|
43
|
+
return (data ?? []);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Record a new sale (roastId must belong to userId).
|
|
47
|
+
*/
|
|
48
|
+
export async function recordSale(supabase, userId, input) {
|
|
49
|
+
const parsed = recordSaleSchema.parse(input);
|
|
50
|
+
const todayIso = () => new Date().toISOString().slice(0, 10);
|
|
51
|
+
// Verify the roast profile belongs to the user
|
|
52
|
+
const { data: roastExists, error: roastError } = await supabase
|
|
53
|
+
.from('roast_profiles')
|
|
54
|
+
.select('roast_id')
|
|
55
|
+
.eq('roast_id', parsed.roastId)
|
|
56
|
+
.eq('user', userId)
|
|
57
|
+
.single();
|
|
58
|
+
if (roastError || !roastExists) {
|
|
59
|
+
throw new AuthError(`Roast profile ${parsed.roastId} not found or does not belong to you.`);
|
|
60
|
+
}
|
|
61
|
+
const insertPayload = {
|
|
62
|
+
user: userId,
|
|
63
|
+
roast_id: parsed.roastId,
|
|
64
|
+
oz_sold: parsed.oz,
|
|
65
|
+
sale_price: parsed.price,
|
|
66
|
+
sell_date: parsed.sellDate ?? todayIso(),
|
|
67
|
+
};
|
|
68
|
+
if (parsed.buyer !== undefined)
|
|
69
|
+
insertPayload.buyer = parsed.buyer;
|
|
70
|
+
const { data: inserted, error: insertError } = await supabase
|
|
71
|
+
.from('sales')
|
|
72
|
+
.insert(insertPayload)
|
|
73
|
+
.select('id')
|
|
74
|
+
.single();
|
|
75
|
+
if (insertError)
|
|
76
|
+
throw insertError;
|
|
77
|
+
// Re-fetch the full row with roast join
|
|
78
|
+
const { data, error } = await supabase
|
|
79
|
+
.from('sales')
|
|
80
|
+
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
81
|
+
.eq('id', inserted.id)
|
|
82
|
+
.single();
|
|
83
|
+
if (error)
|
|
84
|
+
throw error;
|
|
85
|
+
return data;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Update an existing sale (must belong to userId).
|
|
89
|
+
*/
|
|
90
|
+
export async function updateSale(supabase, userId, id, input) {
|
|
91
|
+
deleteSaleSchema.parse({ id });
|
|
92
|
+
const parsed = updateSaleSchema.parse(input);
|
|
93
|
+
// Verify ownership
|
|
94
|
+
const { data: existing, error: fetchError } = await supabase
|
|
95
|
+
.from('sales')
|
|
96
|
+
.select('id')
|
|
97
|
+
.eq('id', id)
|
|
98
|
+
.eq('user', userId)
|
|
99
|
+
.single();
|
|
100
|
+
if (fetchError || !existing) {
|
|
101
|
+
throw new AuthError(`Sale ${id} not found or does not belong to you.`);
|
|
102
|
+
}
|
|
103
|
+
const updates = {};
|
|
104
|
+
if (parsed.oz !== undefined)
|
|
105
|
+
updates.oz_sold = parsed.oz;
|
|
106
|
+
if (parsed.price !== undefined)
|
|
107
|
+
updates.sale_price = parsed.price;
|
|
108
|
+
if (parsed.buyer !== undefined)
|
|
109
|
+
updates.buyer = parsed.buyer;
|
|
110
|
+
if (parsed.sellDate !== undefined)
|
|
111
|
+
updates.sell_date = parsed.sellDate;
|
|
112
|
+
if (Object.keys(updates).length === 0) {
|
|
113
|
+
throw new PrvrsError('INVALID_ARGUMENT', 'No update fields provided. Pass at least one of: oz, price, buyer, sellDate.');
|
|
114
|
+
}
|
|
115
|
+
const { error: updateError } = await supabase
|
|
116
|
+
.from('sales')
|
|
117
|
+
.update(updates)
|
|
118
|
+
.eq('id', id)
|
|
119
|
+
.eq('user', userId);
|
|
120
|
+
if (updateError)
|
|
121
|
+
throw updateError;
|
|
122
|
+
// Re-fetch the updated row
|
|
123
|
+
const { data, error } = await supabase
|
|
124
|
+
.from('sales')
|
|
125
|
+
.select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
|
|
126
|
+
.eq('id', id)
|
|
127
|
+
.single();
|
|
128
|
+
if (error)
|
|
129
|
+
throw error;
|
|
130
|
+
return data;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Delete a sale (must belong to userId).
|
|
134
|
+
*/
|
|
135
|
+
export async function deleteSale(supabase, userId, id) {
|
|
136
|
+
deleteSaleSchema.parse({ id });
|
|
137
|
+
// Verify ownership
|
|
138
|
+
const { data: existing, error: fetchError } = await supabase
|
|
139
|
+
.from('sales')
|
|
140
|
+
.select('id')
|
|
141
|
+
.eq('id', id)
|
|
142
|
+
.eq('user', userId)
|
|
143
|
+
.single();
|
|
144
|
+
if (fetchError || !existing) {
|
|
145
|
+
throw new AuthError(`Sale ${id} not found or does not belong to you.`);
|
|
146
|
+
}
|
|
147
|
+
const { error: deleteError } = await supabase
|
|
148
|
+
.from('sales')
|
|
149
|
+
.delete()
|
|
150
|
+
.eq('id', id)
|
|
151
|
+
.eq('user', userId);
|
|
152
|
+
if (deleteError)
|
|
153
|
+
throw deleteError;
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=sales.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sales.js","sourceRoot":"","sources":["../../src/lib/sales.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAepD,iFAAiF;AAEjF,MAAM,CAAC,MAAM,WAAW,GACtB,yEAAyE,CAAC;AAE5E,iFAAiF;AAEjF,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;CAC3C,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACpC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IACzB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACxB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAIH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC;KAC9B,MAAM,CAAC;IACN,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACpC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC5B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC;KACD,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAmB,CAAC,KAAK,SAAS,CAAC,EAAE;IAC/E,OAAO,EAAE,8EAA8E;CACxF,CAAC,CAAC;AAIL,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAIH,iFAAiF;AAEjF;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAwB,EACxB,MAAc,EACd,OAAuB,EAAE;IAEzB,MAAM,MAAM,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE3C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACnC,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,GAAG,WAAW,qDAAqD,CAAC;SAC3E,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAClB,KAAK,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;SACxC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IAEvB,IAAI,KAAK;QAAE,MAAM,KAAK,CAAC;IAEvB,OAAO,CAAC,IAAI,IAAI,EAAE,CAAW,CAAC;AAChC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAwB,EACxB,MAAc,EACd,KAAsB;IAEtB,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAE7C,MAAM,QAAQ,GAAG,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IAE7D,+CAA+C;IAC/C,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ;SAC5D,IAAI,CAAC,gBAAgB,CAAC;SACtB,MAAM,CAAC,UAAU,CAAC;SAClB,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC;SAC9B,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAClB,MAAM,EAAE,CAAC;IAEZ,IAAI,UAAU,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,MAAM,IAAI,SAAS,CAAC,iBAAiB,MAAM,CAAC,OAAO,uCAAuC,CAAC,CAAC;IAC9F,CAAC;IAED,MAAM,aAAa,GAA4B;QAC7C,IAAI,EAAE,MAAM;QACZ,QAAQ,EAAE,MAAM,CAAC,OAAO;QACxB,OAAO,EAAE,MAAM,CAAC,EAAE;QAClB,UAAU,EAAE,MAAM,CAAC,KAAK;QACxB,SAAS,EAAE,MAAM,CAAC,QAAQ,IAAI,QAAQ,EAAE;KACzC,CAAC;IAEF,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,aAAa,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAEnE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,QAAQ;SAC1D,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,aAAa,CAAC;SACrB,MAAM,CAAC,IAAI,CAAC;SACZ,MAAM,EAAE,CAAC;IAEZ,IAAI,WAAW;QAAE,MAAM,WAAW,CAAC;IAEnC,wCAAwC;IACxC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACnC,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,GAAG,WAAW,qDAAqD,CAAC;SAC3E,EAAE,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,CAAC;SACrB,MAAM,EAAE,CAAC;IAEZ,IAAI,KAAK;QAAE,MAAM,KAAK,CAAC;IAEvB,OAAO,IAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAwB,EACxB,MAAc,EACd,EAAU,EACV,KAAsB;IAEtB,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAC/B,MAAM,MAAM,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAE7C,mBAAmB;IACnB,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ;SACzD,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,IAAI,CAAC;SACZ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;SACZ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAClB,MAAM,EAAE,CAAC;IAEZ,IAAI,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,QAAQ,EAAE,uCAAuC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,OAAO,GAA4B,EAAE,CAAC;IAC5C,IAAI,MAAM,CAAC,EAAE,KAAK,SAAS;QAAE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,EAAE,CAAC;IACzD,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;IAClE,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;QAAE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IAC7D,IAAI,MAAM,CAAC,QAAQ,KAAK,SAAS;QAAE,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,CAAC;IAEvE,IAAI,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,UAAU,CAClB,kBAAkB,EAClB,8EAA8E,CAC/E,CAAC;IACJ,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,OAAO,CAAC;SACf,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;SACZ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEtB,IAAI,WAAW;QAAE,MAAM,WAAW,CAAC;IAEnC,2BAA2B;IAC3B,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,QAAQ;SACnC,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,GAAG,WAAW,qDAAqD,CAAC;SAC3E,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;SACZ,MAAM,EAAE,CAAC;IAEZ,IAAI,KAAK;QAAE,MAAM,KAAK,CAAC;IAEvB,OAAO,IAAY,CAAC;AACtB,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,QAAwB,EACxB,MAAc,EACd,EAAU;IAEV,gBAAgB,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;IAE/B,mBAAmB;IACnB,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,MAAM,QAAQ;SACzD,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,CAAC,IAAI,CAAC;SACZ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;SACZ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC;SAClB,MAAM,EAAE,CAAC;IAEZ,IAAI,UAAU,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,SAAS,CAAC,QAAQ,EAAE,uCAAuC,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,QAAQ;SAC1C,IAAI,CAAC,OAAO,CAAC;SACb,MAAM,EAAE;SACR,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;SACZ,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAEtB,IAAI,WAAW;QAAE,MAAM,WAAW,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
3
|
+
import type { InventoryItem } from './inventory.js';
|
|
4
|
+
export type TastingFilter = 'user' | 'supplier' | 'both';
|
|
5
|
+
export interface SupplierTastingNotes {
|
|
6
|
+
source: 'supplier';
|
|
7
|
+
catalogId: number;
|
|
8
|
+
name: string | null;
|
|
9
|
+
processing: string | null;
|
|
10
|
+
region: string | null;
|
|
11
|
+
cupping_notes: string | null;
|
|
12
|
+
ai_tasting_notes: unknown | null;
|
|
13
|
+
ai_description: string | null;
|
|
14
|
+
}
|
|
15
|
+
export interface UserTastingNotes {
|
|
16
|
+
source: 'user';
|
|
17
|
+
inventoryId: number;
|
|
18
|
+
catalogId: number | null;
|
|
19
|
+
cupping_notes: string | null;
|
|
20
|
+
notes: string | null;
|
|
21
|
+
}
|
|
22
|
+
export interface TastingData {
|
|
23
|
+
beanId: number;
|
|
24
|
+
filter: TastingFilter;
|
|
25
|
+
supplier: SupplierTastingNotes | null;
|
|
26
|
+
user: UserTastingNotes | null;
|
|
27
|
+
}
|
|
28
|
+
export interface CuppingNotes {
|
|
29
|
+
aroma?: number;
|
|
30
|
+
body?: number;
|
|
31
|
+
acidity?: number;
|
|
32
|
+
sweetness?: number;
|
|
33
|
+
aftertaste?: number;
|
|
34
|
+
brew_method?: string;
|
|
35
|
+
notes?: string;
|
|
36
|
+
rated_at?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare const tastingFilterSchema: z.ZodEnum<{
|
|
39
|
+
user: "user";
|
|
40
|
+
supplier: "supplier";
|
|
41
|
+
both: "both";
|
|
42
|
+
}>;
|
|
43
|
+
export declare const getTastingNotesSchema: z.ZodObject<{
|
|
44
|
+
id: z.ZodNumber;
|
|
45
|
+
filter: z.ZodDefault<z.ZodEnum<{
|
|
46
|
+
user: "user";
|
|
47
|
+
supplier: "supplier";
|
|
48
|
+
both: "both";
|
|
49
|
+
}>>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type GetTastingNotesInput = z.input<typeof getTastingNotesSchema>;
|
|
52
|
+
export declare const cuppingScoreSchema: z.ZodNumber;
|
|
53
|
+
export declare const rateCoffeeSchema: z.ZodObject<{
|
|
54
|
+
aroma: z.ZodNumber;
|
|
55
|
+
body: z.ZodNumber;
|
|
56
|
+
acidity: z.ZodNumber;
|
|
57
|
+
sweetness: z.ZodNumber;
|
|
58
|
+
aftertaste: z.ZodNumber;
|
|
59
|
+
brewMethod: z.ZodOptional<z.ZodString>;
|
|
60
|
+
notes: z.ZodOptional<z.ZodString>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export type RateCoffeeInput = z.input<typeof rateCoffeeSchema>;
|
|
63
|
+
/** Validate a cupping score is an integer in [1, 5]. */
|
|
64
|
+
export declare function isValidCuppingScore(value: number): boolean;
|
|
65
|
+
/** Parse and validate a cupping score flag value (for CLI use). */
|
|
66
|
+
export declare function parseCuppingScore(raw: string, flag: string): number;
|
|
67
|
+
/**
|
|
68
|
+
* Get tasting notes for a bean by coffee_catalog ID.
|
|
69
|
+
* Combines supplier notes from coffee_catalog with user notes from green_coffee_inv.
|
|
70
|
+
*/
|
|
71
|
+
export declare function getTastingNotes(supabase: SupabaseClient, userId: string, id: number, filter?: TastingFilter): Promise<TastingData>;
|
|
72
|
+
/**
|
|
73
|
+
* Rate a coffee by storing cupping scores on the inventory item (must belong to userId).
|
|
74
|
+
*/
|
|
75
|
+
export declare function rateCoffee(supabase: SupabaseClient, userId: string, id: number, input: RateCoffeeInput): Promise<InventoryItem>;
|
|
76
|
+
//# sourceMappingURL=tasting.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasting.d.ts","sourceRoot":"","sources":["../../src/lib/tasting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAIpD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,eAAO,MAAM,mBAAmB;;;;EAAuC,CAAC;AAExE,eAAO,MAAM,qBAAqB;;;;;;;iBAGhC,CAAC;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEzE,eAAO,MAAM,kBAAkB,aAAiC,CAAC;AAEjE,eAAO,MAAM,gBAAgB;;;;;;;;iBAQ3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAI/D,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,mEAAmE;AACnE,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CASnE;AAID;;;GAGG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,MAAM,GAAE,aAAsB,GAC7B,OAAO,CAAC,WAAW,CAAC,CA+DtB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,KAAK,EAAE,eAAe,GACrB,OAAO,CAAC,aAAa,CAAC,CA6CxB"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { AuthError, PrvrsError } from './errors.js';
|
|
3
|
+
// ─── Zod schemas ──────────────────────────────────────────────────────────────
|
|
4
|
+
export const tastingFilterSchema = z.enum(['user', 'supplier', 'both']);
|
|
5
|
+
export const getTastingNotesSchema = z.object({
|
|
6
|
+
id: z.number().int().positive(),
|
|
7
|
+
filter: tastingFilterSchema.default('both'),
|
|
8
|
+
});
|
|
9
|
+
export const cuppingScoreSchema = z.number().int().min(1).max(5);
|
|
10
|
+
export const rateCoffeeSchema = z.object({
|
|
11
|
+
aroma: cuppingScoreSchema,
|
|
12
|
+
body: cuppingScoreSchema,
|
|
13
|
+
acidity: cuppingScoreSchema,
|
|
14
|
+
sweetness: cuppingScoreSchema,
|
|
15
|
+
aftertaste: cuppingScoreSchema,
|
|
16
|
+
brewMethod: z.string().optional(),
|
|
17
|
+
notes: z.string().optional(),
|
|
18
|
+
});
|
|
19
|
+
// ─── Validation helpers ───────────────────────────────────────────────────────
|
|
20
|
+
/** Validate a cupping score is an integer in [1, 5]. */
|
|
21
|
+
export function isValidCuppingScore(value) {
|
|
22
|
+
return Number.isInteger(value) && value >= 1 && value <= 5;
|
|
23
|
+
}
|
|
24
|
+
/** Parse and validate a cupping score flag value (for CLI use). */
|
|
25
|
+
export function parseCuppingScore(raw, flag) {
|
|
26
|
+
const n = parseInt(raw, 10);
|
|
27
|
+
if (isNaN(n) || !isValidCuppingScore(n)) {
|
|
28
|
+
throw new PrvrsError('INVALID_ARGUMENT', `--${flag} must be an integer between 1 and 5 (got "${raw}").`);
|
|
29
|
+
}
|
|
30
|
+
return n;
|
|
31
|
+
}
|
|
32
|
+
// ─── Pure lib functions ───────────────────────────────────────────────────────
|
|
33
|
+
/**
|
|
34
|
+
* Get tasting notes for a bean by coffee_catalog ID.
|
|
35
|
+
* Combines supplier notes from coffee_catalog with user notes from green_coffee_inv.
|
|
36
|
+
*/
|
|
37
|
+
export async function getTastingNotes(supabase, userId, id, filter = 'both') {
|
|
38
|
+
getTastingNotesSchema.parse({ id, filter });
|
|
39
|
+
const result = {
|
|
40
|
+
beanId: id,
|
|
41
|
+
filter,
|
|
42
|
+
supplier: null,
|
|
43
|
+
user: null,
|
|
44
|
+
};
|
|
45
|
+
// ── Supplier notes (from coffee_catalog) ─────────────────────────
|
|
46
|
+
if (filter === 'supplier' || filter === 'both') {
|
|
47
|
+
const { data: catalogRow, error: catalogError } = await supabase
|
|
48
|
+
.from('coffee_catalog')
|
|
49
|
+
.select('id, name, processing, region, source, cupping_notes, ai_tasting_notes, ai_description')
|
|
50
|
+
.eq('id', id)
|
|
51
|
+
.single();
|
|
52
|
+
if (catalogError && catalogError.code !== 'PGRST116') {
|
|
53
|
+
throw catalogError;
|
|
54
|
+
}
|
|
55
|
+
if (catalogRow) {
|
|
56
|
+
result.supplier = {
|
|
57
|
+
source: 'supplier',
|
|
58
|
+
catalogId: catalogRow.id,
|
|
59
|
+
name: catalogRow.name ?? null,
|
|
60
|
+
processing: catalogRow.processing ?? null,
|
|
61
|
+
region: catalogRow.region ?? null,
|
|
62
|
+
cupping_notes: catalogRow.cupping_notes ?? null,
|
|
63
|
+
ai_tasting_notes: catalogRow.ai_tasting_notes ?? null,
|
|
64
|
+
ai_description: catalogRow.ai_description ?? null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// ── User notes (from green_coffee_inv) ───────────────────────────
|
|
69
|
+
if (filter === 'user' || filter === 'both') {
|
|
70
|
+
const { data: invRows, error: invError } = await supabase
|
|
71
|
+
.from('green_coffee_inv')
|
|
72
|
+
.select('id, catalog_id, cupping_notes, notes')
|
|
73
|
+
.eq('catalog_id', id)
|
|
74
|
+
.eq('user', userId)
|
|
75
|
+
.order('id', { ascending: false })
|
|
76
|
+
.limit(1);
|
|
77
|
+
if (invError)
|
|
78
|
+
throw invError;
|
|
79
|
+
if (invRows && invRows.length > 0) {
|
|
80
|
+
const row = invRows[0];
|
|
81
|
+
result.user = {
|
|
82
|
+
source: 'user',
|
|
83
|
+
inventoryId: row.id,
|
|
84
|
+
catalogId: row.catalog_id ?? null,
|
|
85
|
+
cupping_notes: row.cupping_notes ?? null,
|
|
86
|
+
notes: row.notes ?? null,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return result;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Rate a coffee by storing cupping scores on the inventory item (must belong to userId).
|
|
94
|
+
*/
|
|
95
|
+
export async function rateCoffee(supabase, userId, id, input) {
|
|
96
|
+
const parsed = rateCoffeeSchema.parse(input);
|
|
97
|
+
// Verify ownership of the inventory item
|
|
98
|
+
const { data: existing, error: fetchError } = await supabase
|
|
99
|
+
.from('green_coffee_inv')
|
|
100
|
+
.select('id, catalog_id')
|
|
101
|
+
.eq('id', id)
|
|
102
|
+
.eq('user', userId)
|
|
103
|
+
.single();
|
|
104
|
+
if (fetchError || !existing) {
|
|
105
|
+
throw new AuthError(`Inventory item ${id} not found or does not belong to you.`);
|
|
106
|
+
}
|
|
107
|
+
const cupping = {
|
|
108
|
+
aroma: parsed.aroma,
|
|
109
|
+
body: parsed.body,
|
|
110
|
+
acidity: parsed.acidity,
|
|
111
|
+
sweetness: parsed.sweetness,
|
|
112
|
+
aftertaste: parsed.aftertaste,
|
|
113
|
+
rated_at: new Date().toISOString(),
|
|
114
|
+
};
|
|
115
|
+
if (parsed.brewMethod !== undefined)
|
|
116
|
+
cupping.brew_method = parsed.brewMethod;
|
|
117
|
+
if (parsed.notes !== undefined)
|
|
118
|
+
cupping.notes = parsed.notes;
|
|
119
|
+
const { error: updateError } = await supabase
|
|
120
|
+
.from('green_coffee_inv')
|
|
121
|
+
.update({ cupping_notes: cupping })
|
|
122
|
+
.eq('id', id)
|
|
123
|
+
.eq('user', userId);
|
|
124
|
+
if (updateError)
|
|
125
|
+
throw updateError;
|
|
126
|
+
// Re-fetch the updated row
|
|
127
|
+
const { data, error } = await supabase
|
|
128
|
+
.from('green_coffee_inv')
|
|
129
|
+
.select('id, catalog_id, cupping_notes, notes, last_updated')
|
|
130
|
+
.eq('id', id)
|
|
131
|
+
.single();
|
|
132
|
+
if (error)
|
|
133
|
+
throw error;
|
|
134
|
+
return data;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=tasting.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasting.js","sourceRoot":"","sources":["../../src/lib/tasting.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,11 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@purveyors/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "The official CLI for purveyors.io — coffee intelligence from your terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"purvey": "./dist/index.js"
|
|
8
8
|
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./dist/index.js",
|
|
11
|
+
"./catalog": "./dist/lib/catalog.js",
|
|
12
|
+
"./inventory": "./dist/lib/inventory.js",
|
|
13
|
+
"./roast": "./dist/lib/roast.js",
|
|
14
|
+
"./sales": "./dist/lib/sales.js",
|
|
15
|
+
"./tasting": "./dist/lib/tasting.js",
|
|
16
|
+
"./lib": "./dist/lib/index.js",
|
|
17
|
+
"./artisan": "./dist/lib/artisan/index.js",
|
|
18
|
+
"./ai": "./dist/lib/ai.js"
|
|
19
|
+
},
|
|
9
20
|
"files": [
|
|
10
21
|
"dist",
|
|
11
22
|
"README.md",
|
|
@@ -39,10 +50,12 @@
|
|
|
39
50
|
"node": ">=20"
|
|
40
51
|
},
|
|
41
52
|
"dependencies": {
|
|
53
|
+
"@clack/prompts": "^1.1.0",
|
|
42
54
|
"@supabase/supabase-js": "^2.49.1",
|
|
43
55
|
"chalk": "^5.4.1",
|
|
44
56
|
"commander": "^13.1.0",
|
|
45
|
-
"ora": "^8.2.0"
|
|
57
|
+
"ora": "^8.2.0",
|
|
58
|
+
"zod": "^4.3.6"
|
|
46
59
|
},
|
|
47
60
|
"devDependencies": {
|
|
48
61
|
"@eslint/eslintrc": "^3.3.0",
|