- Apply Biome organize-import/formatting fixes across changed S3 files - Replace remaining string concatenations with template literals for lint - Make home page inline handlers explicit via window.* and add button types - Clean S3 auth lint issues with dot-property access and optional chaining - Keep GetObject proxy and production/S3 SDK tests passing Verification: - bun run lint (0 errors, 1 CSS specificity warning) - S3_SECRET_KEY=<env> bun test test/production-e2e.test.ts (29 pass) - S3_SECRET_KEY=<env> bun test test/s3-sdk.test.ts (20 pass) - bun test test/s3-auth.test.ts (5 pass)
82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import { sql } from 'drizzle-orm';
|
|
import { db } from './index';
|
|
|
|
export interface Bucket {
|
|
id: string;
|
|
name: string;
|
|
createdAt: Date;
|
|
updatedAt: Date;
|
|
}
|
|
|
|
type QueryRow = Record<string, unknown>;
|
|
type QueryResult = QueryRow[];
|
|
|
|
export const createBucket = async (name: string): Promise<Bucket> => {
|
|
const result = (await db.execute(
|
|
sql`INSERT INTO buckets (name) VALUES (${name}) RETURNING id, name, created_at, updated_at`,
|
|
)) as unknown as QueryResult;
|
|
const row = result[0]!;
|
|
return {
|
|
id: row.id as string,
|
|
name: row.name as string,
|
|
createdAt: new Date(row.created_at as string),
|
|
updatedAt: new Date(row.updated_at as string),
|
|
};
|
|
};
|
|
|
|
export const findBucketByName = async (name: string): Promise<Bucket | null> => {
|
|
const result = (await db.execute(
|
|
sql`SELECT id, name, created_at, updated_at FROM buckets WHERE name = ${name}`,
|
|
)) as unknown as QueryResult;
|
|
if (result.length === 0) return null;
|
|
const row = result[0]!;
|
|
return {
|
|
id: row.id as string,
|
|
name: row.name as string,
|
|
createdAt: new Date(row.created_at as string),
|
|
updatedAt: new Date(row.updated_at as string),
|
|
};
|
|
};
|
|
|
|
export const listBuckets = async (): Promise<Bucket[]> => {
|
|
const result = (await db.execute(
|
|
sql`SELECT id, name, created_at, updated_at FROM buckets ORDER BY name`,
|
|
)) as unknown as QueryResult;
|
|
return result.map((row) => ({
|
|
id: row.id as string,
|
|
name: row.name as string,
|
|
createdAt: new Date(row.created_at as string),
|
|
updatedAt: new Date(row.updated_at as string),
|
|
}));
|
|
};
|
|
|
|
export const deleteBucket = async (name: string): Promise<boolean> => {
|
|
// Cascade-delete rows that hold FK references to the bucket
|
|
await db
|
|
.execute(
|
|
sql`DELETE FROM multipart_parts WHERE upload_id IN (SELECT upload_id FROM multipart_uploads WHERE bucket_id IN (SELECT id FROM buckets WHERE name = ${name}))`,
|
|
)
|
|
.catch(() => {});
|
|
await db
|
|
.execute(
|
|
sql`DELETE FROM multipart_uploads WHERE bucket_id IN (SELECT id FROM buckets WHERE name = ${name})`,
|
|
)
|
|
.catch(() => {});
|
|
await db
|
|
.execute(
|
|
sql`DELETE FROM files WHERE bucket_id IN (SELECT id FROM buckets WHERE name = ${name})`,
|
|
)
|
|
.catch(() => {});
|
|
const result = (await db.execute(
|
|
sql`DELETE FROM buckets WHERE name = ${name}`,
|
|
)) as unknown as QueryResult;
|
|
return result.length > 0;
|
|
};
|
|
|
|
export const bucketExists = async (name: string): Promise<boolean> => {
|
|
const result = (await db.execute(
|
|
sql`SELECT 1 FROM buckets WHERE name = ${name}`,
|
|
)) as unknown as QueryResult;
|
|
return result.length > 0;
|
|
};
|