initial commit

This commit is contained in:
root
2026-05-29 17:07:00 +02:00
commit c935e39fa1
30 changed files with 1263 additions and 0 deletions
+27
View File
@@ -0,0 +1,27 @@
import { createClient, SupabaseClient } from '@supabase/supabase-js';
let _admin: SupabaseClient | null = null;
export function getSupabaseAdmin(): SupabaseClient {
if (_admin) return _admin;
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!url || !key) {
throw new Error('Supabase admin env not configured');
}
_admin = createClient(url, key, {
auth: { autoRefreshToken: false, persistSession: false },
});
return _admin;
}
export function getSupabaseAnon(): SupabaseClient {
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
if (!url || !key) {
throw new Error('Supabase anon env not configured');
}
return createClient(url, key, {
auth: { autoRefreshToken: false, persistSession: false },
});
}
+10
View File
@@ -0,0 +1,10 @@
'use client';
import { createBrowserClient } from '@supabase/ssr';
export function createSupabaseBrowserClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
);
}
+26
View File
@@ -0,0 +1,26 @@
import { createServerClient } from '@supabase/ssr';
import { cookies } from 'next/headers';
export function createSupabaseServerClient() {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(toSet) {
try {
toSet.forEach(({ name, value, options }) => {
cookieStore.set(name, value, options);
});
} catch {
// Called from a Server Component — middleware will refresh.
}
},
},
},
);
}
+30
View File
@@ -0,0 +1,30 @@
export const RESERVED_SUBDOMAINS = new Set([
'app',
'api',
'www',
'admin',
'auth',
'mail',
'static',
]);
const SUBDOMAIN_RE = /^[a-z0-9-]{3,32}$/;
export function validateSubdomain(input: unknown):
| { ok: true; value: string }
| { ok: false; error: string } {
if (typeof input !== 'string') {
return { ok: false, error: 'subdomain must be a string' };
}
const value = input.trim().toLowerCase();
if (!SUBDOMAIN_RE.test(value)) {
return {
ok: false,
error: 'subdomain must be 332 chars, lowercase az, 09, hyphen',
};
}
if (RESERVED_SUBDOMAINS.has(value)) {
return { ok: false, error: `'${value}' is reserved` };
}
return { ok: true, value };
}