Prepare production and Coolify deployment with original exports and datetime pickers

This commit is contained in:
2026-09-09 17:59:20 -04:00
parent 574f29a68e
commit 64d7acc9c0
38 changed files with 1296 additions and 8 deletions
+24
View File
@@ -0,0 +1,24 @@
import { HeadBucketCommand, PutBucketCorsCommand, PutBucketLifecycleConfigurationCommand, S3Client } from "@aws-sdk/client-s3";
// Explicit deployment-only initialization, never run implicitly by web requests.
const origin = new URL(process.env.NEXT_PUBLIC_APP_URL!).origin;
if (!origin.startsWith("https://")) throw new Error("Storage CORS requires an HTTPS app origin");
const bucket = process.env.S3_BUCKET!;
if (!bucket || !process.env.S3_ACCESS_KEY || !process.env.S3_SECRET_KEY) throw new Error("Missing storage configuration");
const client = new S3Client({
endpoint: process.env.S3_ENDPOINT,
region: process.env.S3_REGION,
forcePathStyle: true,
credentials: { accessKeyId: process.env.S3_ACCESS_KEY, secretAccessKey: process.env.S3_SECRET_KEY },
requestChecksumCalculation: "WHEN_REQUIRED",
});
await client.send(new HeadBucketCommand({ Bucket: bucket }));
await client.send(new PutBucketCorsCommand({ Bucket: bucket, CORSConfiguration: { CORSRules: [{
AllowedOrigins: [origin], AllowedMethods: ["GET", "PUT", "HEAD"], AllowedHeaders: ["*"],
ExposeHeaders: ["ETag", "Content-Length"], MaxAgeSeconds: 3600,
}] } }));
await client.send(new PutBucketLifecycleConfigurationCommand({ Bucket: bucket, LifecycleConfiguration: { Rules: [
{ ID: "expire-exports", Status: "Enabled", Filter: { Prefix: "exports/" }, Expiration: { Days: 1 } },
{ ID: "abort-incomplete", Status: "Enabled", Filter: { Prefix: "" }, AbortIncompleteMultipartUpload: { DaysAfterInitiation: 1 } },
] } }));
console.log("Storage CORS and lifecycle configured.");
+3
View File
@@ -2,6 +2,9 @@ export {
createPresignedGetUrl,
createPresignedPutUrl,
deletePrefix,
deleteObject,
getSigningClient,
storageConfig,
ensureBucket,
getObjectBuffer,
headObject,
+12 -2
View File
@@ -1,6 +1,7 @@
import {
CreateBucketCommand,
DeleteObjectsCommand,
DeleteObjectCommand,
GetObjectCommand,
HeadBucketCommand,
HeadObjectCommand,
@@ -67,6 +68,11 @@ export async function ensureBucket() {
if (bucketReady) return;
const client = getSigningClient();
const { bucket } = storageConfig();
if (process.env.NODE_ENV === "production") {
await client.send(new HeadBucketCommand({ Bucket: bucket }));
bucketReady = true;
return;
}
try {
await client.send(new HeadBucketCommand({ Bucket: bucket }));
} catch {
@@ -112,7 +118,7 @@ export async function createPresignedPutUrl(input: {
);
}
export async function createPresignedGetUrl(key: string) {
export async function createPresignedGetUrl(key: string, expiresIn = PRESIGN_GET_SECONDS) {
await ensureBucket();
const { bucket } = storageConfig();
return getSignedUrl(
@@ -121,10 +127,14 @@ export async function createPresignedGetUrl(key: string) {
Bucket: bucket,
Key: key,
}),
{ expiresIn: PRESIGN_GET_SECONDS },
{ expiresIn },
);
}
export async function deleteObject(key: string) {
await getSigningClient().send(new DeleteObjectCommand({ Bucket: storageConfig().bucket, Key: key }));
}
export async function headObject(
key: string,
): Promise<HeadObjectCommandOutput | null> {