Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# VitNode

- Always write and run unit tests in vitest for all new features and bug fixes.
- Don't write unit tests if app doesn't have configured vitest.
- Don't use `React.FC` for defining React components. Instead, use the arrow function syntax.
- Don't use `any` type in TypeScript and use `unknown` as less as possible.
- Use `AutoForm` for forms instead of manually creating form components.
Expand Down
1 change: 0 additions & 1 deletion apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ CRON_SECRET=your-secure-cron-secret-key
# S3_PUBLIC_URL=https://cdn.example.com # optional CDN base
# --- Supabase Storage (@vitnode/supabase-storage) ---
# SUPABASE_URL=https://your-project.supabase.co
# SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
# SUPABASE_STORAGE_BUCKET=your-bucket

# === Docker Database Postgres ===
Expand Down
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@hono/zod-validator": "^0.8.0",
"@vitnode/core": "workspace:*",
"@vitnode/node-cron": "workspace:*",
"@vitnode/supabase-storage": "workspace:*",
"drizzle-kit": "^0.31.10",
"drizzle-orm": "^0.45.2",
"hono": "^4.12.27",
Expand Down
16 changes: 8 additions & 8 deletions apps/api/src/vitnode.api.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { blogApiPlugin } from "@vitnode/blog/config.api";
import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local";
// import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local";
import { buildApiConfig } from "@vitnode/core/vitnode.config";
import { NodeCronAdapter } from "@vitnode/node-cron";
import { NodemailerEmailAdapter } from "@vitnode/nodemailer";
// import { S3StorageAdapter } from "@vitnode/s3";
// import { SupabaseStorageAdapter } from "@vitnode/supabase-storage";
import { SupabaseStorageAdapter } from "@vitnode/supabase-storage";
import { config } from "dotenv";
import { drizzle } from "drizzle-orm/postgres-js";

Expand Down Expand Up @@ -40,7 +40,7 @@ export const vitNodeApiConfig = buildApiConfig({
},
storage: {
// Zero-config: writes to `public/uploads` and serves via Hono static files.
adapter: LocalStorageAdapter(),
// adapter: LocalStorageAdapter(),
// Re-encode uploaded images with sharp to shrink them before storing.
image: {
quality: 85,
Expand All @@ -53,11 +53,11 @@ export const vitNodeApiConfig = buildApiConfig({
// endpoint: process.env.S3_ENDPOINT, // Cloudflare R2 endpoint
// publicUrl: process.env.S3_PUBLIC_URL,
// }),
// adapter: SupabaseStorageAdapter({
// url: process.env.SUPABASE_URL,
// serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
// bucket: process.env.SUPABASE_STORAGE_BUCKET,
// }),
adapter: SupabaseStorageAdapter({
url: process.env.SUPABASE_URL,
secretKey: process.env.SUPABASE_SECRET_KEY,
bucket: process.env.SUPABASE_STORAGE_BUCKET,
}),
Comment thread
aXenDeveloper marked this conversation as resolved.
},
metadata: {
title: "VitNode API",
Expand Down
16 changes: 9 additions & 7 deletions apps/docs/content/docs/dev/storage/supabase.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: Supabase Storage
description: Store uploads in a Supabase Storage bucket.
---

| Cloud | Self-Hosted | Links |
| Cloud | Self-Hosted | Links |
| ------------ | ------------ | ----------------------------------------------------------------- |
| ✅ Supported | ✅ Supported | [NPM Package](https://www.npmjs.com/package/@supabase/storage-js) |

Expand Down Expand Up @@ -45,7 +45,7 @@ export const vitNodeApiConfig = buildApiConfig({
// [!code ++:5]
adapter: SupabaseStorageAdapter({
url: process.env.SUPABASE_URL,
serviceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
secretKey: process.env.SUPABASE_SECRET_KEY,
bucket: process.env.SUPABASE_STORAGE_BUCKET,
}),
},
Expand All @@ -56,12 +56,13 @@ export const vitNodeApiConfig = buildApiConfig({
<Step>
### Environment variables

The **service role key** is required so the server can write to the bucket. Keep
it secret — never expose it to the client.
A **secret key** (`sb_secret_…`) is required so the server can write to the
bucket. Keep it secret — never expose it to the client. Create one under
**Project Settings → API Keys**.

```bash title=".env"
SUPABASE_URL=https://your-project.supabase.co
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
SUPABASE_SECRET_KEY=sb_secret_...
SUPABASE_STORAGE_BUCKET=your-bucket
```

Expand All @@ -83,8 +84,9 @@ import { TypeTable } from "fumadocs-ui/components/type-table";
description: "Your Supabase project URL.",
type: "string",
},
serviceRoleKey: {
description: "Supabase service role key (server-side only).",
secretKey: {
description:
"Supabase secret key `sb_secret_…` (server-side only). Replaces the legacy service role key.",
type: "string",
},
bucket: {
Expand Down
7 changes: 5 additions & 2 deletions packages/s3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@
"build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json",
"dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint:fix": "eslint . --fix",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@aws-sdk/client-s3": "^3.0.0"
Expand All @@ -41,6 +43,7 @@
"concurrently": "^10.0.3",
"eslint": "^10.5.0",
"tsc-alias": "^1.8.17",
"typescript": "^6.0.3"
"typescript": "^6.0.3",
"vitest": "^4.1.9"
}
}
7 changes: 5 additions & 2 deletions packages/supabase-storage/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
"build:plugins": "tsc -p tsconfig.build.json && swc src -d dist --config-file .swcrc && tsc-alias -p tsconfig.build.json",
"dev:plugins": "concurrently \"tsc -w -p tsconfig.build.json --preserveWatchOutput\" \"swc src -d dist --config-file .swcrc -w\" \"tsc-alias -w -p tsconfig.build.json\"",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
"lint:fix": "eslint . --fix",
"test": "vitest run",
"test:watch": "vitest"
},
"dependencies": {
"@supabase/storage-js": "^2.0.0"
Expand All @@ -40,6 +42,7 @@
"concurrently": "^10.0.3",
"eslint": "^10.5.0",
"tsc-alias": "^1.8.17",
"typescript": "^6.0.3"
"typescript": "^6.0.3",
"vitest": "^4.1.9"
}
}
13 changes: 8 additions & 5 deletions packages/supabase-storage/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,28 @@ import type {

import { StorageClient } from "@supabase/storage-js";

// Uses a Supabase secret key (`sb_secret_…`), the modern replacement for the
// legacy `service_role` key. A still-valid legacy key works too — the value is
// sent verbatim as the `apikey`/`Authorization` header.
export const SupabaseStorageAdapter = ({
bucket = "",
serviceRoleKey = "",
secretKey = "",
url = "",
}: {
bucket: string | undefined;
serviceRoleKey: string | undefined;
secretKey: string | undefined;
url: string | undefined;
}): StorageApiPlugin => {
let client: StorageClient | undefined;

const getClient = (): StorageClient => {
if (!(url && serviceRoleKey && bucket)) {
if (!(url && secretKey && bucket)) {
throw new Error("Missing Supabase Storage configuration");
}

client ??= new StorageClient(`${url.replace(/\/$/, "")}/storage/v1`, {
apikey: serviceRoleKey,
Authorization: `Bearer ${serviceRoleKey}`,
apikey: secretKey,
Authorization: `Bearer ${secretKey}`,
});
Comment thread
aXenDeveloper marked this conversation as resolved.

return client;
Expand Down
50 changes: 48 additions & 2 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading