glimpse2-runbook-test07-save.md14 matches
10Before implementing, identify which pattern to use:
1112- Data API Pattern (/api/_, /views/_): Controllers return data objects, routes handle HTTP
13- Webhook Handler Pattern (/tasks/\*): Controllers handle HTTP requests/responses directly
142728- Controller function (handles HTTP directly)
29- Service function (if new Notion API calls needed)
30- Route definition
31- Documentation updates
35### Step 1: Extend the Service Layer (if needed)
3637If new external API calls are required:
38391. **Location:** /backend/services/notion.service.ts
401. **Pattern:** Add pure API functions that return {success, data/error, timestamp} format
411. **Example:** updatePageUrl(pageId, url) function
421. **Responsibilities:** Only handle external API calls, no business logic
4344### Step 2: Create the Controller
6869- Add endpoint to "Webhooks (Notion Integration)" section
70- Follow format: `POST /tasks/url` - Description (requires `X-API-KEY` header)
7172**Route README** `(/backend/routes/tasks/README.md)`:
9192- All `/tasks/*` routes automatically use webhook authentication
93- Requires X-API-KEY header with NOTION_WEBHOOK_SECRET value
94- No additional auth setup needed in route
951691. Test with missing page ID (expect 400)
1701. Test with missing host header (expect 400)
1711. Test with Notion API failures (expect 500)
172173### Add Test Endpoint (optional)
174175Create a test endpoint that simulates the logic without calling external APIs:
176177```
178app.post("/test-url", async (c) => {
179// Same logic but return test response instead of calling Notion API
180});
181```
183## Common Pitfalls to Avoid
1841851. Don't mix patterns: /tasks/\_ routes should use webhook handler pattern, not data API pattern
1861. Don't forget authentication: All /tasks/\_ routes are automatically protected
1871. Don't skip logging: Webhook endpoints need comprehensive logging for debugging
1881. Don't forget documentation: Update all relevant README files
1891. Don't hardcode hosts: Always extract host from request headers
1901. Don't forget error cases: Handle missing data, API failures, etc.
1911. Don't skip the catch-all update: Add new endpoints to available endpoints list
192211- The webhook authentication middleware handles all security automatically
212- Focus on business logic and proper HTTP handling in the controller
213- Keep service functions pure (no business logic, just API calls)
214- Document the architectural decision when mixing patterns
215- Test thoroughly but remember authentication will block test requests without proper keys
2311. Copy the webhook URL and paste it into the `URL` field
2321. Click the `custom header` link under the URL field
2331. For the key add `X-API-KEY` and for the value add the `NOTION_WEBHOOK_SECRET` environment variable from `vars.env` file
2341. Save the automation
2351. Click the `Save URL` button
glimpse2-runbook-test04-view.md11 matches
140- Single Responsibility: Controller handles HTTP concerns + minimal data cleanup
141- Thin Layer: Minimal processing, focused on improving data consumption
142- Useful Filtering: Remove UI elements that don't belong in data APIs
143- Consistent Error Handling: Standard error response format
144145✅ Service Layer:
146147- Data Access: Handle all Notion API interactions
148- Consistent Response Format: Always return {success, data/error, timestamp}
149- Raw Data: Return unfiltered data from external APIs
150151✅ Separation of Concerns:
152153- Controller: HTTP validation, routing, error responses, basic data cleanup
154- Service: External API calls, data retrieval
155- Client: Complex data processing, extraction, formatting
156193}
194195### 11b. Add Demo API Endpoints Section to Dashboard HTML
196197Add this section after the webhook section closes, before the main content div
199200<div class="webhook-section">
201<h3>🔍 Demo API Endpoints</h3>
202203${demoDatabaseId ? `
246- Controller Logic: Keep minimal but include useful data cleanup
247- Property Filtering: Remove UI-specific elements (buttons) that don't belong in
248data APIs
249- Error Handling: Consistent service response pattern
250264## 13\. Benefits of This Approach
265266- Clean Data: Removes UI clutter from data API responses
267- Maintainable: Clear separation between layers with focused responsibilities
268- Flexible: Client can still extract any business data from Notion response
3013021. Briefly summarize the work that was just completed and why it's important
3032. Provide the URL to the dashboard where users can see the live demo API
304endpoint links. Tell the user that they can copy and paste that URL into a
305new tab to see it live.
310311- Users get real Notion page IDs for testing (not fake `/test-id`)
312- One-click access to live API responses from the dashboard
313- Self-documenting API with automatically updated demo links
314- Proper architectural separation maintained
315
glimpse2-runbook-test03-webhooks.md16 matches
7Place authentication middleware in the following order:
891. **Notion webhook auth middleware** (X-API-KEY for `/tasks/*`)
102. **Global user auth middleware** (for other protected routes)
113. **Route handlers**
2122- Create `/backend/routes/webhookAuthCheck.ts`
23- Extract X-API-KEY header from incoming requests
24- Compare against NOTION_WEBHOOK_SECRET environment variable
25- Use constant-time comparison to prevent timing attacks
5455// 2. User auth - for ALL routes EXCEPT /tasks/*
56app.use("/api/*", authCheck);
57app.use("/views/*", authCheck);
58app.use("/", authCheck);
7778```
79app.use("/api/*", authCheck); // Applied to API routes
80app.use("/views/*", authCheck); // Applied to view routes
81app.use("/", authCheck); // Applied to root route
84**This ensures:**
8586✅ `/` (root) still requires user authentication ✅ `/api/*` routes still
87require user authentication ✅ `/views/*` routes still require user
88authentication ✅ `/tasks/*` routes only use webhook authentication (no user
94### Flow Logic
9596- Request to /tasks/\* → Check X-API-KEY → If valid, proceed to route handler
97(skip global auth)
98- Request to /tasks/\* → Check X-API-KEY → If invalid, return 401 (never reaches
99global auth)
100- Request to other routes → Skip webhook auth → Go to global auth → Then route
1111. **Test with wrong key** (should fail):
112113POST /tasks/test Headers: {"X-API-KEY": "wrong-key"} Expected: 403
114"Authentication failed"
115117118POST /tasks/test\
119Headers: {"X-API-KEY": "your-configured-secret-value"} Expected: 200 (reaches
120your route handler)
121188Test with:
189190POST /tasks/notion-webhook Headers: {"X-API-KEY":
191"your-configured-secret-value"} Body: {"test": "data"} Expected: 200 {"success":
192true}
269cursor: not-allowed;
270}
271.api-key-input {
272padding: 6px 10px;
273border: 1px solid #ced4da;
307<p><strong>To test:</strong> Get the <code>NOTION_WEBHOOK_SECRET</code> value from your <code>vars.env</code> file (provided by your administrator)</p>
308<form class="test-controls" onsubmit="testWebhook(event)">
309<input type="text" class="api-key-input" id="customApiKey" placeholder="Enter webhook secret" required>
310<button type="submit" class="test-btn">Test Webhook</button>
311</form>
324325const resultDiv = document.getElementById('testResult');
326const customKeyInput = document.getElementById('customApiKey');
327328// Clear previous result
338}
339340const headers = { 'X-API-KEY': customKey };
341342const response = await fetch('/tasks/test', {
378Make sure these are true:
379380- Webhook endpoints only accessible with correct X-API-KEY
381- User authentication preserved for non-webhook routes
382- Dashboard provides clean, simplified webhook testing interface
397398- /tasks/\* → Webhook auth required (Notion POSTs)
399- /api/\* → User auth required (if applicable)
400- /\* → Public routes (static files, etc.)
401
glimpse2-runbook-test02-auth.md5 matches
64- `import dashboardRoute from "./backend/routes/views/dashboard.tsx";`
65- **Mount in main.tsx**: `app.get("/", dashboardRoute)`
66- **Note**: Dashboard is placed in `/routes/views/` because it's a user-facing view, not an API endpoint
6768This approach:
70- Keeps `main.tsx` focused on app setup and middleware
71- Makes the dashboard logic easier to maintain and test
72- Follows the modular architecture pattern with views separated from API routes
73- Places user-facing interfaces in `/routes/views/` for better organization
74- Allows for easier dashboard enhancements without touching core routing
7778- Display user information (email from `c.get("userEmail")`)
79- Display the `/api/health` endpoint results as raw JSON in a <pre> tag; e.g., JSON.stringify(data, null, 2)
80- Include logout functionality via `/auth/logout`
81- Show a simple and professional welcome interface with minimal styling
106- Add a `PUBLIC_ROUTES` array at the top for routes that should be accessible without authentication
107- **Don't include `/auth/logout`** in the whitelist (LastLogin handles it before your middleware runs)
108- Use for truly public routes like: `/demo/:id`, `/api/health`, static assets, etc.
109110If you encounter problems loading the React button, try this simple approach for authCheck.ts:
115// Add public routes here as needed
116// '/demo/:id', // Example: public demo viewing
117// '/api/public', // Example: public API endpoints
118];
119
glimpse2-runbook-test01-scaffold.md13 matches
15**Services** (/backend/services/):
1617- Single-purpose functions that make direct API calls to external systems
18- Pure functions that handle HTTP transport layer (response.ok)
19- Example: getDatabaseById(id), searchNotionDatabases()
43**Layer 1: HTTP Transport Layer** (response.ok)
4445- Used when checking responses from external APIs (Notion, etc.)
46- Validates HTTP request succeeded (status 200-299)
47- Handles network failures, auth issues, server errors
62- /backend - directory for backend code
63- /routes - HTTP route handlers organized by functionality
64- /api - JSON API endpoints for frontend/backend communication
65- /tasks - Webhook handlers for Notion integrations
66- /views - Server-side rendered views for frontend
67- /services - External API integrations (pure API calls only)
68- /controllers - Business logic coordination between routes and services
69- /crons - Scheduled task handlers
9091// Import route modules
92import apiRoutes from "./backend/routes/api/\_api.routes.ts";
93import taskRoutes from "./backend/routes/tasks/\_tasks.routes.ts";
94import viewRoutes from "./backend/routes/views/\_views.routes.ts";
102103// Mount route modules
104app.route("/api", apiRoutes);
105app.route("/tasks", taskRoutes);
106app.route("/views", viewRoutes);
128---
129130### Step 3: Create Service Layer (API Calls Only)
131132Create /backend/services/notion.service.ts with ONLY direct Notion API calls:
133134**Notion Service Requirements:**
135136- Import Notion: `import { Client } from "npm:@notionhq/client";`
137- Initialize: `const notion = new Client({ auth: Deno.env.get("NOTION_API_KEY") });`
138- Create these functions (API calls only, no business logic):
139- `getDatabases()` - search for all databases
140- `getDatabaseById(databaseId: string)` - retrieve specific database
185186- `getConfiguredDatabases()` - Check all configured databases using environment variables
187- `getHealthStatus()` - Generate formatted health response for API
188189**Controller Function Pattern:**
230**Add Health Endpoint**
231232In /backend/routes/api/\_api.routes.ts:
233234```
267Before proceeding, verify your implementation follows the architecture:
268269- Services only make direct API calls (no environment variables, no orchestration)
270- Controllers handle business logic and coordinate services
271- Routes only handle HTTP concerns and call controllers
glimpse2-runbook-test00-setup.md4 matches
33The most important thing to understand about them is that they are required to connect val.town to Notion, and to connect this Val's code to the Notion databases that hold the demo content.
3435- `NOTION_API_KEY` - Your Notion integration API key
36- `NOTION_WEBHOOK_SECRET` - Matches the X-API-KEY custom header coming from Notion webhooks to /tasks/\* endpoint(s)
37- `GLANCE_DEMOS_DB_ID` - Your Notion database ID for demos, which are personalized to prospects and have unique URLs
38- `GLANCE_CONTENT_DB_ID` - Your Notion database ID for demo page content, which are compiled into demos
1791. In Notion, add a button to the Glimpse Demos database
1801. Edit the automation on that button: add the URL fromj step 1 and then append `/tasks/notion-webhook` to it
1811. Click the 'custom header' link under the webhook URL field and add this key and value: **Key**: `X-API-KEY`, **Value**: `<x-api-key value>`. (Find the value in the `NOTION_WEBHOOK_SECRET` key in `vars.env`)
1821. Click the save button
1831. Click the automation button in the Notion database row
2021. In Notion, add a button to a database row in the Glimpse Demos database called "Staging: set URL"
2031. Edit the automation on that button to be a webhook that hits `https://<branch subdomain url>/tasks/url`
2041. Add the custom header for webhook auth: **Key**: `X-API-KEY` and **Value**: `<custom header value>`. (Find the value in the `NOTION_WEBHOOK_SECRET` key in `vars.env`)
2051. Save the button automation
2061. Click the button in Notion
untitled-1993main.tsx14 matches
5<meta charset="UTF-8">
6<meta name="viewport" content="width=device-width, initial-scale=1.0">
7<title>Probador de API de Grist</title>
8<script src="https://cdn.tailwindcss.com"></script>
9<style>
32<div class="max-w-4xl mx-auto bg-white p-8 rounded-xl shadow-lg">
33<h1 class="text-3xl font-bold text-center mb-6 text-gray-800">
34Probador de API de Grist
35</h1>
36<p class="text-center text-gray-600 mb-8">
37Una herramienta para explorar y probar los endpoints de la API de Grist.
38</p>
3940<div class="mb-6 space-y-4">
41<div class="bg-gray-100 p-4 rounded-lg flex items-center shadow-sm">
42<label for="apiKey" class="font-medium text-gray-700 w-32">API Key:</label>
43<input
44type="text"
45id="apiKey"
46class="flex-1 p-2 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500"
47placeholder="Ingresa tu API Key (Bearer Token)"
48>
49</div>
324</div>
325326<script src="./api.js"></script>
327<script>
328const output = document.getElementById("responseOutput");
346347async function testMethod(methodName, ...args) {
348const apiKey = document.getElementById("apiKey").value;
349const docId = document.getElementById("docId").value;
350const tableId = document.getElementById("tableId").value;
351352if (
353!apiKey ||
354(!docId && methodName.includes("Records") ||
355!tableId && methodName.includes("Records"))
357printToOutput({
358error:
359"Falta la API Key, el ID de Documento o el ID de Tabla",
360});
361return;
362}
363364// Verificar si GristAPIClient existe
365if (typeof GristAPIClient === 'undefined') {
366printToOutput({
367error: "GristAPIClient no está disponible. Asegúrate de que api.js esté cargado correctamente.",
368});
369return;
370}
371372const client = new GristAPIClient(apiKey);
373374try {
untitled-1993main.tsx16 matches
1class GristAPIClient {
2constructor(apiKey) {
3if (!apiKey) {
4throw new Error("API key is required.");
5}
6this.apiKey = apiKey;
7this.baseUrl = "https://docs.getgrist.com/api";
8this.headers = {
9"Authorization": `Bearer ${this.apiKey}`,
10"accept": "application/json",
11};
33const errorText = await response.text();
34throw new Error(
35`API Error: ${response.status} ${response.statusText} - ${errorText}`,
36);
37}
242const errorText = await response.text();
243throw new Error(
244`API Error: ${response.status} ${response.statusText} - ${errorText}`,
245);
246}
260const errorText = await response.text();
261throw new Error(
262`API Error: ${response.status} ${response.statusText} - ${errorText}`,
263);
264}
307308// Gestión de Organizaciones y Documentos
309const apiKey = "1ba558aa677ec970087702df8535483d418805b3";
310const grist = new GristAPIClient(apiKey);
311const orgId = "3Y96qZCVZSpaiVHdoGgasY";
312const workspaceId = 143221; // Reemplaza con tu ID de workspace
336/*
337// Peticiones de Tablas y Registros
338const apiKey = 'YOUR_API_KEY';
339const grist = new GristAPIClient(apiKey);
340const docId = '3Y96qZCVZSpaiVHdoGgasY';
341const tableId = 'Table1';
367/*
368// Peticiones de SQL y Columnas
369const apiKey = 'YOUR_API_KEY';
370const grist = new GristAPIClient(apiKey);
371const docId = '3Y96qZCVZSpaiVHdoGgasY';
372const tableId = 'Pets';
YOUTUBECOMMENTANALYSISnew-file-4421.tsx43 matches
13}
1415const youtubeApiKey = process.env.YOUTUBE_API_KEY;
16const geminiApiKey = process.env.GEMINI_API_KEY;
1718if (!youtubeApiKey || !geminiApiKey) {
19return new Response(
20JSON.stringify({
21error: "API keys not configured",
22required: {
23YOUTUBE_API_KEY: youtubeApiKey ? "✓ Configured" : "❌ Missing",
24GEMINI_API_KEY: geminiApiKey ? "✓ Configured" : "❌ Missing",
25},
26setup: "Contact administrator to configure API keys",
27}),
28{
3738const [videoDetails, comments] = await Promise.all([
39fetchVideoDetailsWithRetry(videoId, youtubeApiKey),
40fetchYouTubeCommentsWithRetry(videoId, youtubeApiKey),
41]);
4248comments,
49videoDetails,
50geminiApiKey,
51);
52116// ========================================
117118async function enhancedMultiPassAnalysis(comments, videoDetails, geminiApiKey) {
119try {
120console.log("🚀 Starting Enhanced Multi-Pass AI Analysis...");
123const [teacherAnalysis, learningAnalysis, behaviorAnalysis] = await Promise
124.all([
125deepTeacherAnalysis(comments, videoDetails, geminiApiKey),
126comprehensiveLearningAnalysis(comments, videoDetails, geminiApiKey),
127studentBehaviorAnalysis(comments, videoDetails, geminiApiKey),
128]);
129130// Additional passes
131const [contentAnalysis, predictiveAnalysis] = await Promise.all([
132contentEffectivenessAnalysis(comments, videoDetails, geminiApiKey),
133predictiveAnalyticsGeneration(comments, videoDetails, geminiApiKey),
134]);
135156// ========================================
157158async function deepTeacherAnalysis(comments, videoDetails, geminiApiKey) {
159const teacherComments = comments.slice(0, 60).map((c) => ({
160text: c.textOriginal,
229Be extremely detailed. Provide evidence for every claim.`;
230231return await processGeminiRequest(teacherPrompt, geminiApiKey);
232}
233235comments,
236videoDetails,
237geminiApiKey,
238) {
239const learningComments = comments.slice(0, 50).map((c) => ({
278}`;
279280return await processGeminiRequest(learningPrompt, geminiApiKey);
281}
282283async function studentBehaviorAnalysis(comments, videoDetails, geminiApiKey) {
284const behaviorComments = comments.slice(0, 40).map((c) => ({
285text: c.textOriginal,
326}`;
327328return await processGeminiRequest(behaviorPrompt, geminiApiKey);
329}
330332comments,
333videoDetails,
334geminiApiKey,
335) {
336const contentComments = comments.slice(0, 30).map((c) => ({
360}`;
361362return await processGeminiRequest(contentPrompt, geminiApiKey);
363}
364366comments,
367videoDetails,
368geminiApiKey,
369) {
370const predictiveComments = comments.slice(0, 25).map((c) => ({
392}`;
393394return await processGeminiRequest(predictivePrompt, geminiApiKey);
395}
396397async function processGeminiRequest(prompt, geminiApiKey) {
398const enhancedPrompt = `
399CONTEXT: You are analyzing educational YouTube comments with 20+ years of expertise.
418try {
419const response = await fetch(
420`https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent?key=${geminiApiKey}`,
421{
422method: "POST",
454return { error: "No valid response from AI", rawResponse: data };
455} catch (error) {
456console.error("Gemini API error:", error);
457return { error: "API request failed", details: error.message };
458}
459}
602}
603604async function fetchYouTubeCommentsWithRetry(videoId, apiKey, maxRetries = 3) {
605for (let attempt = 1; attempt <= maxRetries; attempt++) {
606try {
607return await fetchYouTubeComments(videoId, apiKey);
608} catch (error) {
609if (attempt === maxRetries) throw error;
613}
614615async function fetchVideoDetailsWithRetry(videoId, apiKey, maxRetries = 3) {
616for (let attempt = 1; attempt <= maxRetries; attempt++) {
617try {
618return await fetchVideoDetails(videoId, apiKey);
619} catch (error) {
620if (attempt === maxRetries) throw error;
624}
625626async function fetchYouTubeComments(videoId, apiKey) {
627const comments = [];
628const maxCommentsLimit = 1000;
631const timeComments = await fetchCommentsByOrder(
632videoId,
633apiKey,
634"time",
635500,
640const relevanceComments = await fetchCommentsByOrder(
641videoId,
642apiKey,
643"relevance",
644maxCommentsLimit - comments.length,
663}
664665async function fetchCommentsByOrder(videoId, apiKey, order, remainingLimit) {
666const comments = [];
667let nextPageToken = "";
674675const url =
676`https://www.googleapis.com/youtube/v3/commentThreads?part=snippet,replies&videoId=${videoId}&key=${apiKey}&maxResults=100&order=${order}${
677nextPageToken ? `&pageToken=${nextPageToken}` : ""
678}`;
738}
739740async function fetchVideoDetails(videoId, apiKey) {
741const url =
742`https://www.googleapis.com/youtube/v3/videos?part=snippet,statistics&id=${videoId}&key=${apiKey}`;
743const response = await fetch(url);
744const data = await response.json();
745746if (data.error) throw new Error(`YouTube API Error: ${data.error.message}`);
747if (!data.items || data.items.length === 0) {
748throw new Error("Video not found");
15221523function generateQuotaErrorHTML() {
1524return `<!DOCTYPE html><html><body><h2>API Quota Exceeded</h2><p>Please try again later.</p></body></html>`;
1525}
R2-Backup-SystemREADME.md5 matches
11## Hono
1213This app uses [Hono](https://hono.dev/) as the API framework. You can think of
14Hono as a replacement for [ExpressJS](https://expressjs.com/) that works in
15serverless environments like Val Town or Cloudflare Workers. If you come from
29### `index.html`
3031The most complicated part of this backend API is serving index.html. In this app
32(like most apps) we serve it at the root, ie `GET /`.
3337for client-side rendered apps.
3839## CRUD API Routes
4041This app has two CRUD API routes: for reading and inserting into the messages
42table. They both speak JSON, which is standard. They import their functions from
43`/backend/database/queries.ts`. These routes are called from the React app to
46## Errors
4748Hono and other API frameworks have a habit of swallowing up Errors. We turn off
49this default behavior by re-throwing errors, because we think most of the time
50you'll want to see the full stack trace instead of merely "Internal Server