hm-invoicesv1StatusProgress.tsx3 matches
64{getStatusIcon(status, index)}
65</span>
66<span className="text-xs mt-1 capitalize">
67{status}
68</span>
76<div className="text-sm opacity-70">
77Step {currentIndex + 1} of {statuses.length}:
78<span className="font-medium capitalize ml-1">{currentStatus}</span>
79</div>
80{interactive && currentIndex < statuses.length - 1 && (
124max="100"
125></progress>
126<span className="text-xs capitalize font-medium">
127{currentStatus}
128</span>
hm-invoicesv1App.tsx12 matches
23import React, { useState, useEffect } from "https://esm.sh/react@18.2.0";
4import { InvoiceWithVendor, Vendor, InvoiceFilters, InvoiceStats, InvoiceStatus, ApiResponse } from "../../shared/types.ts";
5import InvoiceList from "./InvoiceList.tsx";
6import InvoiceForm from "./InvoiceForm.tsx";
18const [activeTab, setActiveTab] = useState<'dashboard' | 'invoices' | 'add'>('dashboard');
1920// Fetch data from API
21useEffect(() => {
22fetchData();
40
41const [invoicesResponse, vendorsResponse] = await Promise.all([
42fetch(`/api/invoices?${queryParams.toString()}`),
43fetch('/api/vendors')
44]);
4548}
4950const invoicesData: ApiResponse<InvoiceWithVendor[]> = await invoicesResponse.json();
51const vendorsData: ApiResponse<Vendor[]> = await vendorsResponse.json();
5253if (!invoicesData.success || !vendorsData.success) {
69try {
70setStatsLoading(true);
71const response = await fetch('/api/invoices/stats');
72
73if (!response.ok) {
75}
7677const data: ApiResponse<InvoiceStats> = await response.json();
78
79if (!data.success) {
92const handleAddInvoice = async (invoiceData: any) => {
93try {
94const response = await fetch('/api/invoices', {
95method: 'POST',
96headers: {
104}
105106const data: ApiResponse<InvoiceWithVendor> = await response.json();
107
108if (!data.success) {
123const handleStatusUpdate = async (invoiceId: number, newStatus: InvoiceStatus) => {
124try {
125const response = await fetch(`/api/invoices/${invoiceId}`, {
126method: 'PUT',
127headers: {
135}
136137const data: ApiResponse<InvoiceWithVendor> = await response.json();
138
139if (!data.success) {
hm-invoicesv1InvoiceList.tsx1 match
217</div>
218</td>
219<td className="capitalize">{invoice.expected_day}</td>
220<td>{new Date(invoice.invoice_date).toLocaleDateString()}</td>
221<td>
hm-invoicesv1VAL-TOWN-RULES.md10 matches
13- Generate code in TypeScript or TSX
14- Add appropriate TypeScript types and interfaces for all data structures
15- Prefer official SDKs or libraries than writing API calls directly
16- Ask the user to supply API or library documentation if you are at all unsure about it
17- **Never bake in secrets into the code** - always use environment variables
18- Include comments explaining complex logic (avoid commenting obvious operations)
23### 1. HTTP Trigger
2425- Create web APIs and endpoints
26- Handle HTTP requests and responses
27- Example structure:
133However, it's *extremely importing* to note that `parseProject` and other Standard Library utilities ONLY RUN ON THE SERVER.
134If you need access to this data on the client, run it in the server and pass it to the client by splicing it into the HTML page
135or by making an API request for it.
136137## Val Town Platform Specifics
141- **AI Image:** To inline generate an AI image use: `<img src="https://maxm-imggenurl.web.val.run/the-description-of-your-image" />`
142- **Storage:** DO NOT use the Deno KV module for storage
143- **Browser APIs:** DO NOT use the `alert()`, `prompt()`, or `confirm()` methods
144- **Weather Data:** Use open-meteo for weather data (doesn't require API keys) unless otherwise specified
145- **View Source:** Add a view source link by importing & using `import.meta.url.replace("ems.sh", "val.town)"` (or passing this data to the client) and include `target="_top"` attribute
146- **Error Debugging:** Add `<script src="https://esm.town/v/std/catch"></script>` to HTML to capture client-side errors
147- **Error Handling:** Only use try...catch when there's a clear local resolution; Avoid catches that merely log or return 500s. Let errors bubble up with full context
148- **Environment Variables:** Use `Deno.env.get('keyname')` when you need to, but generally prefer APIs that don't require keys
149- **Imports:** Use `https://esm.sh` for npm and Deno dependencies to ensure compatibility on server and browser
150- **Storage Strategy:** Only use backend storage if explicitly required; prefer simple static client-side sites
371### Backend (Hono) Best Practices
372373- Hono is the recommended API framework
374- Main entry point should be `main.tsx`
375- **Database Setup:** Use the admin utilities to handle table creation and seeding. Backend should focus on CRUD operations only
397});
398```
399- Create RESTful API routes for CRUD operations
400- Always include this snippet at the top-level Hono app to re-throwing errors to see full stack traces:
401```ts
437- For files in the project, use `readFile` helpers
4384395. **API Design:**
440- `fetch` handler is the entry point for HTTP vals
441- Run the Hono app with `export default app.fetch // This is the entry point for HTTP vals`
hm-invoicesv1main.tsx64 matches
25UpdateInvoiceRequest,
26InvoiceFilters,
27ApiResponse
28} from "./shared/types.ts";
297071// ============================================================================
72// INVOICE API ENDPOINTS
73// ============================================================================
7475// GET /api/invoices/stats - Dashboard statistics (must come before /:id route)
76app.get("/api/invoices/stats", async (c) => {
77try {
78const stats = await getInvoiceStats();
79
80const response: ApiResponse<typeof stats> = {
81success: true,
82data: stats
85return c.json(response);
86} catch (error) {
87const response: ApiResponse<null> = {
88success: false,
89error: error.message
93});
9495// GET /api/invoices - List all invoices with filtering
96app.get("/api/invoices", async (c) => {
97try {
98const url = new URL(c.req.url);
120const invoices = await getAllInvoices(filters);
121
122const response: ApiResponse<typeof invoices> = {
123success: true,
124data: invoices
127return c.json(response);
128} catch (error) {
129const response: ApiResponse<null> = {
130success: false,
131error: error.message
135});
136137// GET /api/invoices/:id - Get specific invoice
138app.get("/api/invoices/:id", async (c) => {
139try {
140const id = parseInt(c.req.param('id'));
141if (isNaN(id)) {
142const response: ApiResponse<null> = {
143success: false,
144error: "Invalid invoice ID"
149const invoice = await getInvoiceById(id);
150if (!invoice) {
151const response: ApiResponse<null> = {
152success: false,
153error: "Invoice not found"
156}
157
158const response: ApiResponse<typeof invoice> = {
159success: true,
160data: invoice
163return c.json(response);
164} catch (error) {
165const response: ApiResponse<null> = {
166success: false,
167error: error.message
171});
172173// POST /api/invoices - Create new invoice
174app.post("/api/invoices", async (c) => {
175try {
176const body = await c.req.json();
180
181if (!vendor_id || !type || !expected_day || !invoice_date) {
182const response: ApiResponse<null> = {
183success: false,
184error: "Missing required fields: vendor_id, type, expected_day, invoice_date"
189// Validate field values
190if (!isValidType(type)) {
191const response: ApiResponse<null> = {
192success: false,
193error: `Invalid type. Must be one of: ${CONFIG.INVOICE_TYPES.join(', ')}`
197
198if (!isValidDay(expected_day)) {
199const response: ApiResponse<null> = {
200success: false,
201error: `Invalid expected_day. Must be one of: ${CONFIG.DAYS_OF_WEEK.join(', ')}`
207const vendor = await getVendorById(vendor_id);
208if (!vendor) {
209const response: ApiResponse<null> = {
210success: false,
211error: "Vendor not found"
217const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
218if (!dateRegex.test(invoice_date)) {
219const response: ApiResponse<null> = {
220success: false,
221error: "Invalid date format. Use YYYY-MM-DD"
234const newInvoice = await getInvoiceById(invoiceId);
235
236const response: ApiResponse<typeof newInvoice> = {
237success: true,
238data: newInvoice
241return c.json(response, 201);
242} catch (error) {
243const response: ApiResponse<null> = {
244success: false,
245error: error.message
249});
250251// PUT /api/invoices/:id - Update invoice (mainly status changes)
252app.put("/api/invoices/:id", async (c) => {
253try {
254const id = parseInt(c.req.param('id'));
255if (isNaN(id)) {
256const response: ApiResponse<null> = {
257success: false,
258error: "Invalid invoice ID"
264const existingInvoice = await getInvoiceById(id);
265if (!existingInvoice) {
266const response: ApiResponse<null> = {
267success: false,
268error: "Invoice not found"
278const vendor = await getVendorById(body.vendor_id);
279if (!vendor) {
280const response: ApiResponse<null> = {
281success: false,
282error: "Vendor not found"
289if (body.type !== undefined) {
290if (!isValidType(body.type)) {
291const response: ApiResponse<null> = {
292success: false,
293error: `Invalid type. Must be one of: ${CONFIG.INVOICE_TYPES.join(', ')}`
300if (body.expected_day !== undefined) {
301if (!isValidDay(body.expected_day)) {
302const response: ApiResponse<null> = {
303success: false,
304error: `Invalid expected_day. Must be one of: ${CONFIG.DAYS_OF_WEEK.join(', ')}`
311if (body.status !== undefined) {
312if (!isValidStatus(body.status)) {
313const response: ApiResponse<null> = {
314success: false,
315error: `Invalid status. Must be one of: ${CONFIG.INVOICE_STATUSES.join(', ')}`
323const dateRegex = /^\d{4}-\d{2}-\d{2}$/;
324if (!dateRegex.test(body.invoice_date)) {
325const response: ApiResponse<null> = {
326success: false,
327error: "Invalid date format. Use YYYY-MM-DD"
334// Check if there are any updates
335if (Object.keys(updates).length === 0) {
336const response: ApiResponse<null> = {
337success: false,
338error: "No valid fields to update"
343const success = await updateInvoice(id, updates);
344if (!success) {
345const response: ApiResponse<null> = {
346success: false,
347error: "Failed to update invoice"
352const updatedInvoice = await getInvoiceById(id);
353
354const response: ApiResponse<typeof updatedInvoice> = {
355success: true,
356data: updatedInvoice
359return c.json(response);
360} catch (error) {
361const response: ApiResponse<null> = {
362success: false,
363error: error.message
367});
368369// DELETE /api/invoices/:id - Delete invoice
370app.delete("/api/invoices/:id", async (c) => {
371try {
372const id = parseInt(c.req.param('id'));
373if (isNaN(id)) {
374const response: ApiResponse<null> = {
375success: false,
376error: "Invalid invoice ID"
382const existingInvoice = await getInvoiceById(id);
383if (!existingInvoice) {
384const response: ApiResponse<null> = {
385success: false,
386error: "Invoice not found"
391const success = await deleteInvoice(id);
392if (!success) {
393const response: ApiResponse<null> = {
394success: false,
395error: "Failed to delete invoice"
398}
399
400const response: ApiResponse<{ id: number }> = {
401success: true,
402data: { id }
405return c.json(response);
406} catch (error) {
407const response: ApiResponse<null> = {
408success: false,
409error: error.message
414415// ============================================================================
416// VENDOR API ENDPOINTS
417// ============================================================================
418419// GET /api/vendors - List all vendors
420app.get("/api/vendors", async (c) => {
421try {
422const vendors = await getAllVendors();
423
424const response: ApiResponse<typeof vendors> = {
425success: true,
426data: vendors
429return c.json(response);
430} catch (error) {
431const response: ApiResponse<null> = {
432success: false,
433error: error.message
437});
438439// GET /api/vendors/:id - Get specific vendor
440app.get("/api/vendors/:id", async (c) => {
441try {
442const id = parseInt(c.req.param('id'));
443if (isNaN(id)) {
444const response: ApiResponse<null> = {
445success: false,
446error: "Invalid vendor ID"
451const vendor = await getVendorById(id);
452if (!vendor) {
453const response: ApiResponse<null> = {
454success: false,
455error: "Vendor not found"
458}
459
460const response: ApiResponse<typeof vendor> = {
461success: true,
462data: vendor
465return c.json(response);
466} catch (error) {
467const response: ApiResponse<null> = {
468success: false,
469error: error.message
477// ============================================================================
478479// POST /api/seed - Seed vendors (for admin use)
480app.post("/api/seed", async (c) => {
481try {
482const count = await seedVendors();
483const newCounts = await getTableCounts();
484
485const response: ApiResponse<{ seededCount: number; totalCounts: typeof newCounts }> = {
486success: true,
487data: {
493return c.json(response);
494} catch (error) {
495const response: ApiResponse<null> = {
496success: false,
497error: error.message
501});
502503// GET /api/test - Test database functionality
504app.get("/api/test", async (c) => {
505try {
506const counts = await getTableCounts();
507const vendors = await getAllVendors();
508
509const response: ApiResponse<{ counts: typeof counts; vendors: typeof vendors; totalVendors: number }> = {
510success: true,
511data: {
518return c.json(response);
519} catch (error) {
520const response: ApiResponse<null> = {
521success: false,
522error: error.message
hm-invoicesv1InvoiceForm.tsx2 matches
67onChange={() => setType(invoiceType)}
68/>
69<span className="label-text ml-2 capitalize">{invoiceType}</span>
70</label>
71))}
85>
86{CONFIG.DAYS_OF_WEEK.map(day => (
87<option key={day} value={day} className="capitalize">{day}</option>
88))}
89</select>
hm-invoicesv1vt-deploy.mdc2 matches
4alwaysApply: false
5---
6# command to use api key to push to val town
78VAL_TOWN_API_KEY=vtwn_J5YqHdcJA2g99RsZs7UXNFcCXGY vt push
910# current project HTTP endpoints
hm-invoicesv1db-viewer.tsx1 match
233</span>
234</td>
235<td className="capitalize">{invoice.expected_day}</td>
236<td className="font-mono">{invoice.invoice_date}</td>
237<td>
api_ianmenethil_comapiServerGenerator.ts27 matches
1#!/usr/bin/env -S deno run --allow-read --allow-write --allow-net
2/**
3* api-server-generator.ts โ Template generator for creating new API servers from OpenAPI specs.
4* Generates controller stubs, registry configuration, and basic server structure.
5*/
11interface ServerTemplate {
12name: string;
13openApiSpecPath: string;
14outputDirectory: string;
15controllers: ControllerInfo[];
31}
3233export class APIServerGenerator {
34/**
35* Generate a new API server from OpenAPI specification
36*/
37static async generateServer(template: ServerTemplate): Promise<void> {
38console.log(`๐ Generating API server: ${template.name}`);
39console.log(`๐ OpenAPI Spec: ${template.openApiSpecPath}`);
40console.log(`๐ Output Directory: ${template.outputDirectory}`);
4142// 1. Parse OpenAPI spec
43const spec = await this.parseOpenAPISpec(template.openApiSpecPath);
4445// 2. Extract controller information
61await this.copyBaseFiles(template.outputDirectory);
6263console.log(`โ API server generated successfully in ${template.outputDirectory}`);
64console.log(`๐ Next steps:`);
65console.log(` 1. cd ${template.outputDirectory}`);
7071/**
72* Parse OpenAPI specification file
73*/
74private static async parseOpenAPISpec(specPath: string): Promise<any> {
75try {
76const content = await Deno.readTextFile(specPath);
85} catch (error) {
86const errorMessage = error instanceof Error ? error.message : String(error);
87throw new Error(`Failed to parse OpenAPI spec: ${errorMessage}`);
88}
89}
9091/**
92* Extract controller information from OpenAPI spec
93*/
94private static extractControllers(spec: any): ControllerInfo[] {
9697if (!spec.paths) {
98console.warn("No paths found in OpenAPI spec");
99return [];
100}
138139/**
140* Create directory structure for new API server
141*/
142private static async createDirectoryStructure(outputDir: string): Promise<void> {
145"src/core",
146"src/middleware",
147"src/openapi",
148"src/utils",
149"src/types",
150"src/external-apis",
151"src/tests",
152];
362): string {
363return `/**
364* main.tsx โ Generated API server entry point
365* Server: ${template.name}
366*/
369import { cors } from 'hono/cors';
370import { logger } from 'hono/logger';
371import { OpenAPIRouteGenerator } from './src/openapi/route.generator.ts';
372373const app = new Hono();
386});
387388// Initialize OpenAPI routes
389try {
390\tconst routeGenerator = new OpenAPIRouteGenerator(app);
391\tawait routeGenerator.loadAndGenerateRoutes('./src/openapi');
392\tconsole.log('โ Routes loaded successfully');
393} catch (error) {
417418console.log(\`๐ Starting \${' ${template.name}'} server on port \${port}\`);
419console.log(\`๐ API Documentation: http://localhost:\${port}/docs\`);
420421Deno.serve({ port }, app.fetch);
430if (args.length < 3) {
431console.log(
432"Usage: deno run --allow-read --allow-write api-server-generator.ts <name> <openapi-spec> <output-dir>",
433);
434console.log(
435"Example: deno run --allow-read --allow-write api-server-generator.ts MyAPI ./openapi.yaml ./my-api-server",
436);
437Deno.exit(1);
441442try {
443await APIServerGenerator.generateServer({
444name,
445openApiSpecPath: specPath,
446outputDirectory: outputDir,
447controllers: [], // Will be extracted from spec
1/**
2* token.encryption.ts โ Token encryption/decryption utilities for OAuth tokens.
3* Uses Web Crypto API for AES-GCM encryption to secure sensitive token data.
4*/
5