db: Appwrite schema (isletmem db, 11 tables, indexes) + SDK helpers
- Created database 'isletmem' with 11 tables via Appwrite MCP: tenant_settings, customers, services, software, customer_software, calendar_events, tasks, finance_entries, invoices, invoice_items, audit_logs - All tables use rowSecurity=true; row-level perms scoped to Appwrite Team (tenant) - 18 indexes (composite on tenantId + queried columns; unique for invoice numbers, tenant_settings) - src/lib/appwrite/schema.ts as TS source of truth (DATABASE_ID, TABLES, row types) - src/lib/appwrite/client.ts (browser SDK) - src/lib/appwrite/server.ts (node-appwrite admin + session clients) - .env.example template, .env.local for dev (gitignored) - typecheck script added
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
import { Account, Avatars, Client, Databases, Storage, TablesDB, Teams } from "appwrite";
|
||||
|
||||
const endpoint = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT;
|
||||
const projectId = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID;
|
||||
|
||||
if (!endpoint || !projectId) {
|
||||
throw new Error(
|
||||
"Missing NEXT_PUBLIC_APPWRITE_ENDPOINT or NEXT_PUBLIC_APPWRITE_PROJECT_ID. Check .env.local.",
|
||||
);
|
||||
}
|
||||
|
||||
export const client = new Client().setEndpoint(endpoint).setProject(projectId);
|
||||
|
||||
export const account = new Account(client);
|
||||
export const teams = new Teams(client);
|
||||
export const databases = new Databases(client);
|
||||
export const tablesDB = new TablesDB(client);
|
||||
export const storage = new Storage(client);
|
||||
export const avatars = new Avatars(client);
|
||||
@@ -0,0 +1,167 @@
|
||||
import type { Models } from "appwrite";
|
||||
|
||||
export const DATABASE_ID = "isletmem";
|
||||
|
||||
export const TABLES = {
|
||||
tenantSettings: "tenant_settings",
|
||||
customers: "customers",
|
||||
services: "services",
|
||||
software: "software",
|
||||
customerSoftware: "customer_software",
|
||||
calendarEvents: "calendar_events",
|
||||
tasks: "tasks",
|
||||
financeEntries: "finance_entries",
|
||||
invoices: "invoices",
|
||||
invoiceItems: "invoice_items",
|
||||
auditLogs: "audit_logs",
|
||||
} as const;
|
||||
|
||||
export type TableId = (typeof TABLES)[keyof typeof TABLES];
|
||||
|
||||
type Row = Models.Document;
|
||||
|
||||
export type TenantRole = "owner" | "admin" | "member";
|
||||
|
||||
export interface TenantSettings extends Row {
|
||||
tenantId: string;
|
||||
companyName: string;
|
||||
companyTaxId?: string;
|
||||
companyAddress?: string;
|
||||
companyEmail?: string;
|
||||
companyPhone?: string;
|
||||
logo?: string;
|
||||
defaultVatRate?: number;
|
||||
invoicePrefix?: string;
|
||||
invoiceCounter?: number;
|
||||
}
|
||||
|
||||
export type CustomerStatus = "active" | "passive";
|
||||
|
||||
export interface Customer extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
name: string;
|
||||
email?: string;
|
||||
phone?: string;
|
||||
taxId?: string;
|
||||
address?: string;
|
||||
notes?: string;
|
||||
status?: CustomerStatus;
|
||||
}
|
||||
|
||||
export type BillingPeriod = "monthly" | "yearly" | "onetime";
|
||||
|
||||
export interface Service extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
customerId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
unitPrice: number;
|
||||
recurring?: boolean;
|
||||
billingPeriod?: BillingPeriod;
|
||||
}
|
||||
|
||||
export interface Software extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
name: string;
|
||||
version?: string;
|
||||
description?: string;
|
||||
defaultFee?: number;
|
||||
}
|
||||
|
||||
export interface CustomerSoftware extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
customerId: string;
|
||||
softwareId: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
fee?: number;
|
||||
billingPeriod?: BillingPeriod;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface CalendarEvent extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
start: string;
|
||||
end: string;
|
||||
allDay?: boolean;
|
||||
customerId?: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
export type TaskStatus = "backlog" | "todo" | "in_progress" | "done";
|
||||
export type TaskPriority = "low" | "medium" | "high" | "urgent";
|
||||
|
||||
export interface Task extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
status?: TaskStatus;
|
||||
priority?: TaskPriority;
|
||||
dueDate?: string;
|
||||
assigneeId?: string;
|
||||
customerId?: string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export type FinanceType = "income" | "expense" | "debt" | "receivable";
|
||||
export type PaymentMethod = "cash" | "transfer" | "card" | "check" | "other";
|
||||
|
||||
export interface FinanceEntry extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
type: FinanceType;
|
||||
amount: number;
|
||||
date: string;
|
||||
description?: string;
|
||||
customerId?: string;
|
||||
invoiceId?: string;
|
||||
paymentMethod?: PaymentMethod;
|
||||
}
|
||||
|
||||
export type InvoiceStatus = "draft" | "sent" | "paid" | "overdue" | "cancelled";
|
||||
|
||||
export interface Invoice extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
number: string;
|
||||
customerId: string;
|
||||
issueDate: string;
|
||||
dueDate: string;
|
||||
status?: InvoiceStatus;
|
||||
subtotal?: number;
|
||||
vatTotal?: number;
|
||||
total?: number;
|
||||
notes?: string;
|
||||
}
|
||||
|
||||
export interface InvoiceItem extends Row {
|
||||
tenantId: string;
|
||||
createdBy: string;
|
||||
invoiceId: string;
|
||||
description: string;
|
||||
quantity: number;
|
||||
unitPrice: number;
|
||||
vatRate?: number;
|
||||
lineTotal: number;
|
||||
}
|
||||
|
||||
export type AuditAction = "create" | "update" | "delete";
|
||||
|
||||
export interface AuditLog extends Row {
|
||||
tenantId: string;
|
||||
userId: string;
|
||||
action: AuditAction;
|
||||
entityType: string;
|
||||
entityId: string;
|
||||
changes?: string;
|
||||
ipAddress?: string;
|
||||
userAgent?: string;
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import "server-only";
|
||||
|
||||
import { cookies } from "next/headers";
|
||||
import {
|
||||
Account,
|
||||
Client,
|
||||
Databases,
|
||||
Storage,
|
||||
TablesDB,
|
||||
Teams,
|
||||
Users,
|
||||
} from "node-appwrite";
|
||||
|
||||
const endpoint = process.env.NEXT_PUBLIC_APPWRITE_ENDPOINT;
|
||||
const projectId = process.env.NEXT_PUBLIC_APPWRITE_PROJECT_ID;
|
||||
const apiKey = process.env.APPWRITE_API_KEY;
|
||||
|
||||
if (!endpoint || !projectId) {
|
||||
throw new Error(
|
||||
"Missing NEXT_PUBLIC_APPWRITE_ENDPOINT or NEXT_PUBLIC_APPWRITE_PROJECT_ID. Check .env.local.",
|
||||
);
|
||||
}
|
||||
|
||||
export const APPWRITE_SESSION_COOKIE = "isletmem-session";
|
||||
|
||||
function baseClient() {
|
||||
return new Client().setEndpoint(endpoint!).setProject(projectId!);
|
||||
}
|
||||
|
||||
export function createAdminClient() {
|
||||
if (!apiKey) {
|
||||
throw new Error("Missing APPWRITE_API_KEY. Required for admin operations.");
|
||||
}
|
||||
const client = baseClient().setKey(apiKey);
|
||||
return {
|
||||
client,
|
||||
account: new Account(client),
|
||||
teams: new Teams(client),
|
||||
users: new Users(client),
|
||||
databases: new Databases(client),
|
||||
tablesDB: new TablesDB(client),
|
||||
storage: new Storage(client),
|
||||
};
|
||||
}
|
||||
|
||||
export async function createSessionClient() {
|
||||
const session = (await cookies()).get(APPWRITE_SESSION_COOKIE);
|
||||
if (!session?.value) {
|
||||
throw new Error("No active session.");
|
||||
}
|
||||
const client = baseClient().setSession(session.value);
|
||||
return {
|
||||
client,
|
||||
account: new Account(client),
|
||||
teams: new Teams(client),
|
||||
databases: new Databases(client),
|
||||
tablesDB: new TablesDB(client),
|
||||
storage: new Storage(client),
|
||||
};
|
||||
}
|
||||
|
||||
export async function getCurrentUser() {
|
||||
try {
|
||||
const { account } = await createSessionClient();
|
||||
return await account.get();
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user