diff --git a/apps/web/eslint.config.js b/apps/web/eslint.config.js
index acfc754..022268c 100644
--- a/apps/web/eslint.config.js
+++ b/apps/web/eslint.config.js
@@ -38,15 +38,4 @@ export default [
// add more generic rule sets here, such as:
// js.configs.recommended,
...eslintPluginAstro.configs.recommended,
- {
- rules: {
- // override/add rules settings here, such as:
- // "astro/no-set-html-directive": "error"
- },
- languageOptions: {
- parserOptions: {
- project: "./tsconfig.json",
- },
- },
- },
];
diff --git a/apps/web/src/layouts/Layout.astro b/apps/web/src/layouts/Layout.astro
index 3567314..324fcb3 100644
--- a/apps/web/src/layouts/Layout.astro
+++ b/apps/web/src/layouts/Layout.astro
@@ -6,7 +6,7 @@
Cloudflare App with Astro | Atyantik Technologies
diff --git a/apps/web/src/pages/api/storage.ts b/apps/web/src/pages/api/storage.ts
deleted file mode 100644
index c575dce..0000000
--- a/apps/web/src/pages/api/storage.ts
+++ /dev/null
@@ -1,42 +0,0 @@
-import type { APIRoute } from "astro";
-import { listStorageRecords } from "@services/database";
-
-/**
- * POST route handler for uploading images to R2.
- */
-export const GET: APIRoute = async ({ locals }) => {
- try {
- const cache = locals.runtime.env.CACHE;
- const cachedStorageRecords = await cache.get("storage_records");
- if (cachedStorageRecords) {
- return new Response(cachedStorageRecords, {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- "X-Cache": "HIT",
- },
- });
- }
- const storageRecords = await listStorageRecords(locals.dbClient);
- await cache.put("storage_records", JSON.stringify(storageRecords));
- return new Response(JSON.stringify(storageRecords), {
- status: 200,
- headers: {
- "Content-Type": "application/json",
- "X-Cache": "MISS",
- },
- });
- } catch (ex) {
- return new Response(
- JSON.stringify({
- error: ex instanceof Error ? ex.message : "An error occurred",
- }),
- {
- status: 500,
- headers: { "Content-Type": "application/json" },
- },
- );
- }
-
- // Respond with the image URL
-};
diff --git a/apps/web/src/pages/gallery.astro b/apps/web/src/pages/gallery.astro
new file mode 100644
index 0000000..55f75e1
--- /dev/null
+++ b/apps/web/src/pages/gallery.astro
@@ -0,0 +1,91 @@
+---
+import Layout from "@layouts/Layout.astro";
+import { listStorageRecords } from "@services/database";
+
+const cache = Astro.locals.runtime.env.CACHE;
+let cachedStorageRecords = await cache.get("storage_records");
+if (!cachedStorageRecords) {
+ const storageRecords = await listStorageRecords(Astro.locals.dbClient);
+ await cache.put("storage_records", JSON.stringify(storageRecords));
+ cachedStorageRecords = JSON.stringify(storageRecords);
+}
+const records = JSON.parse(cachedStorageRecords) as Awaited<
+ ReturnType
+>;
+---
+
+
+
+
+
+
diff --git a/apps/web/src/pages/index.astro b/apps/web/src/pages/index.astro
index 1427761..d740f81 100644
--- a/apps/web/src/pages/index.astro
+++ b/apps/web/src/pages/index.astro
@@ -12,6 +12,9 @@ const serverTime = Date.now();
⚡ Blazing Fast, Budget Friendly, Built to Scale
+
+ Checkout Gallery Example
+
diff --git a/apps/web/src/utils/r2-storage.util.ts b/apps/web/src/utils/r2-storage.util.ts
index 961f9bb..fa17626 100644
--- a/apps/web/src/utils/r2-storage.util.ts
+++ b/apps/web/src/utils/r2-storage.util.ts
@@ -8,7 +8,7 @@ import type { R2Bucket } from "@cloudflare/workers-types";
*/
export function validateFile(file: File | null): File {
if (!file || !(file instanceof File)) {
- throw new Error("No image file provided or invalid file type.");
+ throw new Error("No file provided or invalid file type.");
}
return file;
}
@@ -50,25 +50,3 @@ export async function uploadFile(
},
});
}
-
-/**
- * Constructs the CDN URL based on the environment.
- * @param mode - The current environment mode ('production' or others).
- * @param cdnUrl - The CDN base URL from environment variables.
- * @param requestUrl - The original request URL.
- * @param key - The unique key of the uploaded file.
- * @returns The full CDN URL as a string.
- */
-export function constructCdnUrl(
- mode: string,
- cdnUrl: string | undefined,
- requestUrl: string,
- key: string,
-): string {
- const isProduction = mode === "production";
- const baseCdnUrl =
- isProduction && typeof cdnUrl === "string" && cdnUrl.length
- ? cdnUrl
- : new URL("/cdn/", requestUrl);
- return new URL(key, baseCdnUrl).toString();
-}
diff --git a/apps/web/src/pages/api/upload.ts b/apps/web/src/utils/upload.util.ts
similarity index 73%
rename from apps/web/src/pages/api/upload.ts
rename to apps/web/src/utils/upload.util.ts
index deac777..3488705 100644
--- a/apps/web/src/pages/api/upload.ts
+++ b/apps/web/src/utils/upload.util.ts
@@ -1,11 +1,5 @@
-import type { APIRoute } from "astro";
import type { R2Bucket } from "@cloudflare/workers-types";
-import {
- constructCdnUrl,
- fileExists,
- uploadFile,
- validateFile,
-} from "@utils/r2-storage.util";
+import { fileExists, uploadFile, validateFile } from "@utils/r2-storage.util";
import { computeShortHash } from "@utils/hash.util";
import type { DrizzleD1Database } from "drizzle-orm/d1";
import {
@@ -35,15 +29,12 @@ function generateKey(hashHex: string, fileName: string): string {
* @throws Error if any step fails.
*/
async function handleUpload(
- formData: FormData,
+ formFile: File,
storage: R2Bucket,
- cdnUrlEnv: string | undefined,
- mode: string,
- requestUrl: string,
db: DrizzleD1Database,
): Promise {
// Validate the image
- const file = validateFile(formData.get("file") as File);
+ const file = validateFile(formFile);
// Read the file content as ArrayBuffer
const arrayBuffer = await file.arrayBuffer();
@@ -79,7 +70,7 @@ async function handleUpload(
}
// Construct the CDN URL
- const fileUrl = constructCdnUrl(mode, cdnUrlEnv, requestUrl, key);
+ const fileUrl = `/cdn/${key}`;
return {
...fileFromKey,
url: fileUrl,
@@ -87,10 +78,13 @@ async function handleUpload(
}
/**
- * POST route handler for uploading images to R2.
+ * Handles the File Upload from the request.
+ * @param file - The uploaded file.
+ * @param locals - The request locals.
+ * @returns The URL of the uploaded image.
+ * @throws Error if any step fails.
*/
-export const POST: APIRoute = async ({ request, locals }) => {
- const { PUBLIC_CDN_URL } = locals.runtime.env;
+export const handleFile = async (file: File, locals: globalThis.App.Locals) => {
// @ts-expect-error we are using STORAGE from wrangler and types which has different
// signatures than the one from the worker
const storage = locals.runtime.env.STORAGE as R2Bucket;
@@ -99,28 +93,13 @@ export const POST: APIRoute = async ({ request, locals }) => {
throw new Error("You need to add storage binding to the environment.");
}
const { dbClient } = locals;
- const mode = import.meta.env.MODE;
- const requestUrl = request.url;
-
try {
- // Parse form data
- const formData = await request.formData();
// Handle the upload process
- const fileData = await handleUpload(
- formData,
- storage,
- PUBLIC_CDN_URL,
- mode,
- requestUrl,
- dbClient,
- );
+ const fileData = await handleUpload(file, storage, dbClient);
// Empty the cache
cache.delete("storage_records");
// Respond with the image URL
- return new Response(JSON.stringify(fileData), {
- status: 200,
- headers: { "Content-Type": "application/json" },
- });
+ return fileData;
} catch (error) {
let errorMessage = "Failed to upload image. Please try again later.";
let status = 500;
@@ -134,10 +113,6 @@ export const POST: APIRoute = async ({ request, locals }) => {
? error.message
: "Failed to upload image. Please try again later.";
}
-
- return new Response(JSON.stringify({ error: errorMessage }), {
- status,
- headers: { "Content-Type": "application/json" },
- });
+ throw new Error(errorMessage);
}
};
diff --git a/apps/worker/package.json b/apps/worker/package.json
index 95a4d73..c8a2811 100644
--- a/apps/worker/package.json
+++ b/apps/worker/package.json
@@ -4,7 +4,7 @@
"private": true,
"scripts": {
"deploy": "npm run setup && wrangler deploy",
- "dev": "npm run setup && wrangler dev --persist-to=../../.wrangler/state",
+ "dev": "npm run setup && wrangler dev --test-scheduled --persist-to=../../.wrangler/state",
"start": "npm run setup && wrangler dev",
"test": "npm run setup && CI=true vitest run",
"setup": "node ../../scripts/generate-wrangler.json.js && wrangler types"
diff --git a/apps/worker/src/index.ts b/apps/worker/src/index.ts
index 6444a05..ec8916c 100644
--- a/apps/worker/src/index.ts
+++ b/apps/worker/src/index.ts
@@ -43,20 +43,18 @@ export default {
ctx.waitUntil(
(async () => {
// Clear the storage every 5th minute
- if (new Date().getMinutes() % 5 === 0) {
+ if (event.cron.startsWith('*/5')) {
const DB = await getDBClient(this, env.DB);
const STORAGE = env.STORAGE;
const CACHE = env.CACHE;
- DB.transaction(async (tx) => {
- // Get all storage Records
- const storageRecords = await listStorageRecords(tx);
- // Remove each storage record from
- for (const record of storageRecords) {
- await STORAGE.delete(record.key);
- }
- await clearStorageRecords(tx);
- await CACHE.delete('storage_records');
- });
+ // Get all storage Records
+ const storageRecords = await listStorageRecords(DB);
+ // Remove each storage record from
+ for (const record of storageRecords) {
+ await STORAGE.delete(record.key);
+ }
+ await clearStorageRecords(DB);
+ await CACHE.delete('storage_records');
}
})(),
);