- Add comprehensive analytics components with export functionality - Implement territory management with manager performance tracking - Add seatmap components for venue layout management - Create customer management features with modal interface - Add advanced hooks for dashboard flags and territory data - Implement seat selection and venue management utilities - Add type definitions for ticketing and seatmap systems 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
300 lines
11 KiB
JavaScript
300 lines
11 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.createDefaultOrganization = exports.verifyDomain = exports.requestDomainVerification = exports.resolveDomain = void 0;
|
|
const v2_1 = require("firebase-functions/v2");
|
|
const firestore_1 = require("firebase-admin/firestore");
|
|
const zod_1 = require("zod");
|
|
// Validation schemas
|
|
const resolveRequestSchema = zod_1.z.object({
|
|
host: zod_1.z.string().min(1),
|
|
});
|
|
const verificationRequestSchema = zod_1.z.object({
|
|
orgId: zod_1.z.string().min(1),
|
|
host: zod_1.z.string().min(1),
|
|
});
|
|
const verifyRequestSchema = zod_1.z.object({
|
|
orgId: zod_1.z.string().min(1),
|
|
host: zod_1.z.string().min(1),
|
|
});
|
|
// Default theme for new organizations
|
|
const DEFAULT_THEME = {
|
|
accent: '#F0C457',
|
|
bgCanvas: '#2B2D2F',
|
|
bgSurface: '#34373A',
|
|
textPrimary: '#F1F3F5',
|
|
textSecondary: '#C9D0D4',
|
|
};
|
|
/**
|
|
* Resolve organization by host domain
|
|
* GET /api/domains/resolve?host=tickets.acme.com
|
|
*/
|
|
exports.resolveDomain = v2_1.https.onRequest({
|
|
cors: true,
|
|
region: "us-central1",
|
|
}, async (req, res) => {
|
|
try {
|
|
if (req.method !== 'GET') {
|
|
res.status(405).json({ error: 'Method not allowed' });
|
|
return;
|
|
}
|
|
const { host } = resolveRequestSchema.parse(req.query);
|
|
v2_1.logger.info(`Resolving domain for host: ${host}`);
|
|
const db = (0, firestore_1.getFirestore)();
|
|
// First, try to find org by exact domain match
|
|
const orgsSnapshot = await db.collection('organizations').get();
|
|
for (const doc of orgsSnapshot.docs) {
|
|
const org = doc.data();
|
|
const matchingDomain = org.domains?.find(d => d.host === host && d.verified);
|
|
if (matchingDomain) {
|
|
v2_1.logger.info(`Found org by domain: ${org.id} for host: ${host}`);
|
|
res.json({
|
|
orgId: org.id,
|
|
name: org.name,
|
|
branding: org.branding,
|
|
domains: org.domains,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
// If no direct domain match, try subdomain pattern (e.g., acme.bct.dev)
|
|
const subdomainMatch = host.match(/^([^.]+)\.bct\.dev$/);
|
|
if (subdomainMatch) {
|
|
const slug = subdomainMatch[1];
|
|
const orgBySlugSnapshot = await db.collection('organizations')
|
|
.where('slug', '==', slug)
|
|
.limit(1)
|
|
.get();
|
|
if (!orgBySlugSnapshot.empty) {
|
|
const org = orgBySlugSnapshot.docs[0].data();
|
|
v2_1.logger.info(`Found org by slug: ${org.id} for subdomain: ${slug}`);
|
|
res.json({
|
|
orgId: org.id,
|
|
name: org.name,
|
|
branding: org.branding,
|
|
domains: org.domains,
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
// No organization found
|
|
v2_1.logger.warn(`No organization found for host: ${host}`);
|
|
res.status(404).json({
|
|
error: 'Organization not found',
|
|
host,
|
|
message: 'No organization is configured for this domain'
|
|
});
|
|
}
|
|
catch (error) {
|
|
v2_1.logger.error('Error resolving domain:', error);
|
|
if (error instanceof zod_1.z.ZodError) {
|
|
res.status(400).json({
|
|
error: 'Invalid request',
|
|
details: error.errors
|
|
});
|
|
}
|
|
else {
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: 'Failed to resolve domain'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Request domain verification
|
|
* POST /api/domains/request-verification
|
|
* Body: { orgId: string, host: string }
|
|
*/
|
|
exports.requestDomainVerification = v2_1.https.onRequest({
|
|
cors: true,
|
|
region: "us-central1",
|
|
}, async (req, res) => {
|
|
try {
|
|
if (req.method !== 'POST') {
|
|
res.status(405).json({ error: 'Method not allowed' });
|
|
return;
|
|
}
|
|
const { orgId, host } = verificationRequestSchema.parse(req.body);
|
|
v2_1.logger.info(`Requesting verification for ${host} on org ${orgId}`);
|
|
const db = (0, firestore_1.getFirestore)();
|
|
const orgRef = db.collection('organizations').doc(orgId);
|
|
const orgDoc = await orgRef.get();
|
|
if (!orgDoc.exists) {
|
|
res.status(404).json({ error: 'Organization not found' });
|
|
return;
|
|
}
|
|
const org = orgDoc.data();
|
|
// Generate verification token
|
|
const verificationToken = `bct-verify-${Date.now()}-${Math.random().toString(36).substring(2)}`;
|
|
// Check if domain already exists
|
|
const existingDomains = org.domains || [];
|
|
const existingDomainIndex = existingDomains.findIndex(d => d.host === host);
|
|
const newDomain = {
|
|
host,
|
|
verified: false,
|
|
createdAt: new Date().toISOString(),
|
|
verificationToken,
|
|
};
|
|
let updatedDomains;
|
|
if (existingDomainIndex >= 0) {
|
|
// Update existing domain
|
|
updatedDomains = [...existingDomains];
|
|
updatedDomains[existingDomainIndex] = newDomain;
|
|
}
|
|
else {
|
|
// Add new domain
|
|
updatedDomains = [...existingDomains, newDomain];
|
|
}
|
|
await orgRef.update({ domains: updatedDomains });
|
|
v2_1.logger.info(`Generated verification token for ${host}: ${verificationToken}`);
|
|
res.json({
|
|
success: true,
|
|
host,
|
|
verificationToken,
|
|
instructions: {
|
|
type: 'TXT',
|
|
name: '_bct-verification',
|
|
value: verificationToken,
|
|
ttl: 300,
|
|
description: `Add this TXT record to your DNS configuration for ${host}`,
|
|
},
|
|
});
|
|
}
|
|
catch (error) {
|
|
v2_1.logger.error('Error requesting domain verification:', error);
|
|
if (error instanceof zod_1.z.ZodError) {
|
|
res.status(400).json({
|
|
error: 'Invalid request',
|
|
details: error.errors
|
|
});
|
|
}
|
|
else {
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: 'Failed to request domain verification'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Verify domain ownership
|
|
* POST /api/domains/verify
|
|
* Body: { orgId: string, host: string }
|
|
*/
|
|
exports.verifyDomain = v2_1.https.onRequest({
|
|
cors: true,
|
|
region: "us-central1",
|
|
}, async (req, res) => {
|
|
try {
|
|
if (req.method !== 'POST') {
|
|
res.status(405).json({ error: 'Method not allowed' });
|
|
return;
|
|
}
|
|
const { orgId, host } = verifyRequestSchema.parse(req.body);
|
|
v2_1.logger.info(`Verifying domain ${host} for org ${orgId}`);
|
|
const db = (0, firestore_1.getFirestore)();
|
|
const orgRef = db.collection('organizations').doc(orgId);
|
|
const orgDoc = await orgRef.get();
|
|
if (!orgDoc.exists) {
|
|
res.status(404).json({ error: 'Organization not found' });
|
|
return;
|
|
}
|
|
const org = orgDoc.data();
|
|
const domains = org.domains || [];
|
|
const domainIndex = domains.findIndex(d => d.host === host);
|
|
if (domainIndex === -1) {
|
|
res.status(404).json({ error: 'Domain not found in organization' });
|
|
return;
|
|
}
|
|
const domain = domains[domainIndex];
|
|
if (!domain.verificationToken) {
|
|
res.status(400).json({
|
|
error: 'No verification token found',
|
|
message: 'Please request verification first'
|
|
});
|
|
return;
|
|
}
|
|
// In development, we'll mock DNS verification
|
|
// In production, you would use a real DNS lookup library
|
|
const isDevelopment = process.env.NODE_ENV === 'development' ||
|
|
process.env.FUNCTIONS_EMULATOR === 'true';
|
|
let dnsVerified = false;
|
|
if (isDevelopment) {
|
|
// Mock verification - always succeed in development
|
|
v2_1.logger.info(`Mock DNS verification for ${host} - always succeeds in development`);
|
|
dnsVerified = true;
|
|
}
|
|
else {
|
|
// TODO: Implement real DNS lookup
|
|
// const dns = require('dns').promises;
|
|
// const txtRecords = await dns.resolveTxt(`_bct-verification.${host}`);
|
|
// dnsVerified = txtRecords.some(record =>
|
|
// record.join('') === domain.verificationToken
|
|
// );
|
|
v2_1.logger.warn('Real DNS verification not implemented yet - mocking success');
|
|
dnsVerified = true;
|
|
}
|
|
if (dnsVerified) {
|
|
// Update domain as verified
|
|
const updatedDomains = [...domains];
|
|
updatedDomains[domainIndex] = {
|
|
...domain,
|
|
verified: true,
|
|
verifiedAt: new Date().toISOString(),
|
|
};
|
|
await orgRef.update({ domains: updatedDomains });
|
|
v2_1.logger.info(`Successfully verified domain ${host} for org ${orgId}`);
|
|
res.json({
|
|
success: true,
|
|
host,
|
|
verified: true,
|
|
verifiedAt: updatedDomains[domainIndex].verifiedAt,
|
|
message: 'Domain successfully verified',
|
|
});
|
|
}
|
|
else {
|
|
v2_1.logger.warn(`DNS verification failed for ${host}`);
|
|
res.status(400).json({
|
|
success: false,
|
|
verified: false,
|
|
error: 'DNS verification failed',
|
|
message: `TXT record with value "${domain.verificationToken}" not found at _bct-verification.${host}`,
|
|
});
|
|
}
|
|
}
|
|
catch (error) {
|
|
v2_1.logger.error('Error verifying domain:', error);
|
|
if (error instanceof zod_1.z.ZodError) {
|
|
res.status(400).json({
|
|
error: 'Invalid request',
|
|
details: error.errors
|
|
});
|
|
}
|
|
else {
|
|
res.status(500).json({
|
|
error: 'Internal server error',
|
|
message: 'Failed to verify domain'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
/**
|
|
* Helper function to create a default organization
|
|
* Used for seeding or testing
|
|
*/
|
|
const createDefaultOrganization = async (orgId, name, slug) => {
|
|
const db = (0, firestore_1.getFirestore)();
|
|
const org = {
|
|
id: orgId,
|
|
name,
|
|
slug,
|
|
branding: {
|
|
theme: DEFAULT_THEME,
|
|
},
|
|
domains: [],
|
|
};
|
|
await db.collection('organizations').doc(orgId).set(org);
|
|
return org;
|
|
};
|
|
exports.createDefaultOrganization = createDefaultOrganization;
|
|
// # sourceMappingURL=domains.js.map
|