feat: add Railway-ready Jyotish chat product
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
const promptExtractionPatterns = [
|
||||
/(?:ignore|disregard).{0,24}(?:previous|system|developer).{0,24}(?:instruction|prompt)/i,
|
||||
/(?:忽略|无视).{0,20}(?:之前|以上|系统|开发者).{0,20}(?:指令|提示词)/,
|
||||
/(?:system prompt|developer message|api[ _-]?key|secret key)/i,
|
||||
/(?:系统提示词|开发者消息|密钥|skill\s*原文|技能原文)/i,
|
||||
];
|
||||
|
||||
export function blocksPromptExtraction(question: string) {
|
||||
const normalized = question.normalize("NFKC");
|
||||
return promptExtractionPatterns.some((pattern) => pattern.test(normalized));
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import "server-only";
|
||||
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import {
|
||||
getSupabaseUrl,
|
||||
SupabaseConfigurationError,
|
||||
} from "./config";
|
||||
|
||||
export function createAdminSupabaseClient() {
|
||||
const url = getSupabaseUrl();
|
||||
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||
if (!serviceRoleKey) {
|
||||
throw new SupabaseConfigurationError(["SUPABASE_SERVICE_ROLE_KEY"]);
|
||||
}
|
||||
|
||||
return createClient(url, serviceRoleKey, {
|
||||
auth: { autoRefreshToken: false, persistSession: false },
|
||||
});
|
||||
}
|
||||
|
||||
export function isAdminEmail(email: string | null | undefined) {
|
||||
const configured = process.env.ADMIN_EMAILS;
|
||||
if (!configured?.trim() || !email) return false;
|
||||
|
||||
const normalized = email.trim().toLowerCase();
|
||||
return configured
|
||||
.split(",")
|
||||
.some((candidate) => candidate.trim().toLowerCase() === normalized);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { createBrowserClient } from "@supabase/ssr";
|
||||
import { getSupabasePublicConfig } from "./config";
|
||||
|
||||
export function createBrowserSupabaseClient() {
|
||||
const { url, anonKey } = getSupabasePublicConfig();
|
||||
return createBrowserClient(url, anonKey);
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { createHash, randomBytes } from "node:crypto";
|
||||
|
||||
const CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||
|
||||
export function normalizeRedeemCode(code: string) {
|
||||
return code.replace(/\s/g, "").toUpperCase();
|
||||
}
|
||||
|
||||
export function hashRedeemCode(code: string) {
|
||||
return createHash("sha256").update(code, "utf8").digest("hex");
|
||||
}
|
||||
|
||||
export function generateRedeemCode() {
|
||||
const bytes = randomBytes(8);
|
||||
const token = Array.from(bytes, (byte) => CODE_ALPHABET[byte % CODE_ALPHABET.length]).join("");
|
||||
return `JYOTISH-${token.slice(0, 4)}-${token.slice(4)}`;
|
||||
}
|
||||
|
||||
export function maskRedeemCode(code: string) {
|
||||
return `JYOTISH-****-${code.slice(-4)}`;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
export class SupabaseConfigurationError extends Error {
|
||||
readonly code = "SUPABASE_NOT_CONFIGURED";
|
||||
|
||||
constructor(missing: string[]) {
|
||||
super(`Supabase 配置缺失:${missing.join(", ")}`);
|
||||
this.name = "SupabaseConfigurationError";
|
||||
}
|
||||
}
|
||||
|
||||
export function getSupabaseUrl() {
|
||||
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||
if (!url) throw new SupabaseConfigurationError(["NEXT_PUBLIC_SUPABASE_URL"]);
|
||||
return url;
|
||||
}
|
||||
|
||||
export function getSupabasePublicConfig() {
|
||||
const url = getSupabaseUrl();
|
||||
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
||||
if (!anonKey) {
|
||||
throw new SupabaseConfigurationError(["NEXT_PUBLIC_SUPABASE_ANON_KEY"]);
|
||||
}
|
||||
return { url, anonKey };
|
||||
}
|
||||
|
||||
export function isSupabaseConfigurationError(error: unknown) {
|
||||
return error instanceof SupabaseConfigurationError;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import "server-only";
|
||||
|
||||
import { createServerClient } from "@supabase/ssr";
|
||||
import { cookies } from "next/headers";
|
||||
import { getSupabasePublicConfig } from "./config";
|
||||
|
||||
export async function createServerSupabaseClient() {
|
||||
const { url, anonKey } = getSupabasePublicConfig();
|
||||
const cookieStore = await cookies();
|
||||
|
||||
return createServerClient(url, anonKey, {
|
||||
cookies: {
|
||||
getAll: () => cookieStore.getAll(),
|
||||
setAll: (cookiesToSet) => {
|
||||
try {
|
||||
cookiesToSet.forEach(({ name, value, options }) => {
|
||||
cookieStore.set(name, value, options);
|
||||
});
|
||||
} catch {
|
||||
// Server Components cannot write cookies; Route Handlers can.
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user