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:
kovakmedya
2026-04-30 02:43:25 +03:00
parent 29aa346f9e
commit 6dd4f9e9c3
7 changed files with 346 additions and 1 deletions
+19
View File
@@ -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);
+167
View File
@@ -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;
}
+69
View File
@@ -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;
}
}