339
340// ============================================================
341// TELEGRAM API INTERACTION
342// ============================================================
343
344/**
345 * Send a message using the Telegram API
346 */
347async function sendTelegramMessage(chatId, text) {
349 const botToken = process.env.TELEGRAM_BOT_TOKEN;
350
351 const response = await fetch(`https://api.telegram.org/bot${botToken}/sendMessage`, {
352 method: "POST",
353 headers: { "Content-Type": "application/json" },
379 const botToken = process.env.TELEGRAM_BOT_TOKEN;
380
381 const response = await fetch(`https://api.telegram.org/bot${botToken}/setWebhook`, {
382 method: "POST",
383 headers: { "Content-Type": "application/json" },
403 const botToken = process.env.TELEGRAM_BOT_TOKEN;
404
405 const response = await fetch(`https://api.telegram.org/bot${botToken}/getWebhookInfo`);
406 const result = await response.json();
407 return result;
4// Inject the search term in to the URL
5function getSearchURL(term: string) {
6 return `https://legislature.maine.gov/mrs-search/api/billtext?term=${term}&title=&legislature=132&lmSponsorPrimary=false&reqAmendExists=false&reqAmendAdoptH=false&reqAmendAdoptS=false&reqChapterExists=false&reqFNRequired=false&reqEmergency=false&reqGovernor=false&reqBond=false&reqMandate=false&reqPublicLand=false&showExtraParameters=false&mustHave=&mustNotHave=&offset=0&pageSize=12&sortByScore=false&showBillText=false&sortAscending=false&excludeOrders=false`;
7}
8
19
20 // Used to get the list of post id's for the discussion.
21 const discussionRes = await fetch(`${server}/api/discussions/${discussionId}`);
22 const discussionResJson = await discussionRes.json();
23
31
32 await Promise.all(chunks.map(async (c: string[]) => {
33 const postRes = await fetch(`${server}/api/posts?filter[id]=${c.join(",")}`);
34 const postJson = await postRes.json();
35
29 const apps = await getAllApps();
30
31 // Mask API keys for security
32 const maskedApps = apps.map(app => ({
33 ...app,
34 api_key: app.api_key ? app.api_key.slice(0, 8) + "..." + app.api_key.slice(-4) : "N/A"
35 }));
36
183 <div class="mt-4 pt-3 border-t border-gray-200">
184 <div class="text-sm text-gray-600">
185 <div><span class="font-medium">API Key:</span> {{ app.api_key }}</div>
186 <div><span class="font-medium">Created:</span> {{ formatDate(app.created_at) }}</div>
187 </div>
331
332 <script>
333 // Initialize Vue app with Composition API
334 const { createApp, ref, computed, onMounted, watch } = Vue;
335
20### Key Vue Features Used
21
22- **Composition API**: Modern Vue 3 approach with `setup()` function
23- **Template syntax**: `v-for`, `v-if`, `v-model`, etc.
24- **Component props and emits**: Clean component API
25- **Reactive state**: With `ref()` and `computed()`
26- **Event handling**: `@click`, `@submit.prevent`, etc.
472. Vue initializes with the app data received from the server
483. The UI reactively updates as the user interacts with it
494. API calls still go to the existing backend endpoints
50
51## Extending the UI
54 <div class="mt-4 pt-3 border-t border-gray-200">
55 <div class="text-sm text-gray-600">
56 <div><span class="font-medium">API Key:</span> {{ app.api_key }}</div>
57 <div><span class="font-medium">Created:</span> {{ formatDate(app.created_at) }}</div>
58 </div>
13## Authentication
14
15Login to your SQLite Explorer with [password authentication](https://www.val.town/v/pomdtr/password_auth) with your [Val Town API Token](https://www.val.town/settings/api) as the password.
16
17## Todos / Plans
27 <head>
28 <title>SQLite Explorer</title>
29 <link rel="preconnect" href="https://fonts.googleapis.com" />
30
31 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
32 <link
33 href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@300..700&family=Source+Sans+3:ital,wght@0,200..900;1,200..900&display=swap"
34 rel="stylesheet"
35 />
11
12// Utility exports
13export * from "./utils/apiKey.ts";
14export * from "./utils/auth.ts";
15export * from "./utils/validation.ts";
18export * from "./email/notifications.ts";
19
20// API endpoints
21export { default as createApp } from "./api/createApp.http.ts";
22export { default as getApps } from "./api/getApps.http.ts";
23export { default as deleteApp } from "./api/deleteApp.http.ts";
24export { default as submitFeedback } from "./api/submitFeedback.http.ts";
25export { default as getFeedback } from "./api/getFeedback.http.ts";
26export { default as exportFeedback } from "./api/exportFeedback.http.ts";
27
28// Dashboard endpoints
10 id?: number;
11 name: string;
12 api_key: string;
13 description?: string;
14 created_at?: string;
40export async function createApp(app: Omit<App, 'id' | 'created_at'>): Promise<App> {
41 const result = await sqlite.execute(
42 `INSERT INTO apps (name, api_key, description)
43 VALUES (?, ?, ?)
44 RETURNING *`,
45 [app.name, app.api_key, app.description || null]
46 );
47
50
51/**
52 * Get an app by its API key
53 */
54export async function getAppByApiKey(apiKey: string): Promise<App | null> {
55 const result = await sqlite.execute(
56 `SELECT * FROM apps WHERE api_key = ?`,
57 [apiKey]
58 );
59
70 const apps = result.rows.map(row => {
71 // If row is already an object with named properties, return it
72 if (typeof row === 'object' && row !== null && 'id' in row && 'name' in row && 'api_key' in row) {
73 return row as App;
74 }
79 id: row[0],
80 name: row[1],
81 api_key: row[2],
82 description: row[3],
83 created_at: row[4]
90 id: typeof safeRow.id !== 'undefined' ? Number(safeRow.id) : undefined,
91 name: String(safeRow.name || ''),
92 api_key: String(safeRow.api_key || ''),
93 description: safeRow.description ? String(safeRow.description) : undefined,
94 created_at: safeRow.created_at ? String(safeRow.created_at) : undefined