feat(core): setup postgres, minio, and authentik next-auth
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import type { StorageService } from "../types";
|
||||
|
||||
export class DatabaseStorageAdapter implements StorageService {
|
||||
async get<T>(namespace: string, key: string): Promise<T | null> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/storage?namespace=${encodeURIComponent(namespace)}&key=${encodeURIComponent(key)}`,
|
||||
);
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
return data.value as T | null;
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter get error:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async set<T>(namespace: string, key: string, value: T): Promise<void> {
|
||||
try {
|
||||
await fetch("/api/storage", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ namespace, key, value }),
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter set error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(namespace: string, key: string): Promise<void> {
|
||||
try {
|
||||
await fetch(
|
||||
`/api/storage?namespace=${encodeURIComponent(namespace)}&key=${encodeURIComponent(key)}`,
|
||||
{
|
||||
method: "DELETE",
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter delete error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async list(namespace: string): Promise<string[]> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
|
||||
);
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return Object.keys(data.items || {});
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter list error:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async query<T>(
|
||||
namespace: string,
|
||||
predicate: (item: T) => boolean,
|
||||
): Promise<T[]> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
|
||||
);
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
const items = Object.values(data.items || {}) as T[];
|
||||
return items.filter(predicate);
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter query error:", error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async clear(namespace: string): Promise<void> {
|
||||
try {
|
||||
await fetch(`/api/storage?namespace=${encodeURIComponent(namespace)}`, {
|
||||
method: "DELETE",
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter clear error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
async export(namespace: string): Promise<Record<string, unknown>> {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`/api/storage?namespace=${encodeURIComponent(namespace)}`,
|
||||
);
|
||||
if (!res.ok) return {};
|
||||
const data = await res.json();
|
||||
return data.items || {};
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter export error:", error);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
async import(
|
||||
namespace: string,
|
||||
data: Record<string, unknown>,
|
||||
): Promise<void> {
|
||||
try {
|
||||
// Import items one by one (or we could create a bulk endpoint, but this is fine for now)
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
await this.set(namespace, key, value);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("DatabaseStorageAdapter import error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Client } from "minio";
|
||||
|
||||
const globalForMinio = globalThis as unknown as {
|
||||
minioClient: Client | undefined;
|
||||
};
|
||||
|
||||
export const minioClient =
|
||||
globalForMinio.minioClient ??
|
||||
new Client({
|
||||
endPoint: process.env.MINIO_ENDPOINT || "localhost",
|
||||
port: parseInt(process.env.MINIO_PORT || "9000"),
|
||||
useSSL: process.env.MINIO_USE_SSL === "true",
|
||||
accessKey: process.env.MINIO_ACCESS_KEY || "",
|
||||
secretKey: process.env.MINIO_SECRET_KEY || "",
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production")
|
||||
globalForMinio.minioClient = minioClient;
|
||||
|
||||
export const MINIO_BUCKET_NAME = process.env.MINIO_BUCKET_NAME || "tools";
|
||||
|
||||
// Helper to ensure bucket exists
|
||||
export async function ensureBucketExists() {
|
||||
try {
|
||||
const exists = await minioClient.bucketExists(MINIO_BUCKET_NAME);
|
||||
if (!exists) {
|
||||
await minioClient.makeBucket(MINIO_BUCKET_NAME, "eu-west-1");
|
||||
console.log(`Bucket '${MINIO_BUCKET_NAME}' created successfully.`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking/creating MinIO bucket:", error);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined;
|
||||
};
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ??
|
||||
new PrismaClient({
|
||||
log:
|
||||
process.env.NODE_ENV === "development"
|
||||
? ["query", "error", "warn"]
|
||||
: ["error"],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
@@ -1,14 +1,19 @@
|
||||
'use client';
|
||||
"use client";
|
||||
|
||||
import { createContext, useContext, useMemo } from 'react';
|
||||
import type { StorageService } from './types';
|
||||
import { LocalStorageAdapter } from './adapters/local-storage';
|
||||
import { createContext, useContext, useMemo } from "react";
|
||||
import type { StorageService } from "./types";
|
||||
import { LocalStorageAdapter } from "./adapters/local-storage";
|
||||
import { DatabaseStorageAdapter } from "./adapters/database-adapter";
|
||||
|
||||
const StorageContext = createContext<StorageService | null>(null);
|
||||
|
||||
function createAdapter(): StorageService {
|
||||
// Future: select adapter based on environment variable
|
||||
// const adapterType = process.env.NEXT_PUBLIC_STORAGE_ADAPTER;
|
||||
const adapterType = process.env.NEXT_PUBLIC_STORAGE_ADAPTER;
|
||||
|
||||
if (adapterType === "database") {
|
||||
return new DatabaseStorageAdapter();
|
||||
}
|
||||
|
||||
return new LocalStorageAdapter();
|
||||
}
|
||||
|
||||
@@ -28,6 +33,7 @@ export function StorageProvider({ children }: StorageProviderProps) {
|
||||
|
||||
export function useStorageService(): StorageService {
|
||||
const ctx = useContext(StorageContext);
|
||||
if (!ctx) throw new Error('useStorageService must be used within StorageProvider');
|
||||
if (!ctx)
|
||||
throw new Error("useStorageService must be used within StorageProvider");
|
||||
return ctx;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user