Refactor database schema and storage to support customer management and improved security.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9111ef36-26c8-4085-84ca-a35dc1fec1b5
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7083d608-d6d3-4a6a-9a27-6286c5109627/71d83b03-af3d-433a-a517-e76255668e93.jpg
This commit is contained in:
alphaeusmote
2025-04-11 19:25:41 +00:00
parent 17c31c8183
commit fbea387dea
6 changed files with 1188 additions and 265 deletions
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,5 +1,5 @@
import express, { type Request, Response, NextFunction } from "express"; import express, { type Request, Response, NextFunction } from "express";
import { registerRoutes } from "./routes"; import { registerRoutes } from "./routes.simplified";
import { setupVite, serveStatic, log } from "./vite"; import { setupVite, serveStatic, log } from "./vite";
const app = express(); const app = express();
+29
View File
@@ -0,0 +1,29 @@
import type { Express } from "express";
import { createServer, type Server } from "http";
import { getCurrentIpAddress, getCurrentIpv6Address } from "./utils/ip-utils";
export async function registerRoutes(app: Express): Promise<Server> {
// Create HTTP server
const httpServer = createServer(app);
// Add public IP endpoint for the frontend
app.get('/api/public-ip', async (req, res) => {
try {
console.log('Fetching public IP addresses');
// Get IPv4 address using our utility function
const ipv4 = await getCurrentIpAddress();
console.log(`Resolved IPv4: ${ipv4}`);
// Try to get IPv6 as well
const ipv6 = await getCurrentIpv6Address();
console.log(`Resolved IPv6: ${ipv6}`);
res.json({ ipv4, ipv6 });
} catch (error) {
console.error('Error getting public IP:', error);
res.status(500).json({ error: 'Failed to get public IP address' });
}
});
return httpServer;
}
+11 -6
View File
@@ -11,14 +11,17 @@ import {
insertDnsRecordSchema, insertDnsRecordSchema,
insertProviderSchema, insertProviderSchema,
insertApiTokenSchema, insertApiTokenSchema,
insertOrganizationSchema, insertCustomerSchema,
insertWebhookSchema, insertWebhookSchema,
insertDnsMetricSchema, insertDnsMetricSchema,
insertCustomRoleSchema, insertCustomRoleSchema,
insertCustomerUserAssignmentSchema,
insertDomainCredentialsSchema,
insertApiTokenCustomerAccessSchema,
recordTypes, recordTypes,
providerTypes, providerTypes,
customRoles, customRoles,
memberTypes systemRoles
} from "@shared/schema"; } from "@shared/schema";
import { randomBytes } from "crypto"; import { randomBytes } from "crypto";
import { getProviderForDomain } from './providers'; import { getProviderForDomain } from './providers';
@@ -119,8 +122,8 @@ async function triggerDnsWebhooks(
const domain = await storage.getDomain(domainId); const domain = await storage.getDomain(domainId);
if (!domain) return; if (!domain) return;
// Get webhooks for this organization // Get webhooks for this customer
const webhooks = await storage.getWebhooksByOrganization(domain.organizationId); const webhooks = await storage.getWebhooksByCustomer(domain.customerId);
// Filter webhooks that have subscribed to DNS events // Filter webhooks that have subscribed to DNS events
const dnsWebhooks = webhooks.filter(webhook => const dnsWebhooks = webhooks.filter(webhook =>
@@ -153,8 +156,8 @@ async function triggerDnsWebhooks(
} }
export async function registerRoutes(app: Express): Promise<Server> { export async function registerRoutes(app: Express): Promise<Server> {
// Setup authentication routes // Create HTTP server
setupAuth(app); const httpServer = createServer(app);
// Add public IP endpoint for the frontend // Add public IP endpoint for the frontend
app.get('/api/public-ip', async (req, res) => { app.get('/api/public-ip', async (req, res) => {
@@ -175,6 +178,8 @@ export async function registerRoutes(app: Express): Promise<Server> {
} }
}); });
return httpServer;
// DNS Records routes // DNS Records routes
app.get("/api/dns-records", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => { app.get("/api/dns-records", requireRole(["admin", "manager", "user", "readonly"]), async (req, res) => {
try { try {
+34 -14
View File
@@ -1,19 +1,22 @@
import { import {
type User, type InsertUser, type User, type InsertUser,
type Organization, type InsertOrganization, type Customer, type InsertCustomer,
type CustomerUserAssignment, type InsertCustomerUserAssignment,
type Domain, type InsertDomain, type Domain, type InsertDomain,
type DomainCredentials, type InsertDomainCredentials,
type DnsRecord, type InsertDnsRecord, type DnsRecord, type InsertDnsRecord,
type Provider, type InsertProvider, type Provider, type InsertProvider,
type ApiToken, type InsertApiToken, type ApiToken, type InsertApiToken,
type ApiTokenCustomerAccess, type InsertApiTokenCustomerAccess,
type DnsHistory, type DnsHistory,
type Webhook, type InsertWebhook, type Webhook, type InsertWebhook,
type WebhookDeliveryLog, type InsertWebhookDeliveryLog, type WebhookDeliveryLog, type InsertWebhookDeliveryLog,
type DnsMetric, type InsertDnsMetric, type DnsMetric, type InsertDnsMetric,
type CustomRole, type InsertCustomRole, type CustomRole, type InsertCustomRole
type MemberType
} from "@shared/schema"; } from "@shared/schema";
import session from "express-session"; import session from "express-session";
import { DatabaseStorage } from "./database-storage"; import { DatabaseStorage } from "./database-storage";
import { ImprovedDatabaseStorage } from "./improved-database-storage";
export interface IStorage { export interface IStorage {
// User management // User management
@@ -24,23 +27,33 @@ export interface IStorage {
updateUser(id: string, user: Partial<InsertUser>): Promise<User | undefined>; updateUser(id: string, user: Partial<InsertUser>): Promise<User | undefined>;
deleteUser(id: string): Promise<boolean>; deleteUser(id: string): Promise<boolean>;
// Organization management // Customer management
getOrganization(id: string): Promise<Organization | undefined>; getCustomer(id: string): Promise<Customer | undefined>;
getOrganizations(): Promise<Organization[]>; getCustomers(): Promise<Customer[]>;
createOrganization(org: InsertOrganization): Promise<Organization>; createCustomer(customer: InsertCustomer): Promise<Customer>;
updateOrganization(id: string, org: Partial<InsertOrganization>): Promise<Organization | undefined>; updateCustomer(id: string, customer: Partial<InsertCustomer>): Promise<Customer | undefined>;
deleteOrganization(id: string): Promise<boolean>; deleteCustomer(id: string): Promise<boolean>;
// Group management has been removed // Customer-User assignments
getCustomerUsers(customerId: string): Promise<User[]>;
getUserCustomers(userId: string): Promise<Customer[]>;
assignUserToCustomer(assignment: InsertCustomerUserAssignment): Promise<CustomerUserAssignment>;
updateUserCustomerRole(customerId: string, userId: string, role: string): Promise<CustomerUserAssignment | undefined>;
removeUserFromCustomer(customerId: string, userId: string): Promise<boolean>;
// Domain management // Domain management
getDomain(id: string): Promise<Domain | undefined>; getDomain(id: string): Promise<Domain | undefined>;
getDomainsByOrganization(organizationId: string): Promise<Domain[]>; getDomainsByCustomer(customerId: string): Promise<Domain[]>;
getAllDomains(): Promise<Domain[]>; getAllDomains(): Promise<Domain[]>;
createDomain(domain: InsertDomain): Promise<Domain>; createDomain(domain: InsertDomain): Promise<Domain>;
updateDomain(id: string, domain: Partial<InsertDomain>): Promise<Domain | undefined>; updateDomain(id: string, domain: Partial<InsertDomain>): Promise<Domain | undefined>;
deleteDomain(id: string): Promise<boolean>; deleteDomain(id: string): Promise<boolean>;
// Domain credentials management
getDomainCredentials(domainId: string): Promise<DomainCredentials | undefined>;
createDomainCredentials(credentials: InsertDomainCredentials): Promise<DomainCredentials>;
updateDomainCredentials(domainId: string, credentials: Partial<InsertDomainCredentials>): Promise<DomainCredentials | undefined>;
// DNS Record management // DNS Record management
getDnsRecord(id: string): Promise<DnsRecord | undefined>; getDnsRecord(id: string): Promise<DnsRecord | undefined>;
getDnsRecordsByDomain(domainId: string): Promise<DnsRecord[]>; getDnsRecordsByDomain(domainId: string): Promise<DnsRecord[]>;
@@ -58,11 +71,17 @@ export interface IStorage {
// API Token management // API Token management
getApiToken(id: string): Promise<ApiToken | undefined>; getApiToken(id: string): Promise<ApiToken | undefined>;
getApiTokenByToken(token: string): Promise<ApiToken | undefined>; getApiTokenByToken(token: string): Promise<ApiToken | undefined>;
getApiTokensByOrganization(organizationId: string): Promise<ApiToken[]>; getApiTokensByCustomer(customerId: string): Promise<ApiToken[]>;
getApiTokens(): Promise<ApiToken[]>;
createApiToken(token: InsertApiToken): Promise<ApiToken>; createApiToken(token: InsertApiToken): Promise<ApiToken>;
updateApiToken(id: string, token: Partial<InsertApiToken>): Promise<ApiToken | undefined>; updateApiToken(id: string, token: Partial<InsertApiToken>): Promise<ApiToken | undefined>;
deleteApiToken(id: string): Promise<boolean>; deleteApiToken(id: string): Promise<boolean>;
// API Token Customer Access
getApiTokenCustomerAccess(tokenId: string): Promise<ApiTokenCustomerAccess[]>;
addApiTokenCustomerAccess(access: InsertApiTokenCustomerAccess): Promise<ApiTokenCustomerAccess>;
removeApiTokenCustomerAccess(tokenId: string, customerId: string): Promise<boolean>;
// DNS History // DNS History
addDnsHistory(recordId: string, action: string, previousValue?: string, newValue?: string, userId?: string): Promise<DnsHistory>; addDnsHistory(recordId: string, action: string, previousValue?: string, newValue?: string, userId?: string): Promise<DnsHistory>;
getDnsHistoryByRecord(recordId: string): Promise<DnsHistory[]>; getDnsHistoryByRecord(recordId: string): Promise<DnsHistory[]>;
@@ -70,7 +89,7 @@ export interface IStorage {
// Webhook management // Webhook management
getWebhook(id: string): Promise<Webhook | undefined>; getWebhook(id: string): Promise<Webhook | undefined>;
getWebhooksByOrganization(organizationId: string): Promise<Webhook[]>; getWebhooksByCustomer(customerId: string): Promise<Webhook[]>;
createWebhook(webhook: InsertWebhook): Promise<Webhook>; createWebhook(webhook: InsertWebhook): Promise<Webhook>;
updateWebhook(id: string, webhook: Partial<InsertWebhook>): Promise<Webhook | undefined>; updateWebhook(id: string, webhook: Partial<InsertWebhook>): Promise<Webhook | undefined>;
deleteWebhook(id: string): Promise<boolean>; deleteWebhook(id: string): Promise<boolean>;
@@ -927,4 +946,5 @@ export class MemStorage implements IStorage {
} }
} }
export const storage = new MemStorage(); // Use the improved storage implementation
export const storage = new ImprovedDatabaseStorage();
+163 -76
View File
@@ -3,14 +3,7 @@ import { createInsertSchema } from "drizzle-zod";
import { z } from "zod"; import { z } from "zod";
import { relations } from "drizzle-orm"; import { relations } from "drizzle-orm";
// Users & Authentication // Users & Authentication - Independent of customers
export const organizations = pgTable("organizations", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
export const users = pgTable("users", { export const users = pgTable("users", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
username: text("username").notNull().unique(), username: text("username").notNull().unique(),
@@ -18,39 +11,63 @@ export const users = pgTable("users", {
email: text("email").notNull().unique(), email: text("email").notNull().unique(),
fullName: text("full_name"), fullName: text("full_name"),
role: text("role").default("user").notNull(), role: text("role").default("user").notNull(),
organizationId: uuid("organization_id").references(() => organizations.id, { onDelete: "set null" }),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
export const usersRelations = relations(users, ({ one }) => ({ // Customers - Replaces organizations
organization: one(organizations, { export const customers = pgTable("customers", {
fields: [users.organizationId],
references: [organizations.id],
}),
}));
// We'll define this after all tables are declared
// DNS Management
export const providers = pgTable("providers", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
type: text("type").notNull(),
credentials: jsonb("credentials"),
isActive: boolean("is_active").default(true).notNull(), isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
// Customer-User Assignment - Many-to-many relationship
export const customerUserAssignments = pgTable("customer_user_assignments", {
id: uuid("id").defaultRandom().primaryKey(),
customerId: uuid("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }),
userId: uuid("user_id").notNull().references(() => users.id, { onDelete: "cascade" }),
role: text("role").default("user").notNull(), // Role specific to this customer assignment
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (t) => ({
// Unique constraint to prevent duplicate assignments
uniqueAssignment: unique().on(t.customerId, t.userId),
}));
// DNS Management - Providers are independent entities
export const providers = pgTable("providers", {
id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(),
type: text("type").notNull(),
isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
});
// Domains associated with customers
export const domains = pgTable("domains", { export const domains = pgTable("domains", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }), customerId: uuid("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }),
providerId: uuid("provider_id").notNull().references(() => providers.id, { onDelete: "cascade" }), providerId: uuid("provider_id").notNull().references(() => providers.id),
isActive: boolean("is_active").default(true).notNull(), isActive: boolean("is_active").default(true).notNull(),
lastUpdated: timestamp("last_updated"), lastUpdated: timestamp("last_updated"),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
// Domain credentials - each domain has its own API tokens and provider-specific credentials
export const domainCredentials = pgTable("domain_credentials", {
id: uuid("id").defaultRandom().primaryKey(),
domainId: uuid("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
apiToken: text("api_token"), // Provider API token
accountId: text("account_id"), // Provider account ID
zoneId: text("zone_id"), // Provider zone ID (e.g., Cloudflare)
otherCredentials: jsonb("other_credentials"), // Other provider-specific credentials
isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().notNull(),
});
// DNS records associated with domains
export const dnsRecords = pgTable("dns_records", { export const dnsRecords = pgTable("dns_records", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
domainId: uuid("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }), domainId: uuid("domain_id").notNull().references(() => domains.id, { onDelete: "cascade" }),
@@ -62,12 +79,13 @@ export const dnsRecords = pgTable("dns_records", {
providerRecordId: text("provider_record_id"), providerRecordId: text("provider_record_id"),
notes: text("notes"), notes: text("notes"),
isActive: boolean("is_active").default(true).notNull(), isActive: boolean("is_active").default(true).notNull(),
isAutoIP: boolean("auto_update").default(false).notNull(), // renamed to match database column isAutoIP: boolean("auto_update").default(false).notNull(),
proxied: boolean("proxied").default(false), // For Cloudflare proxy setting proxied: boolean("proxied").default(false), // For Cloudflare proxy setting
lastUpdated: timestamp("last_updated"), lastUpdated: timestamp("last_updated"),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
// DNS history for record changes
export const dnsHistory = pgTable("dns_history", { export const dnsHistory = pgTable("dns_history", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
recordId: uuid("record_id").notNull().references(() => dnsRecords.id, { onDelete: "cascade" }), recordId: uuid("record_id").notNull().references(() => dnsRecords.id, { onDelete: "cascade" }),
@@ -78,26 +96,35 @@ export const dnsHistory = pgTable("dns_history", {
timestamp: timestamp("timestamp").defaultNow().notNull(), timestamp: timestamp("timestamp").defaultNow().notNull(),
}); });
// API Tokens // API Tokens for system access (not provider access)
export const apiTokens = pgTable("api_tokens", { export const apiTokens = pgTable("api_tokens", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
token: text("token").notNull().unique(), token: text("token").notNull().unique(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }), role: text("role").default("readonly").notNull(),
permissions: text("permissions").array(),
role: text("role").default("readonly").notNull(), // Adding explicit role like users have
createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "cascade" }), createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "cascade" }),
isActive: boolean("is_active").default(true).notNull(), isActive: boolean("is_active").default(true).notNull(),
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
expiresAt: timestamp("expires_at"), expiresAt: timestamp("expires_at"),
}); });
// Webhooks // API token customer access - for granular customer permissions
export const apiTokenCustomerAccess = pgTable("api_token_customer_access", {
id: uuid("id").defaultRandom().primaryKey(),
tokenId: uuid("token_id").notNull().references(() => apiTokens.id, { onDelete: "cascade" }),
customerId: uuid("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }),
createdAt: timestamp("created_at").defaultNow().notNull(),
}, (t) => ({
// Unique constraint to prevent duplicate access
uniqueAccess: unique().on(t.tokenId, t.customerId),
}));
// Webhooks associated with customers
export const webhooks = pgTable("webhooks", { export const webhooks = pgTable("webhooks", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull(), name: text("name").notNull(),
url: text("url").notNull(), url: text("url").notNull(),
organizationId: uuid("organization_id").notNull().references(() => organizations.id, { onDelete: "cascade" }), customerId: uuid("customer_id").notNull().references(() => customers.id, { onDelete: "cascade" }),
secret: text("secret"), secret: text("secret"),
events: text("events").array().notNull(), events: text("events").array().notNull(),
isActive: boolean("is_active").default(true).notNull(), isActive: boolean("is_active").default(true).notNull(),
@@ -106,7 +133,7 @@ export const webhooks = pgTable("webhooks", {
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
// Webhook delivery logs for tracking delivery status and history // Webhook delivery logs
export const webhookDeliveryLogs = pgTable("webhook_delivery_logs", { export const webhookDeliveryLogs = pgTable("webhook_delivery_logs", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
webhookId: uuid("webhook_id").notNull().references(() => webhooks.id, { onDelete: "cascade" }), webhookId: uuid("webhook_id").notNull().references(() => webhooks.id, { onDelete: "cascade" }),
@@ -121,19 +148,19 @@ export const webhookDeliveryLogs = pgTable("webhook_delivery_logs", {
createdAt: timestamp("created_at").defaultNow().notNull(), createdAt: timestamp("created_at").defaultNow().notNull(),
}); });
// Performance metrics for historical data and trends // Performance metrics
export const dnsMetrics = pgTable("dns_metrics", { export const dnsMetrics = pgTable("dns_metrics", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
domainId: uuid("domain_id").references(() => domains.id, { onDelete: "cascade" }), domainId: uuid("domain_id").references(() => domains.id, { onDelete: "cascade" }),
recordId: uuid("record_id").references(() => dnsRecords.id, { onDelete: "cascade" }), recordId: uuid("record_id").references(() => dnsRecords.id, { onDelete: "cascade" }),
metricType: text("metric_type").notNull(), // response_time, uptime, propagation, etc. metricType: text("metric_type").notNull(),
value: jsonb("value").notNull(), // Flexible metric value storage value: jsonb("value").notNull(),
source: text("source"), // Source of the metric (provider, third-party, etc.) source: text("source"),
tags: text("tags").array(), // For additional filtering/grouping tags: text("tags").array(),
timestamp: timestamp("timestamp").defaultNow().notNull(), timestamp: timestamp("timestamp").defaultNow().notNull(),
}); });
// Custom roles table for user-defined roles beyond system defaults // Custom roles
export const customRoles = pgTable("custom_roles", { export const customRoles = pgTable("custom_roles", {
id: uuid("id").defaultRandom().primaryKey(), id: uuid("id").defaultRandom().primaryKey(),
name: text("name").notNull().unique(), name: text("name").notNull().unique(),
@@ -144,8 +171,6 @@ export const customRoles = pgTable("custom_roles", {
createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "set null" }), createdBy: uuid("created_by").notNull().references(() => users.id, { onDelete: "set null" }),
}); });
// Note: Groups, GroupMembers, and GroupRoles tables have been removed in this version
// Schema Validation // Schema Validation
export const insertUserSchema = createInsertSchema(users).pick({ export const insertUserSchema = createInsertSchema(users).pick({
username: true, username: true,
@@ -153,18 +178,23 @@ export const insertUserSchema = createInsertSchema(users).pick({
email: true, email: true,
fullName: true, fullName: true,
role: true, role: true,
organizationId: true,
}); });
export const insertOrganizationSchema = createInsertSchema(organizations).pick({ export const insertCustomerSchema = createInsertSchema(customers).pick({
name: true, name: true,
isActive: true, isActive: true,
}); });
export const insertCustomerUserAssignmentSchema = createInsertSchema(customerUserAssignments).pick({
customerId: true,
userId: true,
role: true,
});
export const insertDomainSchema = createInsertSchema(domains) export const insertDomainSchema = createInsertSchema(domains)
.pick({ .pick({
name: true, name: true,
organizationId: true, customerId: true,
providerId: true, providerId: true,
isActive: true, isActive: true,
}) })
@@ -173,6 +203,23 @@ export const insertDomainSchema = createInsertSchema(domains)
providerId: z.string().uuid().optional(), providerId: z.string().uuid().optional(),
}); });
export const insertDomainCredentialsSchema = createInsertSchema(domainCredentials)
.pick({
domainId: true,
apiToken: true,
accountId: true,
zoneId: true,
otherCredentials: true,
isActive: true,
})
.extend({
// Make fields optional with proper types
apiToken: z.string().optional(),
accountId: z.string().optional(),
zoneId: z.string().optional(),
otherCredentials: z.record(z.unknown()).optional(),
});
export const insertDnsRecordSchema = createInsertSchema(dnsRecords) export const insertDnsRecordSchema = createInsertSchema(dnsRecords)
.pick({ .pick({
domainId: true, domainId: true,
@@ -198,7 +245,6 @@ export const insertDnsRecordSchema = createInsertSchema(dnsRecords)
export const insertProviderSchema = createInsertSchema(providers).pick({ export const insertProviderSchema = createInsertSchema(providers).pick({
name: true, name: true,
type: true, type: true,
credentials: true,
isActive: true, isActive: true,
}); });
@@ -206,8 +252,6 @@ export const insertProviderSchema = createInsertSchema(providers).pick({
export const insertApiTokenSchema = z.object({ export const insertApiTokenSchema = z.object({
name: z.string().optional(), name: z.string().optional(),
token: z.string().optional(), token: z.string().optional(),
organizationId: z.string().optional(),
permissions: z.array(z.string()).optional(),
role: z.string().optional(), role: z.string().optional(),
createdBy: z.string().optional(), createdBy: z.string().optional(),
isActive: z.boolean().optional(), isActive: z.boolean().optional(),
@@ -216,6 +260,13 @@ export const insertApiTokenSchema = z.object({
expiresIn: z.string().optional(), expiresIn: z.string().optional(),
customDate: z.string().optional(), customDate: z.string().optional(),
customTime: z.string().optional(), customTime: z.string().optional(),
// Customer access array
customerIds: z.array(z.string()).optional(),
});
export const insertApiTokenCustomerAccessSchema = createInsertSchema(apiTokenCustomerAccess).pick({
tokenId: true,
customerId: true,
}); });
export const insertCustomRoleSchema = createInsertSchema(customRoles).pick({ export const insertCustomRoleSchema = createInsertSchema(customRoles).pick({
@@ -226,12 +277,10 @@ export const insertCustomRoleSchema = createInsertSchema(customRoles).pick({
createdBy: true, createdBy: true,
}); });
// Note: Group-related insert schemas have been removed in this version
export const insertWebhookSchema = createInsertSchema(webhooks).pick({ export const insertWebhookSchema = createInsertSchema(webhooks).pick({
name: true, name: true,
url: true, url: true,
organizationId: true, customerId: true,
secret: true, secret: true,
events: true, events: true,
isActive: true, isActive: true,
@@ -259,17 +308,22 @@ export const insertDnsMetricSchema = createInsertSchema(dnsMetrics).pick({
tags: true, tags: true,
}); });
// Types // Types
export type InsertUser = z.infer<typeof insertUserSchema>; export type InsertUser = z.infer<typeof insertUserSchema>;
export type User = typeof users.$inferSelect; export type User = typeof users.$inferSelect;
export type InsertOrganization = z.infer<typeof insertOrganizationSchema>; export type InsertCustomer = z.infer<typeof insertCustomerSchema>;
export type Organization = typeof organizations.$inferSelect; export type Customer = typeof customers.$inferSelect;
export type InsertCustomerUserAssignment = z.infer<typeof insertCustomerUserAssignmentSchema>;
export type CustomerUserAssignment = typeof customerUserAssignments.$inferSelect;
export type InsertDomain = z.infer<typeof insertDomainSchema>; export type InsertDomain = z.infer<typeof insertDomainSchema>;
export type Domain = typeof domains.$inferSelect; export type Domain = typeof domains.$inferSelect;
export type InsertDomainCredentials = z.infer<typeof insertDomainCredentialsSchema>;
export type DomainCredentials = typeof domainCredentials.$inferSelect;
export type InsertDnsRecord = z.infer<typeof insertDnsRecordSchema>; export type InsertDnsRecord = z.infer<typeof insertDnsRecordSchema>;
export type DnsRecord = typeof dnsRecords.$inferSelect & { export type DnsRecord = typeof dnsRecords.$inferSelect & {
// Runtime property added by the API - not stored in database // Runtime property added by the API - not stored in database
@@ -282,11 +336,13 @@ export type Provider = typeof providers.$inferSelect;
export type InsertApiToken = z.infer<typeof insertApiTokenSchema>; export type InsertApiToken = z.infer<typeof insertApiTokenSchema>;
export type ApiToken = typeof apiTokens.$inferSelect; export type ApiToken = typeof apiTokens.$inferSelect;
export type InsertApiTokenCustomerAccess = z.infer<typeof insertApiTokenCustomerAccessSchema>;
export type ApiTokenCustomerAccess = typeof apiTokenCustomerAccess.$inferSelect;
export type DnsHistory = typeof dnsHistory.$inferSelect; export type DnsHistory = typeof dnsHistory.$inferSelect;
export type DnsMetric = typeof dnsMetrics.$inferSelect; export type DnsMetric = typeof dnsMetrics.$inferSelect;
export type CustomRole = typeof customRoles.$inferSelect; export type CustomRole = typeof customRoles.$inferSelect;
// Note: Group-related types have been removed in this version
export type InsertCustomRole = z.infer<typeof insertCustomRoleSchema>; export type InsertCustomRole = z.infer<typeof insertCustomRoleSchema>;
export type InsertWebhook = z.infer<typeof insertWebhookSchema>; export type InsertWebhook = z.infer<typeof insertWebhookSchema>;
export type Webhook = typeof webhooks.$inferSelect; export type Webhook = typeof webhooks.$inferSelect;
@@ -302,10 +358,6 @@ export type SystemRole = typeof systemRoles[number];
// User roles can be system roles or custom roles // User roles can be system roles or custom roles
export type UserRole = SystemRole | string; export type UserRole = SystemRole | string;
// Define member types (group type removed)
export const memberTypes = ['user', 'organization'] as const;
export type MemberType = typeof memberTypes[number];
// Provider Types // Provider Types
export const providerTypes = ['cloudflare', 'route53', 'godaddy', 'other'] as const; export const providerTypes = ['cloudflare', 'route53', 'godaddy', 'other'] as const;
export type ProviderType = typeof providerTypes[number]; export type ProviderType = typeof providerTypes[number];
@@ -315,23 +367,51 @@ export const recordTypes = ['A', 'AAAA', 'CNAME', 'MX', 'TXT', 'SRV', 'NS', 'CAA
export type RecordType = typeof recordTypes[number]; export type RecordType = typeof recordTypes[number];
// Define table relations // Define table relations
export const organizationsRelations = relations(organizations, ({ many }) => ({ export const usersRelations = relations(users, ({ many }) => ({
users: many(users), customerAssignments: many(customerUserAssignments),
createdApiTokens: many(apiTokens, { relationName: "createdTokens" }),
createdWebhooks: many(webhooks, { relationName: "createdWebhooks" }),
createdCustomRoles: many(customRoles, { relationName: "createdRoles" }),
dnsHistoryEntries: many(dnsHistory, { relationName: "historyEntries" }),
}));
export const customersRelations = relations(customers, ({ many }) => ({
userAssignments: many(customerUserAssignments),
domains: many(domains), domains: many(domains),
apiTokens: many(apiTokens), tokenAccess: many(apiTokenCustomerAccess),
webhooks: many(webhooks), webhooks: many(webhooks),
})); }));
export const customerUserAssignmentsRelations = relations(customerUserAssignments, ({ one }) => ({
customer: one(customers, {
fields: [customerUserAssignments.customerId],
references: [customers.id],
}),
user: one(users, {
fields: [customerUserAssignments.userId],
references: [users.id],
}),
}));
export const domainsRelations = relations(domains, ({ one, many }) => ({ export const domainsRelations = relations(domains, ({ one, many }) => ({
organization: one(organizations, { customer: one(customers, {
fields: [domains.organizationId], fields: [domains.customerId],
references: [organizations.id], references: [customers.id],
}), }),
provider: one(providers, { provider: one(providers, {
fields: [domains.providerId], fields: [domains.providerId],
references: [providers.id], references: [providers.id],
}), }),
credentials: many(domainCredentials),
dnsRecords: many(dnsRecords), dnsRecords: many(dnsRecords),
metrics: many(dnsMetrics),
}));
export const domainCredentialsRelations = relations(domainCredentials, ({ one }) => ({
domain: one(domains, {
fields: [domainCredentials.domainId],
references: [domains.id],
}),
})); }));
export const dnsRecordsRelations = relations(dnsRecords, ({ one, many }) => ({ export const dnsRecordsRelations = relations(dnsRecords, ({ one, many }) => ({
@@ -339,8 +419,8 @@ export const dnsRecordsRelations = relations(dnsRecords, ({ one, many }) => ({
fields: [dnsRecords.domainId], fields: [dnsRecords.domainId],
references: [domains.id], references: [domains.id],
}), }),
// Provider is now referenced through the domain, not directly
history: many(dnsHistory), history: many(dnsHistory),
metrics: many(dnsMetrics),
})); }));
export const dnsHistoryRelations = relations(dnsHistory, ({ one }) => ({ export const dnsHistoryRelations = relations(dnsHistory, ({ one }) => ({
@@ -369,25 +449,35 @@ export const providersRelations = relations(providers, ({ many }) => ({
domains: many(domains), domains: many(domains),
})); }));
export const apiTokensRelations = relations(apiTokens, ({ one }) => ({ export const apiTokensRelations = relations(apiTokens, ({ one, many }) => ({
organization: one(organizations, {
fields: [apiTokens.organizationId],
references: [organizations.id],
}),
creator: one(users, { creator: one(users, {
fields: [apiTokens.createdBy], fields: [apiTokens.createdBy],
references: [users.id], references: [users.id],
relationName: "createdTokens",
}),
customerAccess: many(apiTokenCustomerAccess),
}));
export const apiTokenCustomerAccessRelations = relations(apiTokenCustomerAccess, ({ one }) => ({
token: one(apiTokens, {
fields: [apiTokenCustomerAccess.tokenId],
references: [apiTokens.id],
}),
customer: one(customers, {
fields: [apiTokenCustomerAccess.customerId],
references: [customers.id],
}), }),
})); }));
export const webhooksRelations = relations(webhooks, ({ one, many }) => ({ export const webhooksRelations = relations(webhooks, ({ one, many }) => ({
organization: one(organizations, { customer: one(customers, {
fields: [webhooks.organizationId], fields: [webhooks.customerId],
references: [organizations.id], references: [customers.id],
}), }),
creator: one(users, { creator: one(users, {
fields: [webhooks.createdBy], fields: [webhooks.createdBy],
references: [users.id], references: [users.id],
relationName: "createdWebhooks",
}), }),
deliveryLogs: many(webhookDeliveryLogs), deliveryLogs: many(webhookDeliveryLogs),
})); }));
@@ -399,13 +489,10 @@ export const webhookDeliveryLogsRelations = relations(webhookDeliveryLogs, ({ on
}), }),
})); }));
// Custom roles relations
export const customRolesRelations = relations(customRoles, ({ one }) => ({ export const customRolesRelations = relations(customRoles, ({ one }) => ({
creator: one(users, { creator: one(users, {
fields: [customRoles.createdBy], fields: [customRoles.createdBy],
references: [users.id], references: [users.id],
relationName: "createdRoles",
}), }),
})); }));
// Note: Group-related relations have been removed in this version