17Townie is fully open-source and itself runs on Val Town. Pull requests welcome!
18
19To get Townie running in your Val Town account, click the **Remix** button and then add your ANTHROPIC_API_KEY. You can leave all the other environment variables blank.
20
21Authentication in Townie is handled via Val Town Oauth. However, we have not yet opened up our OAuth to anyone else, which currently makes it very awkward to use your own Townie. Here is a temporary workaround:
8import { OpenAI } from "https://esm.town/v/std/openai";
9import { Octokit } from "https://esm.sh/@octokit/rest@20.0.2";
10import { WebClient } from "https://esm.sh/@slack/web-api@7.0.2";
11
12// Dedupe events
13// https://api.slack.com/apis/events-api#responding-to-events
14const processedEvents = new Set<string>();
15
129async function isBugReportLLM(text: string): Promise<boolean> {
130 try {
131 // Check if OpenAI API key is available
132 if (!Deno.env.get("OPENAI_API_KEY")) {
133 console.warn("OpenAI API key not found - bug detection disabled");
134 return false;
135 }
225 });
226
227 // Filter out pull requests (GitHub API includes PRs in issues endpoint)
228 const actualIssues = response.data.filter(issue => !issue.html_url.includes("/pull/"));
229
249async function findRelatedIssues(slackMessage: string, issues: any[]): Promise<any[]> {
250 try {
251 // Check if OpenAI API key is available
252 if (!Deno.env.get("OPENAI_API_KEY")) {
253 return [];
254 }
12app.get("/frontend/**/*", c => serveFile(c.req.path, import.meta.url));
13
14// Add your API routes here
15// app.get("/api/data", c => c.json({ hello: "world" }));
16
17// Unwrap and rethrow Hono errors as the original error
21 setLoading(true);
22 const [destinationsRes, userRes] = await Promise.all([
23 fetch('/api/goals/travel-destinations'),
24 fetch('/api/user/profile')
25 ]);
26
64
65 try {
66 const response = await fetch('/api/goals', {
67 method: 'POST',
68 headers: {
26 try {
27 setLoading(true);
28 const response = await fetch('/api/goals');
29 const data = await response.json();
30 if (data.success) {
68
69 try {
70 const response = await fetch('/api/goals', {
71 method: 'POST',
72 headers: {
123
124 try {
125 const response = await fetch(`/api/goals/${goalId}/progress`, {
126 method: 'POST',
127 headers: {
165
166 try {
167 const response = await fetch(`/api/goals/${goalId}`, {
168 method: 'DELETE',
169 });
22 const fetchRecentExpenses = async () => {
23 try {
24 const response = await fetch('/api/expenses?limit=10');
25 const data = await response.json();
26 if (data.success) {
60
61 try {
62 const response = await fetch('/api/expenses', {
63 method: 'POST',
64 headers: {
114
115 try {
116 const response = await fetch(`/api/expenses/${expenseId}`, {
117 method: 'DELETE',
118 });
24
25 const [monthlyReportRes, expensesRes, goalsRes, insightsRes, userRes] = await Promise.all([
26 fetch('/api/analytics/monthly-report'),
27 fetch('/api/expenses?limit=5'),
28 fetch('/api/goals'),
29 fetch('/api/analytics/insights'),
30 fetch('/api/user/profile')
31 ]);
32
11// Unwrap Hono errors to see original error details
12app.onError((err, c) => {
13 console.error("API Error:", err);
14 throw err;
15});
18await runMigrations();
19
20// API Routes
21app.route("/api/expenses", expenses);
22app.route("/api/goals", goals);
23app.route("/api/analytics", analytics);
24app.route("/api/user", user);
25
26// Health check
27app.get("/api/health", (c) => {
28 return c.json({
29 success: true,
30 message: "Expense Tracker API is running!",
31 timestamp: new Date().toISOString()
32 });
45 const initialDataScript = `<script>
46 window.__INITIAL_DATA__ = {
47 apiBase: '',
48 appName: 'Smart Expense Tracker',
49 version: '1.0.0'
68 <div class="mt-4 p-4 bg-blue-50 rounded">
69 <p class="text-sm text-blue-800">
70 <strong>API Status:</strong> โ
Backend is running<br>
71 <strong>Database:</strong> โ
Initialized<br>
72 <strong>Frontend:</strong> โ Not found
82// Catch-all for SPA routing
83app.get("*", async (c) => {
84 // For SPA, serve index.html for all non-API routes
85 if (!c.req.path.startsWith("/api/")) {
86 return app.fetch(new Request(new URL("/", c.req.url)));
87 }
89 return c.json({
90 success: false,
91 error: "API endpoint not found"
92 }, 404);
93});
40โ โ โโโ goals.ts # Goal management
41โ โ โโโ analytics.ts # Data analysis endpoints
42โ โโโ index.ts # Main API server
43โโโ frontend/
44โ โโโ components/
22```
23โโโ backend/
24โ โโโ index.ts # Hono API server with auth endpoints
25โโโ frontend/
26โ โโโ index.html # Main HTML template
34```
35
36## API Endpoints
37
38- `POST /api/login` - Authenticate user
39- `POST /api/register` - Create new user account
40- `POST /api/logout` - End user session
41- `GET /api/auth` - Check authentication status
42
43## Database Schema