15await runMigrations();
16
17// API routes
18app.route("/api/jobs", jobsRouter);
19app.route("/api/chat", chatRouter);
20
21// Serve static files
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { useState, useEffect, useRef } from "https://esm.sh/react@18.2.0";
3import type { ChatMessage, CreateMessageRequest, ApiResponse } from "../../shared/types.ts";
4
5export default function ChatRoom() {
19 try {
20 setLoading(true);
21 const response = await fetch('/api/chat/messages');
22 const result: ApiResponse<ChatMessage[]> = await response.json();
23
24 if (result.success && result.data) {
51 };
52
53 const response = await fetch('/api/chat/messages', {
54 method: 'POST',
55 headers: {
59 });
60
61 const result: ApiResponse<ChatMessage> = await response.json();
62
63 if (result.success && result.data) {
1/** @jsxImportSource https://esm.sh/react@18.2.0 */
2import { useState } from "https://esm.sh/react@18.2.0";
3import type { Job, CreateJobRequest, ApiResponse } from "../../shared/types.ts";
4
5interface JobFormProps {
25
26 try {
27 const response = await fetch('/api/jobs', {
28 method: 'POST',
29 headers: {
33 });
34
35 const result: ApiResponse<Job> = await response.json();
36
37 if (result.success && result.data) {
2import { useState, useEffect } from "https://esm.sh/react@18.2.0";
3import JobForm from "./JobForm.tsx";
4import type { Job, ApiResponse } from "../../shared/types.ts";
5
6export default function JobBoard() {
13 try {
14 setLoading(true);
15 const response = await fetch('/api/jobs');
16 const result: ApiResponse<Job[]> = await response.json();
17
18 if (result.success && result.data) {
40
41 try {
42 const response = await fetch(`/api/jobs/${jobId}`, {
43 method: 'DELETE'
44 });
45
46 const result: ApiResponse<{ id: number }> = await response.json();
47
48 if (result.success) {
19await runMigrations();
20
21// API routes
22app.route("/api/products", productsRoutes);
23app.route("/api/cart", cartRoutes);
24app.route("/api/orders", ordersRoutes);
25app.route("/api/chat", chatRoutes);
26
27// Serve static files
43 const loadMessages = async () => {
44 try {
45 const response = await fetch(`/api/chat/messages/${roomId}`);
46 const result = await response.json();
47
59 }
60
61 const eventSource = new EventSource(`/api/chat/stream/${roomId}`);
62 eventSourceRef.current = eventSource;
63
105 setSending(true);
106 try {
107 const response = await fetch('/api/chat/messages', {
108 method: 'POST',
109 headers: { 'Content-Type': 'application/json' },
1import { Hono } from "https://esm.sh/hono@3.11.7";
2import { getRecentMessages, createMessage } from "../database/queries.ts";
3import type { CreateMessageRequest, ApiResponse } from "../../shared/types.ts";
4
5const chat = new Hono();
12 success: true,
13 data: messages
14 } as ApiResponse<typeof messages>);
15 } catch (error) {
16 return c.json({
17 success: false,
18 error: "Failed to fetch messages"
19 } as ApiResponse<never>, 500);
20 }
21});
31 success: false,
32 error: "Username and message are required"
33 } as ApiResponse<never>, 400);
34 }
35
42 success: false,
43 error: "Username must be 1-50 characters"
44 } as ApiResponse<never>, 400);
45 }
46
49 success: false,
50 error: "Message must be 1-500 characters"
51 } as ApiResponse<never>, 400);
52 }
53
56 success: true,
57 data: newMessage
58 } as ApiResponse<typeof newMessage>, 201);
59 } catch (error) {
60 return c.json({
61 success: false,
62 error: "Failed to send message"
63 } as ApiResponse<never>, 500);
64 }
65});
1import { Hono } from "https://esm.sh/hono@3.11.7";
2import { getAllJobs, createJob, deleteJob } from "../database/queries.ts";
3import type { CreateJobRequest, ApiResponse } from "../../shared/types.ts";
4
5const jobs = new Hono();
12 success: true,
13 data: jobList
14 } as ApiResponse<typeof jobList>);
15 } catch (error) {
16 return c.json({
17 success: false,
18 error: "Failed to fetch jobs"
19 } as ApiResponse<never>, 500);
20 }
21});
32 success: false,
33 error: "All fields are required"
34 } as ApiResponse<never>, 400);
35 }
36
41 success: false,
42 error: "Invalid email format"
43 } as ApiResponse<never>, 400);
44 }
45
48 success: true,
49 data: newJob
50 } as ApiResponse<typeof newJob>, 201);
51 } catch (error) {
52 return c.json({
53 success: false,
54 error: "Failed to create job"
55 } as ApiResponse<never>, 500);
56 }
57});
65 success: false,
66 error: "Invalid job ID"
67 } as ApiResponse<never>, 400);
68 }
69
73 success: false,
74 error: "Job not found"
75 } as ApiResponse<never>, 404);
76 }
77
79 success: true,
80 data: { id }
81 } as ApiResponse<{ id: number }>);
82 } catch (error) {
83 return c.json({
84 success: false,
85 error: "Failed to delete job"
86 } as ApiResponse<never>, 500);
87 }
88});
33}
34
35export interface ApiResponse<T> {
36 success: boolean;
37 data?: T;
17โ โ โโโ queries.ts # Database query functions
18โ โโโ routes/
19โ โ โโโ jobs.ts # Job posting API routes
20โ โ โโโ chat.ts # Chat API routes
21โ โโโ index.ts # Main Hono server
22โโโ frontend/
32```
33
34## API Endpoints
35
36### Jobs
37- `GET /api/jobs` - Get all job postings
38- `POST /api/jobs` - Create a new job posting
39- `DELETE /api/jobs/:id` - Delete a job posting
40
41### Chat
42- `GET /api/chat/messages` - Get recent chat messages
43- `POST /api/chat/messages` - Send a new chat message
44
45## Database Schema
63## Getting Started
64
65This app runs on Val Town. The backend serves both the API and the frontend files.
66
67Visit the HTTP endpoint to access the application.