feat(db): mesh data model — meshes, members, invites, audit log
- pgSchema "mesh" with 4 tables isolating the peer mesh domain - Enums: visibility, transport, tier, role - audit_log is metadata-only (E2E encryption enforced at broker/client) - Cascade on mesh delete, soft-delete via archivedAt/revokedAt Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1
packages/storage/src/env.ts
Normal file
1
packages/storage/src/env.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./providers/env";
|
||||
13
packages/storage/src/lib/schema.ts
Normal file
13
packages/storage/src/lib/schema.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as z from "zod";
|
||||
|
||||
import { env } from "../env";
|
||||
|
||||
export const getObjectUrlSchema = z.object({
|
||||
path: z.string(),
|
||||
bucket: z
|
||||
.string()
|
||||
.optional()
|
||||
.default(env.S3_BUCKET ?? ""),
|
||||
});
|
||||
|
||||
export type GetObjectUrlInput = z.input<typeof getObjectUrlSchema>;
|
||||
1
packages/storage/src/providers/env.ts
Normal file
1
packages/storage/src/providers/env.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./s3/env";
|
||||
1
packages/storage/src/providers/index.ts
Normal file
1
packages/storage/src/providers/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./s3";
|
||||
27
packages/storage/src/providers/s3/client.ts
Normal file
27
packages/storage/src/providers/s3/client.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { S3Client } from "@aws-sdk/client-s3";
|
||||
|
||||
import { env } from "../../env";
|
||||
|
||||
let s3Client: S3Client | null = null;
|
||||
|
||||
export const getClient = () => {
|
||||
if (s3Client) {
|
||||
return s3Client;
|
||||
}
|
||||
|
||||
if (!env.S3_ACCESS_KEY_ID || !env.S3_SECRET_ACCESS_KEY) {
|
||||
throw new Error("S3_ACCESS_KEY_ID and S3_SECRET_ACCESS_KEY are required when using S3 storage");
|
||||
}
|
||||
|
||||
s3Client = new S3Client({
|
||||
forcePathStyle: true,
|
||||
region: env.S3_REGION,
|
||||
endpoint: env.S3_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: env.S3_ACCESS_KEY_ID,
|
||||
secretAccessKey: env.S3_SECRET_ACCESS_KEY,
|
||||
},
|
||||
});
|
||||
|
||||
return s3Client;
|
||||
};
|
||||
22
packages/storage/src/providers/s3/env.ts
Normal file
22
packages/storage/src/providers/s3/env.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { defineEnv } from "envin";
|
||||
import * as z from "zod";
|
||||
|
||||
import { envConfig } from "@turbostarter/shared/constants";
|
||||
|
||||
import type { Preset } from "envin/types";
|
||||
|
||||
export const preset = {
|
||||
id: "s3",
|
||||
server: {
|
||||
S3_BUCKET: z.string().optional(),
|
||||
S3_REGION: z.string().optional().default("us-east-1"),
|
||||
S3_ENDPOINT: z.string().optional(),
|
||||
S3_ACCESS_KEY_ID: z.string().optional(),
|
||||
S3_SECRET_ACCESS_KEY: z.string().optional(),
|
||||
},
|
||||
} as const satisfies Preset;
|
||||
|
||||
export const env = defineEnv({
|
||||
...envConfig,
|
||||
...preset,
|
||||
});
|
||||
94
packages/storage/src/providers/s3/index.ts
Normal file
94
packages/storage/src/providers/s3/index.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
DeleteObjectCommand,
|
||||
GetObjectCommand,
|
||||
PutObjectCommand,
|
||||
} from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl as getSignedUrlCommand } from "@aws-sdk/s3-request-presigner";
|
||||
|
||||
import { getObjectUrlSchema } from "../../lib/schema";
|
||||
|
||||
import { getClient } from "./client";
|
||||
|
||||
import type { GetObjectUrlInput } from "../../lib/schema";
|
||||
import type { StorageProviderStrategy } from "../types";
|
||||
|
||||
// Helper to apply schema defaults (bucket from env)
|
||||
const withDefaults = (input: GetObjectUrlInput) => getObjectUrlSchema.parse(input);
|
||||
|
||||
export const { getUploadUrl, getSignedUrl, getPublicUrl, getDeleteUrl } = {
|
||||
getUploadUrl: async (input: GetObjectUrlInput) => {
|
||||
const { path, bucket } = withDefaults(input);
|
||||
const client = getClient();
|
||||
|
||||
const url = await getSignedUrlCommand(
|
||||
client,
|
||||
new PutObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: path,
|
||||
}),
|
||||
{
|
||||
expiresIn: 60,
|
||||
},
|
||||
);
|
||||
|
||||
return { url };
|
||||
},
|
||||
getSignedUrl: async (input: GetObjectUrlInput) => {
|
||||
const { path, bucket } = withDefaults(input);
|
||||
const client = getClient();
|
||||
|
||||
const url = await getSignedUrlCommand(
|
||||
client,
|
||||
new GetObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: path,
|
||||
}),
|
||||
{
|
||||
expiresIn: 3600,
|
||||
},
|
||||
);
|
||||
|
||||
return { url };
|
||||
},
|
||||
getPublicUrl: async (input: GetObjectUrlInput) => {
|
||||
const { path, bucket } = withDefaults(input);
|
||||
const client = getClient();
|
||||
const endpoint = await client.config.endpoint?.();
|
||||
const forcePathStyle = client.config.forcePathStyle;
|
||||
|
||||
if (endpoint?.hostname.includes("supabase.co")) {
|
||||
return {
|
||||
url: `${endpoint.protocol}//${endpoint.hostname}/storage/v1/object/public/${bucket}/${path}`,
|
||||
};
|
||||
}
|
||||
|
||||
// Use path-style URL for MinIO and other S3-compatible storage (forcePathStyle: true)
|
||||
if (forcePathStyle) {
|
||||
const port = endpoint?.port ? `:${endpoint.port}` : "";
|
||||
return {
|
||||
url: `${endpoint?.protocol}//${endpoint?.hostname}${port}/${bucket}/${path}`,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
url: `${endpoint?.protocol}//${bucket}.${endpoint?.hostname}/${path}`,
|
||||
};
|
||||
},
|
||||
getDeleteUrl: async (input: GetObjectUrlInput) => {
|
||||
const { path, bucket } = withDefaults(input);
|
||||
const client = getClient();
|
||||
|
||||
const url = await getSignedUrlCommand(
|
||||
client,
|
||||
new DeleteObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: path,
|
||||
}),
|
||||
{
|
||||
expiresIn: 60,
|
||||
},
|
||||
);
|
||||
|
||||
return { url };
|
||||
},
|
||||
} satisfies StorageProviderStrategy;
|
||||
8
packages/storage/src/providers/types.ts
Normal file
8
packages/storage/src/providers/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { GetObjectUrlInput } from "../lib/schema";
|
||||
|
||||
export interface StorageProviderStrategy {
|
||||
getUploadUrl: (data: GetObjectUrlInput) => Promise<{ url: string }>;
|
||||
getSignedUrl: (data: GetObjectUrlInput) => Promise<{ url: string }>;
|
||||
getPublicUrl: (data: GetObjectUrlInput) => Promise<{ url: string }>;
|
||||
getDeleteUrl: (data: GetObjectUrlInput) => Promise<{ url: string }>;
|
||||
}
|
||||
8
packages/storage/src/server.ts
Normal file
8
packages/storage/src/server.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export {
|
||||
getUploadUrl,
|
||||
getDeleteUrl,
|
||||
getPublicUrl,
|
||||
getSignedUrl,
|
||||
} from "./providers";
|
||||
|
||||
export * from "./lib/schema";
|
||||
Reference in New Issue
Block a user