From 96afec3b9f6fb99d2905da0fdb929321ed2633d6 Mon Sep 17 00:00:00 2001 From: Lane C Date: Tue, 17 Dec 2024 09:27:25 -0800 Subject: [PATCH 01/11] create cache table schemas --- src/db/schema/cacheRequests.ts | 38 +++++++++++++++++++++++++++ src/db/schema/cacheStats.ts | 48 ++++++++++++++++++++++++++++++++++ src/pages/api/v1/cache.ts | 12 +++++++++ 3 files changed, 98 insertions(+) create mode 100644 src/db/schema/cacheRequests.ts create mode 100644 src/db/schema/cacheStats.ts create mode 100644 src/pages/api/v1/cache.ts diff --git a/src/db/schema/cacheRequests.ts b/src/db/schema/cacheRequests.ts new file mode 100644 index 00000000..660aa8e2 --- /dev/null +++ b/src/db/schema/cacheRequests.ts @@ -0,0 +1,38 @@ +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +import { isAdminOrEditor } from "../config-helpers"; +import type { ApiConfig } from "../routes"; +export const icon = ` + + + +`; +export const tableName = "cacheRequests"; +export const name = "Cache Requests"; + +export const route = "cache-requests"; + +export const definition = { + url: text("url").primaryKey(), + createdOn: integer('createdOn').notNull().default(Date.now()), +}; + +export const table = sqliteTable(tableName, { + ...definition, +}); + + +export const access: ApiConfig["access"] = { + operation: { + read: true, + create: true, + update: isAdminOrEditor, + delete: isAdminOrEditor, + }, +}; + +export const fields: ApiConfig["fields"] = { + // body:{ + // type:'textArea' + // } +}; \ No newline at end of file diff --git a/src/db/schema/cacheStats.ts b/src/db/schema/cacheStats.ts new file mode 100644 index 00000000..5f30fbfb --- /dev/null +++ b/src/db/schema/cacheStats.ts @@ -0,0 +1,48 @@ +import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; +import { relations } from "drizzle-orm"; +import * as cacheRequests from './cacheRequests'; + +import { isAdminOrEditor } from "../config-helpers"; +import type { ApiConfig } from "../routes"; +export const icon = ` + + + +`; +export const tableName = "cacheStats"; +export const name = "Cache Stats"; + +export const route = "cache-stats"; + +export const definition = { + id: text("id").primaryKey(), + url: text("url").notNull(), + createdOn: integer('createdOn').notNull().default(Date.now()), + executionTime: integer('executionTime').notNull(), +}; + +export const table = sqliteTable(tableName, { + ...definition +}); + +export const relation = relations(table, ({ one }) => ({ + url: one(cacheRequests.table, { + fields: [table.url], + references: [cacheRequests.table.url], + }), +})); + +export const access: ApiConfig["access"] = { + operation: { + read: true, + create: true, + update: isAdminOrEditor, + delete: isAdminOrEditor, + }, +}; + +export const fields: ApiConfig["fields"] = { + body:{ + type:'textArea' + } +}; \ No newline at end of file diff --git a/src/pages/api/v1/cache.ts b/src/pages/api/v1/cache.ts new file mode 100644 index 00000000..33173b2b --- /dev/null +++ b/src/pages/api/v1/cache.ts @@ -0,0 +1,12 @@ +import { return200 } from "@services/return-types"; +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async (context) => { + const start = Date.now(); + let params = context.params; + console.log("params", params); + console.log("context.url", context.url); + return new Response(JSON.stringify({ url: "checj" }), { + headers: { "Content-Type": "application/json" }, + }); +}; From ecca153c64501743c206ff4b1ccdfc43d6521eca Mon Sep 17 00:00:00 2001 From: Lane C Date: Tue, 17 Dec 2024 09:34:45 -0800 Subject: [PATCH 02/11] gen migrration --- migrations/0001_loose_phalanx.sql | 11 + migrations/meta/0001_snapshot.json | 499 +++++++++++++++++++++++++++++ migrations/meta/_journal.json | 7 + src/db/schema/cacheRequests.ts | 2 +- src/db/schema/cacheStats.ts | 2 +- 5 files changed, 519 insertions(+), 2 deletions(-) create mode 100644 migrations/0001_loose_phalanx.sql create mode 100644 migrations/meta/0001_snapshot.json diff --git a/migrations/0001_loose_phalanx.sql b/migrations/0001_loose_phalanx.sql new file mode 100644 index 00000000..fbc7318a --- /dev/null +++ b/migrations/0001_loose_phalanx.sql @@ -0,0 +1,11 @@ +CREATE TABLE `cacheRequests` ( + `url` text PRIMARY KEY NOT NULL, + `createdOn` integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE `cacheStats` ( + `id` text PRIMARY KEY NOT NULL, + `url` text NOT NULL, + `createdOn` integer NOT NULL, + `executionTime` integer NOT NULL +); diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json new file mode 100644 index 00000000..1a914364 --- /dev/null +++ b/migrations/meta/0001_snapshot.json @@ -0,0 +1,499 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "38defc9a-5507-44c8-b3f0-9f1d3fc9e72f", + "prevId": "337a2d93-7636-4120-8d28-dc52b0896e41", + "tables": { + "cacheRequests": { + "name": "cacheRequests", + "columns": { + "url": { + "name": "url", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "cacheStats": { + "name": "cacheStats", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executionTime": { + "name": "executionTime", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "categories": { + "name": "categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "categoriesToPosts": { + "name": "categoriesToPosts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "postId": { + "name": "postId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryId": { + "name": "categoryId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "categoriesToPosts_postId_posts_id_fk": { + "name": "categoriesToPosts_postId_posts_id_fk", + "tableFrom": "categoriesToPosts", + "tableTo": "posts", + "columnsFrom": [ + "postId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "categoriesToPosts_categoryId_categories_id_fk": { + "name": "categoriesToPosts_categoryId_categories_id_fk", + "tableFrom": "categoriesToPosts", + "tableTo": "categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "categoriesToPosts_postId_categoryId_pk": { + "columns": [ + "postId", + "categoryId" + ], + "name": "categoriesToPosts_postId_categoryId_pk" + } + }, + "uniqueConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "postId": { + "name": "postId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "commentsUserIdIndex": { + "name": "commentsUserIdIndex", + "columns": [ + "userId" + ], + "isUnique": false + }, + "commentsPostIdIndex": { + "name": "commentsPostIdIndex", + "columns": [ + "postId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "posts": { + "name": "posts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "images": { + "name": "images", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "postUserIdIndex": { + "name": "postUserIdIndex", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "userSessions": { + "name": "userSessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activeExpires": { + "name": "activeExpires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idleExpires": { + "name": "idleExpires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "userSessions_userId_users_id_fk": { + "name": "userSessions_userId_users_id_fk", + "tableFrom": "userSessions", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "firstName": { + "name": "firstName", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lastName": { + "name": "lastName", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 3d07e1d0..7d3b2377 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1733957131005, "tag": "0000_milky_iron_man", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1734456810326, + "tag": "0001_loose_phalanx", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/db/schema/cacheRequests.ts b/src/db/schema/cacheRequests.ts index 660aa8e2..e11099da 100644 --- a/src/db/schema/cacheRequests.ts +++ b/src/db/schema/cacheRequests.ts @@ -14,7 +14,7 @@ export const route = "cache-requests"; export const definition = { url: text("url").primaryKey(), - createdOn: integer('createdOn').notNull().default(Date.now()), + createdOn: integer('createdOn').notNull(), }; export const table = sqliteTable(tableName, { diff --git a/src/db/schema/cacheStats.ts b/src/db/schema/cacheStats.ts index 5f30fbfb..6533294d 100644 --- a/src/db/schema/cacheStats.ts +++ b/src/db/schema/cacheStats.ts @@ -17,7 +17,7 @@ export const route = "cache-stats"; export const definition = { id: text("id").primaryKey(), url: text("url").notNull(), - createdOn: integer('createdOn').notNull().default(Date.now()), + createdOn: integer('createdOn').notNull(), executionTime: integer('executionTime').notNull(), }; From 250addd55e3ea03192314aff780bb72e51460360 Mon Sep 17 00:00:00 2001 From: Lane C Date: Tue, 17 Dec 2024 09:40:38 -0800 Subject: [PATCH 03/11] url to id --- src/db/routes.ts | 5 ++++- src/db/schema/cacheRequests.ts | 2 +- src/db/schema/cacheStats.ts | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/db/routes.ts b/src/db/routes.ts index c50d9ba8..7a7a08e3 100644 --- a/src/db/routes.ts +++ b/src/db/routes.ts @@ -5,7 +5,8 @@ import * as categories from "./schema/categories"; import * as categoriesToPosts from "./schema/categoriesToPosts"; // import * as userKeys from "./schema/userKeys"; import * as userSessions from "./schema/userSessions"; - +import * as cacheRequests from "./schema/cacheRequests"; +import * as cacheStats from "./schema/cacheStats"; // import { AppContext } from '../server'; import { isAdminOrEditor } from "./config-helpers"; import type { APIContext as AppContext } from "astro"; @@ -214,6 +215,8 @@ export const tableSchemas = { categories, categoriesToPosts, userSessions, + cacheRequests, + cacheStats }; for (const key of Object.keys(tableSchemas)) { diff --git a/src/db/schema/cacheRequests.ts b/src/db/schema/cacheRequests.ts index e11099da..b3b39ae3 100644 --- a/src/db/schema/cacheRequests.ts +++ b/src/db/schema/cacheRequests.ts @@ -13,7 +13,7 @@ export const name = "Cache Requests"; export const route = "cache-requests"; export const definition = { - url: text("url").primaryKey(), + id: text("id").primaryKey(), createdOn: integer('createdOn').notNull(), }; diff --git a/src/db/schema/cacheStats.ts b/src/db/schema/cacheStats.ts index 6533294d..c466aa30 100644 --- a/src/db/schema/cacheStats.ts +++ b/src/db/schema/cacheStats.ts @@ -28,7 +28,7 @@ export const table = sqliteTable(tableName, { export const relation = relations(table, ({ one }) => ({ url: one(cacheRequests.table, { fields: [table.url], - references: [cacheRequests.table.url], + references: [cacheRequests.table.id], }), })); From c17895c6c8d14ea848e9c165e4a8db7377b7b132 Mon Sep 17 00:00:00 2001 From: Lane C Date: Tue, 17 Dec 2024 09:43:01 -0800 Subject: [PATCH 04/11] migration cleanup --- migrations/0001_loose_phalanx.sql | 11 - migrations/meta/0001_snapshot.json | 499 ----------------------------- migrations/meta/_journal.json | 7 - 3 files changed, 517 deletions(-) delete mode 100644 migrations/0001_loose_phalanx.sql delete mode 100644 migrations/meta/0001_snapshot.json diff --git a/migrations/0001_loose_phalanx.sql b/migrations/0001_loose_phalanx.sql deleted file mode 100644 index fbc7318a..00000000 --- a/migrations/0001_loose_phalanx.sql +++ /dev/null @@ -1,11 +0,0 @@ -CREATE TABLE `cacheRequests` ( - `url` text PRIMARY KEY NOT NULL, - `createdOn` integer NOT NULL -); ---> statement-breakpoint -CREATE TABLE `cacheStats` ( - `id` text PRIMARY KEY NOT NULL, - `url` text NOT NULL, - `createdOn` integer NOT NULL, - `executionTime` integer NOT NULL -); diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json deleted file mode 100644 index 1a914364..00000000 --- a/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,499 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "38defc9a-5507-44c8-b3f0-9f1d3fc9e72f", - "prevId": "337a2d93-7636-4120-8d28-dc52b0896e41", - "tables": { - "cacheRequests": { - "name": "cacheRequests", - "columns": { - "url": { - "name": "url", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "cacheStats": { - "name": "cacheStats", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "url": { - "name": "url", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "executionTime": { - "name": "executionTime", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "categories": { - "name": "categories", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "body": { - "name": "body", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "categoriesToPosts": { - "name": "categoriesToPosts", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "postId": { - "name": "postId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "categoryId": { - "name": "categoryId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "categoriesToPosts_postId_posts_id_fk": { - "name": "categoriesToPosts_postId_posts_id_fk", - "tableFrom": "categoriesToPosts", - "tableTo": "posts", - "columnsFrom": [ - "postId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - }, - "categoriesToPosts_categoryId_categories_id_fk": { - "name": "categoriesToPosts_categoryId_categories_id_fk", - "tableFrom": "categoriesToPosts", - "tableTo": "categories", - "columnsFrom": [ - "categoryId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "categoriesToPosts_postId_categoryId_pk": { - "columns": [ - "postId", - "categoryId" - ], - "name": "categoriesToPosts_postId_categoryId_pk" - } - }, - "uniqueConstraints": {} - }, - "comments": { - "name": "comments", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "body": { - "name": "body", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "postId": { - "name": "postId", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "tags": { - "name": "tags", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "commentsUserIdIndex": { - "name": "commentsUserIdIndex", - "columns": [ - "userId" - ], - "isUnique": false - }, - "commentsPostIdIndex": { - "name": "commentsPostIdIndex", - "columns": [ - "postId" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "posts": { - "name": "posts", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "title": { - "name": "title", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "body": { - "name": "body", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "image": { - "name": "image", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "images": { - "name": "images", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "tags": { - "name": "tags", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "postUserIdIndex": { - "name": "postUserIdIndex", - "columns": [ - "userId" - ], - "isUnique": false - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "userSessions": { - "name": "userSessions", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "userId": { - "name": "userId", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "activeExpires": { - "name": "activeExpires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "idleExpires": { - "name": "idleExpires", - "type": "integer", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": {}, - "foreignKeys": { - "userSessions_userId_users_id_fk": { - "name": "userSessions_userId_users_id_fk", - "tableFrom": "userSessions", - "tableTo": "users", - "columnsFrom": [ - "userId" - ], - "columnsTo": [ - "id" - ], - "onDelete": "no action", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - }, - "users": { - "name": "users", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true, - "autoincrement": false - }, - "firstName": { - "name": "firstName", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "lastName": { - "name": "lastName", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": true, - "autoincrement": false - }, - "role": { - "name": "role", - "type": "text", - "primaryKey": false, - "notNull": false, - "autoincrement": false, - "default": "'user'" - }, - "createdOn": { - "name": "createdOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - }, - "updatedOn": { - "name": "updatedOn", - "type": "integer", - "primaryKey": false, - "notNull": false, - "autoincrement": false - } - }, - "indexes": { - "users_email_unique": { - "name": "users_email_unique", - "columns": [ - "email" - ], - "isUnique": true - }, - "email_idx": { - "name": "email_idx", - "columns": [ - "email" - ], - "isUnique": true - } - }, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {} - } - }, - "enums": {}, - "_meta": { - "schemas": {}, - "tables": {}, - "columns": {} - }, - "internal": { - "indexes": {} - } -} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 7d3b2377..3d07e1d0 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -8,13 +8,6 @@ "when": 1733957131005, "tag": "0000_milky_iron_man", "breakpoints": true - }, - { - "idx": 1, - "version": "6", - "when": 1734456810326, - "tag": "0001_loose_phalanx", - "breakpoints": true } ] } \ No newline at end of file From a6e970e2d7165a56a011a0442b9bed174f49bd6c Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 09:25:13 -0800 Subject: [PATCH 05/11] cache test api --- migrations/0001_perfect_iron_man.sql | 11 + migrations/meta/0001_snapshot.json | 499 +++++++++++++++++++++++++++ migrations/meta/_journal.json | 7 + src/pages/api/v1/cache-test.ts | 20 ++ src/pages/api/v1/cache.ts | 70 +++- 5 files changed, 606 insertions(+), 1 deletion(-) create mode 100644 migrations/0001_perfect_iron_man.sql create mode 100644 migrations/meta/0001_snapshot.json create mode 100644 src/pages/api/v1/cache-test.ts diff --git a/migrations/0001_perfect_iron_man.sql b/migrations/0001_perfect_iron_man.sql new file mode 100644 index 00000000..5bf88db5 --- /dev/null +++ b/migrations/0001_perfect_iron_man.sql @@ -0,0 +1,11 @@ +CREATE TABLE `cacheRequests` ( + `id` text PRIMARY KEY NOT NULL, + `createdOn` integer NOT NULL +); +--> statement-breakpoint +CREATE TABLE `cacheStats` ( + `id` text PRIMARY KEY NOT NULL, + `url` text NOT NULL, + `createdOn` integer NOT NULL, + `executionTime` integer NOT NULL +); diff --git a/migrations/meta/0001_snapshot.json b/migrations/meta/0001_snapshot.json new file mode 100644 index 00000000..cdfe9fe2 --- /dev/null +++ b/migrations/meta/0001_snapshot.json @@ -0,0 +1,499 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "5bae2ddb-2982-452f-987e-545676cd10a4", + "prevId": "337a2d93-7636-4120-8d28-dc52b0896e41", + "tables": { + "cacheRequests": { + "name": "cacheRequests", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "cacheStats": { + "name": "cacheStats", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "executionTime": { + "name": "executionTime", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "categories": { + "name": "categories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "categoriesToPosts": { + "name": "categoriesToPosts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "postId": { + "name": "postId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "categoryId": { + "name": "categoryId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "categoriesToPosts_postId_posts_id_fk": { + "name": "categoriesToPosts_postId_posts_id_fk", + "tableFrom": "categoriesToPosts", + "tableTo": "posts", + "columnsFrom": [ + "postId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "categoriesToPosts_categoryId_categories_id_fk": { + "name": "categoriesToPosts_categoryId_categories_id_fk", + "tableFrom": "categoriesToPosts", + "tableTo": "categories", + "columnsFrom": [ + "categoryId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "categoriesToPosts_postId_categoryId_pk": { + "columns": [ + "postId", + "categoryId" + ], + "name": "categoriesToPosts_postId_categoryId_pk" + } + }, + "uniqueConstraints": {} + }, + "comments": { + "name": "comments", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "postId": { + "name": "postId", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "commentsUserIdIndex": { + "name": "commentsUserIdIndex", + "columns": [ + "userId" + ], + "isUnique": false + }, + "commentsPostIdIndex": { + "name": "commentsPostIdIndex", + "columns": [ + "postId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "posts": { + "name": "posts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "images": { + "name": "images", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tags": { + "name": "tags", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "postUserIdIndex": { + "name": "postUserIdIndex", + "columns": [ + "userId" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "userSessions": { + "name": "userSessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "userId": { + "name": "userId", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "activeExpires": { + "name": "activeExpires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "idleExpires": { + "name": "idleExpires", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "userSessions_userId_users_id_fk": { + "name": "userSessions_userId_users_id_fk", + "tableFrom": "userSessions", + "tableTo": "users", + "columnsFrom": [ + "userId" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "firstName": { + "name": "firstName", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "lastName": { + "name": "lastName", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'user'" + }, + "createdOn": { + "name": "createdOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "updatedOn": { + "name": "updatedOn", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "email_idx": { + "name": "email_idx", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + } + }, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/migrations/meta/_journal.json b/migrations/meta/_journal.json index 3d07e1d0..93d9e8b1 100644 --- a/migrations/meta/_journal.json +++ b/migrations/meta/_journal.json @@ -8,6 +8,13 @@ "when": 1733957131005, "tag": "0000_milky_iron_man", "breakpoints": true + }, + { + "idx": 1, + "version": "6", + "when": 1734457385254, + "tag": "0001_perfect_iron_man", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/pages/api/v1/cache-test.ts b/src/pages/api/v1/cache-test.ts new file mode 100644 index 00000000..fd333cb2 --- /dev/null +++ b/src/pages/api/v1/cache-test.ts @@ -0,0 +1,20 @@ +import { return200 } from "@services/return-types"; +import type { APIRoute } from "astro"; + +export const GET: APIRoute = async (context) => { + const request = context.request; + const cacheUrl = new URL(context.url); + + // Construct the cache key from the cache URL + const cacheKey = new Request(cacheUrl.toString(), request); + const cache = context.locals.runtime.caches.default; + + console.log('before', cache) + + cache.put('cacheKey', { message: "Cache test" }); + + console.log('after', cache) + + +return return200({ message: "Cache test" }); +}; diff --git a/src/pages/api/v1/cache.ts b/src/pages/api/v1/cache.ts index 33173b2b..12614a28 100644 --- a/src/pages/api/v1/cache.ts +++ b/src/pages/api/v1/cache.ts @@ -6,7 +6,75 @@ export const GET: APIRoute = async (context) => { let params = context.params; console.log("params", params); console.log("context.url", context.url); - return new Response(JSON.stringify({ url: "checj" }), { + + const urlToCache = "https://demo.sonicjs.com/api/v1/posts?limit=10"; + + console.log("processing url start", urlToCache); + + // Construct the cache key from the cache URL + const cacheKey = new Request(urlToCache, context.request); +const cache = context.locals.runtime.caches.default; + + // Check whether the value is already available in the cache + // if not, fetch it from origin, and store it in the cache + let response = await cache.match(urlToCache); + + if (!response) { + console.log(`--> MISS: ${urlToCache}`); + // If not in cache, get it from origin + response = await fetch(urlToCache); + + // Must use Response constructor to inherit all of response's fields + response = new Response(response.body, response); + + // Cache API respects Cache-Control headers. Setting s-max-age to 10 + // will limit the response to be in cache for 10 seconds max + + // Any changes made to the response here will be reflected in the cached value + response.headers.append("Cache-Control", "s-maxage=2592000"); + response.headers.append("SonicJs-Source", "fetch"); + + postProcessRequest(context, cache, cacheKey, urlToCache, response, start); + return response; + } else { + console.log( `--> HIT: ${urlToCache}`); + const responeCloned = new Response(response.body, response); + responeCloned.headers.delete("SonicJs-Source"); + responeCloned.headers.append("SonicJs-Source", "cache"); + + return responeCloned; + } + + return new Response(JSON.stringify({ url: "check" }), { headers: { "Content-Type": "application/json" }, }); }; + +const postProcessRequest = ( + ctx, + cache, + cacheKey, + cacheUrl, + response, + start +) => { + putInCache(ctx, cache, cacheKey, response); + +// storeUrlInDB(ctx, cacheUrl); + + const end = Date.now(); + const executionTime = end - start; + // insertStatInDB(ctx, cacheUrl, executionTime); + // addToKVCache(ctx, cacheKey, response); +}; + +const putInCache = (ctx, cache, cacheKey, response) => { + //HACK: for testing + try { + cache.put(cacheKey, response.clone()); + // ctx.executionCtx.waitUntil(cache.put(cacheKey, response.clone())); + console.log(`--> ADDED: ${cacheKey}`); + } catch (error) { + console.error("unable to cache put", error); + } +}; From 19d63ecb85cbf0fd7f667202eaa0e0f871a8c9fe Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 11:19:30 -0800 Subject: [PATCH 06/11] simple test --- src/pages/api/v1/[table].test.ts | 4 +- src/pages/api/v1/cache-test.ts | 34 +++++++++++------ src/pages/api/v1/cache.ts | 64 ++++++++++++++++---------------- 3 files changed, 56 insertions(+), 46 deletions(-) diff --git a/src/pages/api/v1/[table].test.ts b/src/pages/api/v1/[table].test.ts index 87582afe..4e26551f 100644 --- a/src/pages/api/v1/[table].test.ts +++ b/src/pages/api/v1/[table].test.ts @@ -44,7 +44,7 @@ describe('GET /api/v1/:table', () => { }; const response = await GET(context as any); - const result = await response.json(); + const result = await response.json() as { error: string }; expect(response.status).toBe(500); expect(result.error).toBe('Table "undefined-table" not defined in your schema'); @@ -60,7 +60,7 @@ describe('GET /api/v1/:table', () => { (getApiAccessControlResult as any).mockResolvedValue(false); const response = await GET(context as any); - const result = await response.json(); + const result = await response.json() as { error: string }; expect(response.status).toBe(401); expect(result.error).toBe('Unauthorized'); diff --git a/src/pages/api/v1/cache-test.ts b/src/pages/api/v1/cache-test.ts index fd333cb2..da603cf9 100644 --- a/src/pages/api/v1/cache-test.ts +++ b/src/pages/api/v1/cache-test.ts @@ -1,20 +1,30 @@ -import { return200 } from "@services/return-types"; -import type { APIRoute } from "astro"; +export const GET = async (context) => { + const { request } = context; + const { url } = request; -export const GET: APIRoute = async (context) => { - const request = context.request; - const cacheUrl = new URL(context.url); - - // Construct the cache key from the cache URL - const cacheKey = new Request(cacheUrl.toString(), request); const cache = context.locals.runtime.caches.default; - console.log('before', cache) + let cacheFound = false; + const cacheResponse = await cache.match(url); + if (cacheResponse) { + console.log("Cache hit"); + cacheFound = true; + return cacheResponse; + } else{ + console.log("Cache miss"); - cache.put('cacheKey', { message: "Cache test" }); + } - console.log('after', cache) + var response = new Response( + JSON.stringify({ + "message": "Cache test: " + cacheFound + }), + { status: 200 } + ); + response.headers.set("cache-control", "public, max-age=600000"); + response.headers.set("content-type", "application/json"); + await cache.put(url, new Response(response.body, response)); -return return200({ message: "Cache test" }); + return response; }; diff --git a/src/pages/api/v1/cache.ts b/src/pages/api/v1/cache.ts index 12614a28..ac11b4c3 100644 --- a/src/pages/api/v1/cache.ts +++ b/src/pages/api/v1/cache.ts @@ -2,48 +2,48 @@ import { return200 } from "@services/return-types"; import type { APIRoute } from "astro"; export const GET: APIRoute = async (context) => { - const start = Date.now(); - let params = context.params; - console.log("params", params); - console.log("context.url", context.url); +// const start = Date.now(); +// let params = context.params; +// console.log("params", params); +// console.log("context.url", context.url); - const urlToCache = "https://demo.sonicjs.com/api/v1/posts?limit=10"; +// const urlToCache = "https://demo.sonicjs.com/api/v1/posts?limit=10"; - console.log("processing url start", urlToCache); +// console.log("processing url start", urlToCache); - // Construct the cache key from the cache URL - const cacheKey = new Request(urlToCache, context.request); -const cache = context.locals.runtime.caches.default; +// // Construct the cache key from the cache URL +// const cacheKey = new Request(urlToCache, context.request); +// const cache = context.locals.runtime.caches.default; - // Check whether the value is already available in the cache - // if not, fetch it from origin, and store it in the cache - let response = await cache.match(urlToCache); +// // Check whether the value is already available in the cache +// // if not, fetch it from origin, and store it in the cache +// let response = await cache.match(urlToCache); - if (!response) { - console.log(`--> MISS: ${urlToCache}`); - // If not in cache, get it from origin - response = await fetch(urlToCache); +// if (!response) { +// console.log(`--> MISS: ${urlToCache}`); +// // If not in cache, get it from origin +// response = await fetch(urlToCache); - // Must use Response constructor to inherit all of response's fields - response = new Response(response.body, response); +// // Must use Response constructor to inherit all of response's fields +// response = new Response(response.body, response); - // Cache API respects Cache-Control headers. Setting s-max-age to 10 - // will limit the response to be in cache for 10 seconds max +// // Cache API respects Cache-Control headers. Setting s-max-age to 10 +// // will limit the response to be in cache for 10 seconds max - // Any changes made to the response here will be reflected in the cached value - response.headers.append("Cache-Control", "s-maxage=2592000"); - response.headers.append("SonicJs-Source", "fetch"); +// // Any changes made to the response here will be reflected in the cached value +// response.headers.append("Cache-Control", "s-maxage=2592000"); +// response.headers.append("SonicJs-Source", "fetch"); - postProcessRequest(context, cache, cacheKey, urlToCache, response, start); - return response; - } else { - console.log( `--> HIT: ${urlToCache}`); - const responeCloned = new Response(response.body, response); - responeCloned.headers.delete("SonicJs-Source"); - responeCloned.headers.append("SonicJs-Source", "cache"); +// postProcessRequest(context, cache, cacheKey, urlToCache, response, start); +// return response; +// } else { +// console.log( `--> HIT: ${urlToCache}`); +// const responeCloned = new Response(response.body, response); +// responeCloned.headers.delete("SonicJs-Source"); +// responeCloned.headers.append("SonicJs-Source", "cache"); - return responeCloned; - } +// return responeCloned; +// } return new Response(JSON.stringify({ url: "check" }), { headers: { "Content-Type": "application/json" }, From 002736315c7b22b68732c9a5a2fe88aa464427ff Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 14:51:36 -0800 Subject: [PATCH 07/11] try middleware --- src/middleware/index.ts | 156 ++++++++++++++++++++++++---------------- 1 file changed, 94 insertions(+), 62 deletions(-) diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 661c572d..76f4017d 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -1,66 +1,98 @@ import { defineMiddleware } from "astro:middleware"; import { - validateSessionToken, - createSession, - invalidateSession + validateSessionToken, + createSession, + invalidateSession, } from "@services/sessions"; +import { sequence } from "astro:middleware"; -export const onRequest = defineMiddleware(async (context, next) => { - // const config = initializeConfig( - // context.locals.runtime.env.D1, - // context.locals.runtime.env - // ); - // context.locals.auth = new Auth(config); - - // Get session token from cookie - const sessionId = context.cookies.get('session')?.value; - - // Check if we're already on the login or register page - const isAuthPage = context.url.pathname.match(/^\/admin\/(login|register)/); - - try { - if (sessionId) { - // Validate the session - const { user } = await validateSessionToken(context.locals.runtime.env.D1, sessionId); - // const { user } = await context.locals.auth.validateSession(sessionId); - if (user) { - context.locals.user = user; - - // If user is logged in and tries to access login/register, redirect to admin - if (isAuthPage) { - return context.redirect('/admin'); - } - - return next(); - } - } - - // Don't redirect if already on auth pages - if (isAuthPage) { - return next(); - } - - // If no valid session and trying to access protected routes, redirect to login - if (context.url.pathname.startsWith('/admin')) { - return context.redirect('/admin/login'); - } - } catch (error) { - // Handle session validation errors (expired, invalid, etc) - console.error('Session validation error:', error); - - // Clear invalid session cookie - context.cookies.delete('session', { path: '/' }); - - // Don't redirect if already on auth pages - if (isAuthPage) { - return next(); - } - - // Redirect to login for protected routes - if (context.url.pathname.startsWith('/admin')) { - return context.redirect('/admin/login'); - } - } - - return next(); -}); \ No newline at end of file +async function cache(context, next) { + console.log("cache middleware"); + + const { request } = context; + const { url } = request; + + const cache = context.locals.runtime.caches.default; + + const cacheResponse = await cache.match(url); + if (cacheResponse) { + console.log("---> Cache hit"); + return cacheResponse; + } + + const response = await next(); + + response.headers.set("cache-control", "public, max-age=60"); + response.headers.set("content-type", "application/json"); + + await cache.put(url, new Response(response.body, response)); + + return response; + // return next(); + +} + +async function auth(context, next) { + // const config = initializeConfig( + // context.locals.runtime.env.D1, + // context.locals.runtime.env + // ); + // context.locals.auth = new Auth(config); + + // Get session token from cookie + const sessionId = context.cookies.get("session")?.value; + + // Check if we're already on the login or register page + const isAuthPage = context.url.pathname.match(/^\/admin\/(login|register)/); + + try { + if (sessionId) { + // Validate the session + const { user } = await validateSessionToken( + context.locals.runtime.env.D1, + sessionId + ); + // const { user } = await context.locals.auth.validateSession(sessionId); + if (user) { + context.locals.user = user; + + // If user is logged in and tries to access login/register, redirect to admin + if (isAuthPage) { + return context.redirect("/admin"); + } + + return next(); + } + } + + // Don't redirect if already on auth pages + if (isAuthPage) { + return next(); + } + + // If no valid session and trying to access protected routes, redirect to login + if (context.url.pathname.startsWith("/admin")) { + return context.redirect("/admin/login"); + } + } catch (error) { + // Handle session validation errors (expired, invalid, etc) + console.error("Session validation error:", error); + + // Clear invalid session cookie + context.cookies.delete("session", { path: "/" }); + + // Don't redirect if already on auth pages + if (isAuthPage) { + return next(); + } + + // Redirect to login for protected routes + if (context.url.pathname.startsWith("/admin")) { + return context.redirect("/admin/login"); + } + } + + return next(); +} + +export const onRequest = sequence(cache, auth); From fa9f795720f43f33395a0756040baf83153aa0a8 Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 14:55:38 -0800 Subject: [PATCH 08/11] middleware 2 --- src/middleware/index.ts | 55 ++++++++++++++++++++++++----------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 76f4017d..b524efad 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -7,28 +7,39 @@ import { import { sequence } from "astro:middleware"; async function cache(context, next) { - console.log("cache middleware"); - - const { request } = context; - const { url } = request; - - const cache = context.locals.runtime.caches.default; - - const cacheResponse = await cache.match(url); - if (cacheResponse) { - console.log("---> Cache hit"); - return cacheResponse; - } - - const response = await next(); - - response.headers.set("cache-control", "public, max-age=60"); - response.headers.set("content-type", "application/json"); - - await cache.put(url, new Response(response.body, response)); - - return response; - // return next(); + console.log('Handling GET request for character'); + const request = context.request; + const cacheUrl = new URL(request.url); + + // Construct the cache key from the cache URL + const cacheKey = new Request(cacheUrl.toString(), request); + const cache = context.locals.runtime.caches.default; + + let response = await cache.match(cacheKey); + let nextResponse; + + if (!response) { + console.log( + `MISS ** Response for request url: ${request.url} not present in cache. Fetching and caching request.` + ); + + response = await next(); + + response.headers.append("Cache-Control", "s-maxage=10"); + await cache.put(cacheKey, response.clone()); + nextResponse = response; + } else { + console.log(`Cache hit for: ${request.url}.`); + nextResponse = await next(); + } + + // clone and create a new response because + // otherwise you get errors modifying the content type + const clonedResponse = nextResponse.clone(); + const finalResponse = new Response(response.body, clonedResponse); + + finalResponse.headers.append("Content-Type", "application/json"); + return new Response(finalResponse.body, finalResponse); } From 2b70b2ed396d2211b94112c9b50811b313fbb3b1 Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 16:49:57 -0800 Subject: [PATCH 09/11] reqs readme --- README-REQUIREMENTS.md | 145 ++++++++++++++++++++++++++++++++++++++++ src/middleware/index.ts | 2 +- 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 README-REQUIREMENTS.md diff --git a/README-REQUIREMENTS.md b/README-REQUIREMENTS.md new file mode 100644 index 00000000..12aa01af --- /dev/null +++ b/README-REQUIREMENTS.md @@ -0,0 +1,145 @@ +``` +npm install +npm run dev +``` + +``` +npm run deploy +``` + +------ + +# D1 +``` +{ + "id": "123-abc", + "firstName": "Lane" +} +``` + +# KV +``` +{ + "id": "123-abc", + "firstName": "Lane" +} +``` + +# In Memory +Maybe don't use bc we can't update on all nodes. May need to confine cache to kv + +# Select + +Once we have the data from a GET, we can: +1. Cache it using the Cloudflare cache API +2. Store it in memory (edge servers limited to 128MB) + +The challenge is, how do we know when to invalidate the cache? + +Is cache api global, if so that is great +Is the cache api per node? if so might as well use memory + +If using memory, how to be notified that data has been updated? + +maybe go back to KV, and forget in memory caching? what is the latency delta? + +maybe use Durable object? + +Is KV lookup a slow process, if so maybe manage list of cache urls . + +# Update/Delete + +Update record "123-abc": +1. Update D1 +1. Get List of cache items containing "123-abc" +1. Invalidate cache items from above list + 1. 123-abc | /v1/users?limit=10 + 1. 123-abc | /v1/users?firstName=Lane + 1. 123-abc | /v1/users?id=123* +1. Now rehydrate the cache for the affected cache item: + 1. /v1/users?limit=10 + 1. /v1/users?firstName=Lane + 1. /v1/users?id=123* + +# Insert + +Inserts are tricky because any list could be affected. + +Do we need to invalidate all lists that have a dependency based on the source table + +For example if we insert a new user record, we also need to track all cache item lists that could be impacted by a new user + +Anything with a cache key like: +/v1/users* +/v1/users?limit=10 +/v1/users?limit=20 + + +Then async update all affected caches + +# Key Tracking + +Do we need to track all previous urls so that we can rehydrate? + +# Lock and Questions +Do we need to prevent close subsequent calls from returning stale data? +maybe use in memory just on node where the update/delete/insert occurs so we can quickly return non stale data +maybe update lists instead of replace? yes - then we don't have to run a query to re-select +but then joins get messy +what is fields are updated that aren't used in joined queries? + +if we pass insetts, updates and deletes thru edgecache, will those be materially slower? + +what if there is an advanced update that updates several records in multiple tables? How are we supposed to detect cache updates on that? Maybe we nneed to allow the developer to hit our API to manually trigger cache updates in such cases. + +Use ML to learn which endpints need to be updated with each PUT/POST/DELETE + +# KV Dependencies + +We must track every dependency for each record in the source database + +We don't need to store any system of record data + +|record| url | +| ---- | -----| +|123-abc | /v1/users?limit=10 | +|123-abc | /v1/users?firstName=Lane | +|123-abc | /v1/users?id=123* | + +|table| url | +| ---- | -----| +|users | /v1/users?limit=10| +|users | /v1/users?firstName=Lane| +|users | /v1/users?id=123*| + +|table| url | +| ---- | -----| +|users | /v1/usersWithOrders?limit=10| +|orders | /v1/usersWithOrders?firstName=Lane| + +# MVP + + ## 1.0 +- GETS - Read only API +- Cache expires after 24 hours by default +- Developers can set their own time to expire +- CLI only, no web app +- Developer must manually invalidate cache + +### Process +1. Build MVP + 1. Tenant provisioning +1. Build CLI +1. Reach out to Beta users on LinkedIn +1. Build Marketing Website + +## 1.1 +- Developer can set cache TTL + +## 1.2 +- Add dependency tracking (High Effort) + +## 1.3 +- Use machine learning to determine appropriate cache invalidation + +# Technical Requirements \ No newline at end of file diff --git a/src/middleware/index.ts b/src/middleware/index.ts index b524efad..16edfc78 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -13,7 +13,7 @@ async function cache(context, next) { // Construct the cache key from the cache URL const cacheKey = new Request(cacheUrl.toString(), request); - const cache = context.locals.runtime.caches.default; + const cache = context.locals.runtime.caches?.default; let response = await cache.match(cacheKey); let nextResponse; From b2ef8a8ac3a844b7da111ab978fe7d4e35edb3e8 Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 17:09:07 -0800 Subject: [PATCH 10/11] static posts --- src/middleware/index.ts | 3 ++- src/pages/api/v1/speed-test.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 src/pages/api/v1/speed-test.ts diff --git a/src/middleware/index.ts b/src/middleware/index.ts index 16edfc78..f4d1dc57 100644 --- a/src/middleware/index.ts +++ b/src/middleware/index.ts @@ -106,4 +106,5 @@ async function auth(context, next) { return next(); } -export const onRequest = sequence(cache, auth); +export const onRequest = sequence( auth); +// export const onRequest = sequence(cache, auth); diff --git a/src/pages/api/v1/speed-test.ts b/src/pages/api/v1/speed-test.ts new file mode 100644 index 00000000..1aac8b3c --- /dev/null +++ b/src/pages/api/v1/speed-test.ts @@ -0,0 +1,19 @@ +import { return200WithObject } from '@services/return-types'; +import { APIRoute } from 'astro'; + +export const GET: APIRoute = async (context) => { + const blogPosts = [ + { id: 1, title: 'First Blog Post', content: 'This is the content of the first blog post.' }, + { id: 2, title: 'Second Blog Post', content: 'This is the content of the second blog post.' }, + { id: 3, title: 'Third Blog Post', content: 'This is the content of the third blog post.' }, + { id: 4, title: 'Fourth Blog Post', content: 'This is the content of the fourth blog post.' }, + { id: 5, title: 'Fifth Blog Post', content: 'This is the content of the fifth blog post.' }, + { id: 6, title: 'Sixth Blog Post', content: 'This is the content of the sixth blog post.' }, + { id: 7, title: 'Seventh Blog Post', content: 'This is the content of the seventh blog post.' }, + { id: 8, title: 'Eighth Blog Post', content: 'This is the content of the eighth blog post.' }, + { id: 9, title: 'Ninth Blog Post', content: 'This is the content of the ninth blog post.' }, + { id: 10, title: 'Tenth Blog Post', content: 'This is the content of the tenth blog post.' }, + ]; + + return return200WithObject(blogPosts); +}; \ No newline at end of file From 7e1a82090a3d3d6069dea7990c4b0c927945f0b9 Mon Sep 17 00:00:00 2001 From: Lane C Date: Thu, 19 Dec 2024 17:12:34 -0800 Subject: [PATCH 11/11] fix build error --- src/pages/api/v1/speed-test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/v1/speed-test.ts b/src/pages/api/v1/speed-test.ts index 1aac8b3c..90a4f566 100644 --- a/src/pages/api/v1/speed-test.ts +++ b/src/pages/api/v1/speed-test.ts @@ -1,5 +1,5 @@ import { return200WithObject } from '@services/return-types'; -import { APIRoute } from 'astro'; +import type { APIRoute } from "astro"; export const GET: APIRoute = async (context) => { const blogPosts = [