mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
feat(backend): add standalone data plane
Replace the Supabase application data plane with the Node/PostgreSQL API, migrate mobile, admin, web, and MCP callers, add controlled cutover tooling, and define hardened Dokploy infrastructure.
This commit is contained in:
1 parent
01bb8225ae
commit
006761d3b0
294 files changed
+16259
-6138
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { supabase } from '@/config';
|
||||
import { auditedSupabase } from '@/hooks/useAuditedSupabase';
|
||||
import { adminData } from '@/lib/adminApi';
|
||||
import { auditedAdminData } from '@/hooks/useAuditedAdminData';
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -32,7 +32,7 @@ export interface CategoryFormData {
|
||||
*/
|
||||
export async function fetchCategories(): Promise<Category[]> {
|
||||
// Fetch categories
|
||||
const { data: categories, error: catError } = await supabase
|
||||
const { data: categories, error: catError } = await adminData
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.order('sort_order', { ascending: true });
|
||||
@@ -40,7 +40,7 @@ export async function fetchCategories(): Promise<Category[]> {
|
||||
if (catError) throw catError;
|
||||
|
||||
// Fetch pack counts per category
|
||||
const { data: packs } = await supabase
|
||||
const { data: packs } = await adminData
|
||||
.from('question_packs')
|
||||
.select('category_id');
|
||||
|
||||
@@ -61,7 +61,7 @@ export async function fetchCategories(): Promise<Category[]> {
|
||||
* Fetch a single category by ID
|
||||
*/
|
||||
export async function fetchCategoryById(id: string): Promise<Category | null> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('categories')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
@@ -82,7 +82,7 @@ export async function createCategory(
|
||||
data: CategoryFormData,
|
||||
sortOrder: number
|
||||
): Promise<Category> {
|
||||
const { data: created, error } = await auditedSupabase.insert('categories', {
|
||||
const { data: created, error } = await auditedAdminData.insert('categories', {
|
||||
name: data.name,
|
||||
description: data.description || null,
|
||||
icon: data.icon || null,
|
||||
@@ -102,7 +102,7 @@ export async function updateCategory(
|
||||
id: string,
|
||||
data: Partial<CategoryFormData>
|
||||
): Promise<void> {
|
||||
const { error } = await auditedSupabase.update('categories', id, {
|
||||
const { error } = await auditedAdminData.update('categories', id, {
|
||||
name: data.name,
|
||||
description: data.description || null,
|
||||
icon: data.icon || null,
|
||||
@@ -116,7 +116,7 @@ export async function updateCategory(
|
||||
* Delete a category
|
||||
*/
|
||||
export async function deleteCategory(id: string): Promise<void> {
|
||||
const { error } = await auditedSupabase.delete('categories', id);
|
||||
const { error } = await auditedAdminData.delete('categories', id);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function swapCategorySortOrder(
|
||||
category2Order: number
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
auditedSupabase.update('categories', category1Id, { sort_order: category2Order }),
|
||||
auditedSupabase.update('categories', category2Id, { sort_order: category1Order }),
|
||||
auditedAdminData.update('categories', category1Id, { sort_order: category2Order }),
|
||||
auditedAdminData.update('categories', category2Id, { sort_order: category1Order }),
|
||||
]);
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { supabase } from '@/config';
|
||||
import { auditedSupabase } from '@/hooks/useAuditedSupabase';
|
||||
import { adminData } from '@/lib/adminApi';
|
||||
import { auditedAdminData } from '@/hooks/useAuditedAdminData';
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -42,7 +42,7 @@ export interface Topic {
|
||||
* Fetch all packs for a category with question counts
|
||||
*/
|
||||
export async function fetchPacksForCategory(categoryId: string): Promise<Pack[]> {
|
||||
const { data: packs, error: packError } = await supabase
|
||||
const { data: packs, error: packError } = await adminData
|
||||
.from('question_packs')
|
||||
.select('*, category:categories(name)')
|
||||
.eq('category_id', categoryId)
|
||||
@@ -51,7 +51,7 @@ export async function fetchPacksForCategory(categoryId: string): Promise<Pack[]>
|
||||
if (packError) throw packError;
|
||||
|
||||
// Get question counts per pack
|
||||
const { data: questions } = await supabase
|
||||
const { data: questions } = await adminData
|
||||
.from('questions')
|
||||
.select('pack_id');
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function fetchPacksForCategory(categoryId: string): Promise<Pack[]>
|
||||
* Fetch a single pack by ID
|
||||
*/
|
||||
export async function fetchPackById(id: string): Promise<Pack | null> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('question_packs')
|
||||
.select('*, category:categories(name)')
|
||||
.eq('id', id)
|
||||
@@ -86,7 +86,7 @@ export async function fetchPackById(id: string): Promise<Pack | null> {
|
||||
* Fetch all topics
|
||||
*/
|
||||
export async function fetchAllTopics(): Promise<Topic[]> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('topics')
|
||||
.select('*')
|
||||
.order('name');
|
||||
@@ -99,7 +99,7 @@ export async function fetchAllTopics(): Promise<Topic[]> {
|
||||
* Fetch topic IDs for a specific pack
|
||||
*/
|
||||
export async function fetchPackTopicIds(packId: string): Promise<Set<string>> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('pack_topics')
|
||||
.select('topic_id')
|
||||
.eq('pack_id', packId);
|
||||
@@ -120,7 +120,7 @@ export async function createPack(
|
||||
data: PackFormData,
|
||||
sortOrder: number
|
||||
): Promise<Pack> {
|
||||
const { data: created, error } = await auditedSupabase.insert('question_packs', {
|
||||
const { data: created, error } = await auditedAdminData.insert('question_packs', {
|
||||
name: data.name,
|
||||
description: data.description || null,
|
||||
icon: data.icon || null,
|
||||
@@ -143,7 +143,7 @@ export async function updatePack(
|
||||
id: string,
|
||||
data: Partial<PackFormData>
|
||||
): Promise<void> {
|
||||
const { error } = await auditedSupabase.update('question_packs', id, {
|
||||
const { error } = await auditedAdminData.update('question_packs', id, {
|
||||
name: data.name,
|
||||
description: data.description || null,
|
||||
icon: data.icon || null,
|
||||
@@ -159,7 +159,7 @@ export async function updatePack(
|
||||
* Delete a pack
|
||||
*/
|
||||
export async function deletePack(id: string): Promise<void> {
|
||||
const { error } = await auditedSupabase.delete('question_packs', id);
|
||||
const { error } = await auditedAdminData.delete('question_packs', id);
|
||||
if (error) throw error;
|
||||
}
|
||||
|
||||
@@ -173,8 +173,8 @@ export async function swapPackSortOrder(
|
||||
pack2Order: number
|
||||
): Promise<void> {
|
||||
await Promise.all([
|
||||
auditedSupabase.update('question_packs', pack1Id, { sort_order: pack2Order }),
|
||||
auditedSupabase.update('question_packs', pack2Id, { sort_order: pack1Order }),
|
||||
auditedAdminData.update('question_packs', pack1Id, { sort_order: pack2Order }),
|
||||
auditedAdminData.update('question_packs', pack2Id, { sort_order: pack1Order }),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -190,11 +190,11 @@ export async function updatePackTopics(
|
||||
topicIds: string[]
|
||||
): Promise<void> {
|
||||
// Delete existing topics
|
||||
await supabase.from('pack_topics').delete().eq('pack_id', packId);
|
||||
await adminData.from('pack_topics').delete().eq('pack_id', packId);
|
||||
|
||||
// Insert new topics
|
||||
if (topicIds.length > 0) {
|
||||
const { error } = await supabase.from('pack_topics').insert(
|
||||
const { error } = await adminData.from('pack_topics').insert(
|
||||
topicIds.map(topicId => ({ pack_id: packId, topic_id: topicId }))
|
||||
);
|
||||
if (error) throw error;
|
||||
@@ -205,7 +205,7 @@ export async function updatePackTopics(
|
||||
* Create a new topic
|
||||
*/
|
||||
export async function createTopic(name: string): Promise<Topic> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('topics')
|
||||
.insert({ name })
|
||||
.select()
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { supabase } from '@/config';
|
||||
import { auditedSupabase } from '@/hooks/useAuditedSupabase';
|
||||
import { adminData } from '@/lib/adminApi';
|
||||
import { auditedAdminData } from '@/hooks/useAuditedAdminData';
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -29,7 +29,7 @@ export interface QuestionFormData {
|
||||
* Fetch all questions for a pack (excludes soft-deleted by default)
|
||||
*/
|
||||
export async function fetchQuestionsForPack(packId: string, includeDeleted = false): Promise<Question[]> {
|
||||
let query = supabase
|
||||
let query = adminData
|
||||
.from('questions')
|
||||
.select('*')
|
||||
.eq('pack_id', packId)
|
||||
@@ -49,7 +49,7 @@ export async function fetchQuestionsForPack(packId: string, includeDeleted = fal
|
||||
* Fetch a single question by ID
|
||||
*/
|
||||
export async function fetchQuestionById(id: string): Promise<Question | null> {
|
||||
const { data, error } = await supabase
|
||||
const { data, error } = await adminData
|
||||
.from('questions')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
@@ -63,7 +63,7 @@ export async function fetchQuestionById(id: string): Promise<Question | null> {
|
||||
* Fetch question count for a pack (excludes soft-deleted)
|
||||
*/
|
||||
export async function fetchQuestionCount(packId: string): Promise<number> {
|
||||
const { count, error } = await supabase
|
||||
const { count, error } = await adminData
|
||||
.from('questions')
|
||||
.select('*', { count: 'exact', head: true })
|
||||
.eq('pack_id', packId)
|
||||
@@ -84,7 +84,7 @@ export async function createQuestion(
|
||||
packId: string,
|
||||
data: QuestionFormData
|
||||
): Promise<Question> {
|
||||
const { data: created, error } = await auditedSupabase.insert('questions', {
|
||||
const { data: created, error } = await auditedAdminData.insert('questions', {
|
||||
text: data.text,
|
||||
partner_text: data.partner_text || null,
|
||||
intensity: data.intensity,
|
||||
@@ -103,7 +103,7 @@ export async function updateQuestion(
|
||||
id: string,
|
||||
data: Partial<QuestionFormData>
|
||||
): Promise<void> {
|
||||
const { error } = await auditedSupabase.update('questions', id, {
|
||||
const { error } = await auditedAdminData.update('questions', id, {
|
||||
text: data.text,
|
||||
partner_text: data.partner_text || null,
|
||||
intensity: data.intensity,
|
||||
@@ -116,7 +116,7 @@ export async function updateQuestion(
|
||||
* Soft delete a question (sets deleted_at timestamp)
|
||||
*/
|
||||
export async function deleteQuestion(id: string): Promise<void> {
|
||||
const { error } = await auditedSupabase.update('questions', id, {
|
||||
const { error } = await auditedAdminData.update('questions', id, {
|
||||
deleted_at: new Date().toISOString(),
|
||||
});
|
||||
if (error) throw error;
|
||||
@@ -127,7 +127,7 @@ export async function deleteQuestion(id: string): Promise<void> {
|
||||
*/
|
||||
export async function deleteQuestions(ids: string[]): Promise<void> {
|
||||
await Promise.all(
|
||||
ids.map(id => auditedSupabase.update('questions', id, {
|
||||
ids.map(id => auditedAdminData.update('questions', id, {
|
||||
deleted_at: new Date().toISOString(),
|
||||
}))
|
||||
);
|
||||
@@ -137,7 +137,7 @@ export async function deleteQuestions(ids: string[]): Promise<void> {
|
||||
* Restore a soft-deleted question
|
||||
*/
|
||||
export async function restoreQuestion(id: string): Promise<void> {
|
||||
const { error } = await auditedSupabase.update('questions', id, {
|
||||
const { error } = await auditedAdminData.update('questions', id, {
|
||||
deleted_at: null,
|
||||
});
|
||||
if (error) throw error;
|
||||
@@ -169,7 +169,7 @@ export async function bulkCreateQuestions(
|
||||
|
||||
// Insert standalone questions first
|
||||
for (const q of standaloneQuestions) {
|
||||
const { data, error } = await auditedSupabase.insert('questions', {
|
||||
const { data, error } = await auditedAdminData.insert('questions', {
|
||||
pack_id: packId,
|
||||
text: q.text,
|
||||
partner_text: q.partner_text || null,
|
||||
@@ -186,7 +186,7 @@ export async function bulkCreateQuestions(
|
||||
if (pair.length >= 2) {
|
||||
// First question is primary
|
||||
const primary = pair[0];
|
||||
const { data: primaryData, error: primaryError } = await auditedSupabase.insert('questions', {
|
||||
const { data: primaryData, error: primaryError } = await auditedAdminData.insert('questions', {
|
||||
pack_id: packId,
|
||||
text: primary.text,
|
||||
partner_text: primary.partner_text || null,
|
||||
@@ -201,7 +201,7 @@ export async function bulkCreateQuestions(
|
||||
|
||||
// Second question is inverse
|
||||
const inverse = pair[1];
|
||||
const { data: inverseData, error: inverseError } = await auditedSupabase.insert('questions', {
|
||||
const { data: inverseData, error: inverseError } = await auditedAdminData.insert('questions', {
|
||||
pack_id: packId,
|
||||
text: inverse.text,
|
||||
partner_text: inverse.partner_text || null,
|
||||
@@ -216,7 +216,7 @@ export async function bulkCreateQuestions(
|
||||
// Handle any additional questions in the pair
|
||||
for (let i = 2; i < pair.length; i++) {
|
||||
const extra = pair[i];
|
||||
const { data, error } = await auditedSupabase.insert('questions', {
|
||||
const { data, error } = await auditedAdminData.insert('questions', {
|
||||
pack_id: packId,
|
||||
text: extra.text,
|
||||
partner_text: extra.partner_text || null,
|
||||
@@ -228,7 +228,7 @@ export async function bulkCreateQuestions(
|
||||
}
|
||||
} else if (pair.length === 1) {
|
||||
// Single question with pair_id but no pair
|
||||
const { data, error } = await auditedSupabase.insert('questions', {
|
||||
const { data, error } = await auditedAdminData.insert('questions', {
|
||||
pack_id: packId,
|
||||
text: pair[0].text,
|
||||
partner_text: pair[0].partner_text || null,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { supabase } from '@/config';
|
||||
import { adminData } from '@/lib/adminApi';
|
||||
|
||||
// =============================================================================
|
||||
// Types
|
||||
@@ -25,10 +25,10 @@ export async function fetchDashboardStats(): Promise<DashboardStats> {
|
||||
{ count: questions },
|
||||
{ count: users },
|
||||
] = await Promise.all([
|
||||
supabase.from('categories').select('*', { count: 'exact', head: true }),
|
||||
supabase.from('question_packs').select('*', { count: 'exact', head: true }),
|
||||
supabase.from('questions').select('*', { count: 'exact', head: true }),
|
||||
supabase.from('profiles').select('*', { count: 'exact', head: true }),
|
||||
adminData.from('categories').select('*', { count: 'exact', head: true }),
|
||||
adminData.from('question_packs').select('*', { count: 'exact', head: true }),
|
||||
adminData.from('questions').select('*', { count: 'exact', head: true }),
|
||||
adminData.from('profiles').select('*', { count: 'exact', head: true }),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -43,7 +43,7 @@ export async function fetchDashboardStats(): Promise<DashboardStats> {
|
||||
* Fetch count for a specific table
|
||||
*/
|
||||
export async function fetchTableCount(tableName: string): Promise<number> {
|
||||
const { count, error } = await supabase
|
||||
const { count, error } = await adminData
|
||||
.from(tableName)
|
||||
.select('*', { count: 'exact', head: true });
|
||||
|
||||
|
||||
Reference in new issue
Block a user