mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 18:06:57 -05:00
Compare commits
3 Commits
7663205512
...
99ceacfe0c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99ceacfe0c | ||
|
|
ba6bb8a317 | ||
|
|
c0f468451f |
@@ -2,8 +2,15 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|||||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
|
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer } from 'recharts';
|
||||||
import { AlertCircle, TrendingUp, Users, Zap } from 'lucide-react';
|
import { AlertCircle, TrendingUp, Users, Zap } from 'lucide-react';
|
||||||
|
|
||||||
|
interface ErrorSummary {
|
||||||
|
error_type: string | null;
|
||||||
|
occurrence_count: number | null;
|
||||||
|
affected_users: number | null;
|
||||||
|
avg_duration_ms: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
interface ErrorAnalyticsProps {
|
interface ErrorAnalyticsProps {
|
||||||
errorSummary: any[] | undefined;
|
errorSummary: ErrorSummary[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) {
|
export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) {
|
||||||
@@ -11,8 +18,8 @@ export function ErrorAnalytics({ errorSummary }: ErrorAnalyticsProps) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalErrors = errorSummary.reduce((sum, item) => sum + item.occurrence_count, 0);
|
const totalErrors = errorSummary.reduce((sum, item) => sum + (item.occurrence_count || 0), 0);
|
||||||
const totalAffectedUsers = errorSummary.reduce((sum, item) => sum + item.affected_users, 0);
|
const totalAffectedUsers = errorSummary.reduce((sum, item) => sum + (item.affected_users || 0), 0);
|
||||||
const avgDuration = errorSummary.reduce((sum, item) => sum + (item.avg_duration_ms || 0), 0) / errorSummary.length;
|
const avgDuration = errorSummary.reduce((sum, item) => sum + (item.avg_duration_ms || 0), 0) / errorSummary.length;
|
||||||
|
|
||||||
const topErrors = errorSummary.slice(0, 5);
|
const topErrors = errorSummary.slice(0, 5);
|
||||||
|
|||||||
@@ -6,8 +6,31 @@ import { Copy, ExternalLink } from 'lucide-react';
|
|||||||
import { format } from 'date-fns';
|
import { format } from 'date-fns';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
|
|
||||||
|
interface Breadcrumb {
|
||||||
|
timestamp: string;
|
||||||
|
category: string;
|
||||||
|
message: string;
|
||||||
|
level: string;
|
||||||
|
data?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorDetails {
|
||||||
|
request_id: string;
|
||||||
|
created_at: string;
|
||||||
|
error_type: string;
|
||||||
|
error_message: string;
|
||||||
|
error_stack?: string;
|
||||||
|
endpoint: string;
|
||||||
|
method: string;
|
||||||
|
status_code: number;
|
||||||
|
duration_ms: number;
|
||||||
|
user_id?: string;
|
||||||
|
breadcrumbs?: Breadcrumb[];
|
||||||
|
environment_context?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
|
||||||
interface ErrorDetailsModalProps {
|
interface ErrorDetailsModalProps {
|
||||||
error: any;
|
error: ErrorDetails;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,7 +148,7 @@ ${error.error_stack ? `Stack Trace:\n${error.error_stack}` : ''}
|
|||||||
<TabsContent value="breadcrumbs">
|
<TabsContent value="breadcrumbs">
|
||||||
{error.breadcrumbs && error.breadcrumbs.length > 0 ? (
|
{error.breadcrumbs && error.breadcrumbs.length > 0 ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{error.breadcrumbs.map((crumb: any, index: number) => (
|
{error.breadcrumbs.map((crumb, index) => (
|
||||||
<div key={index} className="border-l-2 border-primary pl-4 py-2">
|
<div key={index} className="border-l-2 border-primary pl-4 py-2">
|
||||||
<div className="flex items-center gap-2 mb-1">
|
<div className="flex items-center gap-2 mb-1">
|
||||||
<Badge variant="outline" className="text-xs">
|
<Badge variant="outline" className="text-xs">
|
||||||
|
|||||||
@@ -86,14 +86,14 @@ export function CoasterStatsEditor({
|
|||||||
onChange(newStats.map((stat, i) => ({ ...stat, display_order: i })));
|
onChange(newStats.map((stat, i) => ({ ...stat, display_order: i })));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateStat = (index: number, field: keyof CoasterStat, value: any) => {
|
const updateStat = (index: number, field: keyof CoasterStat, value: string | number | boolean | null | undefined) => {
|
||||||
const newStats = [...stats];
|
const newStats = [...stats];
|
||||||
|
|
||||||
// Ensure unit is metric when updating unit field
|
// Ensure unit is metric when updating unit field
|
||||||
if (field === 'unit' && value) {
|
if (field === 'unit' && value && typeof value === 'string') {
|
||||||
try {
|
try {
|
||||||
validateMetricUnit(value, 'Unit');
|
validateMetricUnit(value, 'Unit');
|
||||||
newStats[index] = { ...newStats[index], [field]: value };
|
newStats[index] = { ...newStats[index], unit: value };
|
||||||
// Clear error for this index
|
// Clear error for this index
|
||||||
setUnitErrors(prev => {
|
setUnitErrors(prev => {
|
||||||
const updated = { ...prev };
|
const updated = { ...prev };
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export function FormerNamesEditor({ names, onChange, currentName }: FormerNamesE
|
|||||||
onChange(newNames.map((name, i) => ({ ...name, order_index: i })));
|
onChange(newNames.map((name, i) => ({ ...name, order_index: i })));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateName = (index: number, field: keyof FormerName, value: any) => {
|
const updateName = (index: number, field: keyof FormerName, value: string | number | Date | null | undefined) => {
|
||||||
const newNames = [...names];
|
const newNames = [...names];
|
||||||
newNames[index] = { ...newNames[index], [field]: value };
|
newNames[index] = { ...newNames[index], [field]: value };
|
||||||
onChange(newNames);
|
onChange(newNames);
|
||||||
|
|||||||
@@ -64,14 +64,14 @@ export function TechnicalSpecsEditor({
|
|||||||
onChange(newSpecs.map((spec, i) => ({ ...spec, display_order: i })));
|
onChange(newSpecs.map((spec, i) => ({ ...spec, display_order: i })));
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateSpec = (index: number, field: keyof TechnicalSpec, value: any) => {
|
const updateSpec = (index: number, field: keyof TechnicalSpec, value: string | number | boolean | null | undefined) => {
|
||||||
const newSpecs = [...specs];
|
const newSpecs = [...specs];
|
||||||
|
|
||||||
// Ensure unit is metric when updating unit field
|
// Ensure unit is metric when updating unit field
|
||||||
if (field === 'unit' && value) {
|
if (field === 'unit' && value && typeof value === 'string') {
|
||||||
try {
|
try {
|
||||||
validateMetricUnit(value, 'Unit');
|
validateMetricUnit(value, 'Unit');
|
||||||
newSpecs[index] = { ...newSpecs[index], [field]: value };
|
newSpecs[index] = { ...newSpecs[index], unit: value };
|
||||||
// Clear error for this index
|
// Clear error for this index
|
||||||
setUnitErrors(prev => {
|
setUnitErrors(prev => {
|
||||||
const updated = { ...prev };
|
const updated = { ...prev };
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export function TurnstileCaptcha({
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [key, setKey] = useState(0);
|
const [key, setKey] = useState(0);
|
||||||
const turnstileRef = useRef<any>(null);
|
const turnstileRef = useRef(null);
|
||||||
|
|
||||||
const handleSuccess = (token: string) => {
|
const handleSuccess = (token: string) => {
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ interface AdminErrorBoundaryProps {
|
|||||||
section?: string; // e.g., "Moderation", "Users", "Settings"
|
section?: string; // e.g., "Moderation", "Users", "Settings"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrorWithId = Error & { errorId: string };
|
||||||
|
|
||||||
interface AdminErrorBoundaryState {
|
interface AdminErrorBoundaryState {
|
||||||
hasError: boolean;
|
hasError: boolean;
|
||||||
error: Error | null;
|
error: ErrorWithId | null;
|
||||||
errorInfo: ErrorInfo | null;
|
errorInfo: ErrorInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +45,7 @@ export class AdminErrorBoundary extends Component<AdminErrorBoundaryProps, Admin
|
|||||||
static getDerivedStateFromError(error: Error): Partial<AdminErrorBoundaryState> {
|
static getDerivedStateFromError(error: Error): Partial<AdminErrorBoundaryState> {
|
||||||
return {
|
return {
|
||||||
hasError: true,
|
hasError: true,
|
||||||
error,
|
error: error as ErrorWithId,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +62,7 @@ export class AdminErrorBoundary extends Component<AdminErrorBoundaryProps, Admin
|
|||||||
errorId,
|
errorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ errorInfo, error: { ...error, errorId } as any });
|
this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRetry = () => {
|
handleRetry = () => {
|
||||||
|
|||||||
@@ -18,19 +18,8 @@ interface EntityErrorBoundaryState {
|
|||||||
errorInfo: ErrorInfo | null;
|
errorInfo: ErrorInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type ErrorWithId = Error & { errorId: string };
|
||||||
* Entity Error Boundary Component (P0 #5)
|
|
||||||
*
|
|
||||||
* Specialized error boundary for entity detail pages.
|
|
||||||
* Prevents entity rendering errors from crashing the app.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```tsx
|
|
||||||
* <EntityErrorBoundary entityType="park" entitySlug={slug}>
|
|
||||||
* <ParkDetail />
|
|
||||||
* </EntityErrorBoundary>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, EntityErrorBoundaryState> {
|
export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, EntityErrorBoundaryState> {
|
||||||
constructor(props: EntityErrorBoundaryProps) {
|
constructor(props: EntityErrorBoundaryProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -61,7 +50,7 @@ export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, Ent
|
|||||||
errorId,
|
errorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ errorInfo, error: { ...error, errorId } as any });
|
this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleRetry = () => {
|
handleRetry = () => {
|
||||||
@@ -131,9 +120,9 @@ export class EntityErrorBoundary extends Component<EntityErrorBoundaryProps, Ent
|
|||||||
<p className="text-sm">
|
<p className="text-sm">
|
||||||
{this.state.error?.message || `An unexpected error occurred while loading this ${entityLabel.toLowerCase()}`}
|
{this.state.error?.message || `An unexpected error occurred while loading this ${entityLabel.toLowerCase()}`}
|
||||||
</p>
|
</p>
|
||||||
{(this.state.error as any)?.errorId && (
|
{(this.state.error as ErrorWithId)?.errorId && (
|
||||||
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
|
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
|
||||||
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)}
|
Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<p className="text-xs text-muted-foreground">
|
<p className="text-xs text-muted-foreground">
|
||||||
|
|||||||
@@ -18,19 +18,8 @@ interface ErrorBoundaryState {
|
|||||||
errorInfo: ErrorInfo | null;
|
errorInfo: ErrorInfo | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type ErrorWithId = Error & { errorId: string };
|
||||||
* Generic Error Boundary Component (P0 #5)
|
|
||||||
*
|
|
||||||
* Prevents component errors from crashing the entire application.
|
|
||||||
* Shows user-friendly error UI with recovery options.
|
|
||||||
*
|
|
||||||
* Usage:
|
|
||||||
* ```tsx
|
|
||||||
* <ErrorBoundary context="PhotoUpload">
|
|
||||||
* <PhotoUploadForm />
|
|
||||||
* </ErrorBoundary>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
constructor(props: ErrorBoundaryProps) {
|
constructor(props: ErrorBoundaryProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -61,7 +50,7 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
errorId,
|
errorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ errorInfo, error: { ...error, errorId } as any });
|
this.setState({ errorInfo, error: { ...error, errorId } as ErrorWithId });
|
||||||
this.props.onError?.(error, errorInfo);
|
this.props.onError?.(error, errorInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,9 +94,9 @@ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundarySt
|
|||||||
<p className="text-sm mt-2">
|
<p className="text-sm mt-2">
|
||||||
{this.state.error?.message || 'An unexpected error occurred'}
|
{this.state.error?.message || 'An unexpected error occurred'}
|
||||||
</p>
|
</p>
|
||||||
{(this.state.error as any)?.errorId && (
|
{(this.state.error as ErrorWithId)?.errorId && (
|
||||||
<p className="text-xs mt-2 font-mono bg-destructive/10 px-2 py-1 rounded">
|
<p className="text-xs mt-2 font-mono bg-destructive/10 px-2 py-1 rounded">
|
||||||
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)}
|
Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</AlertDescription>
|
</AlertDescription>
|
||||||
|
|||||||
@@ -13,19 +13,8 @@ interface RouteErrorBoundaryState {
|
|||||||
error: Error | null;
|
error: Error | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
type ErrorWithId = Error & { errorId: string };
|
||||||
* Route Error Boundary Component (P0 #5)
|
|
||||||
*
|
|
||||||
* Top-level error boundary that wraps all routes.
|
|
||||||
* Last line of defense to prevent complete app crashes.
|
|
||||||
*
|
|
||||||
* Usage: Wrap Routes component in App.tsx
|
|
||||||
* ```tsx
|
|
||||||
* <RouteErrorBoundary>
|
|
||||||
* <Routes>...</Routes>
|
|
||||||
* </RouteErrorBoundary>
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> {
|
export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, RouteErrorBoundaryState> {
|
||||||
constructor(props: RouteErrorBoundaryProps) {
|
constructor(props: RouteErrorBoundaryProps) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -56,7 +45,7 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
|
|||||||
errorId,
|
errorId,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({ error: { ...error, errorId } as any });
|
this.setState({ error: { ...error, errorId } as ErrorWithId });
|
||||||
}
|
}
|
||||||
|
|
||||||
handleReload = () => {
|
handleReload = () => {
|
||||||
@@ -91,9 +80,9 @@ export class RouteErrorBoundary extends Component<RouteErrorBoundaryProps, Route
|
|||||||
{this.state.error.message}
|
{this.state.error.message}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{(this.state.error as any)?.errorId && (
|
{(this.state.error as ErrorWithId)?.errorId && (
|
||||||
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
|
<p className="text-xs font-mono bg-destructive/10 px-2 py-1 rounded">
|
||||||
Reference ID: {((this.state.error as any).errorId as string).slice(0, 8)}
|
Reference ID: {(this.state.error as ErrorWithId).errorId.slice(0, 8)}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ serve(async (req) => {
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (rateLimitError) {
|
if (rateLimitError) {
|
||||||
console.error('[Export] Rate limit check failed:', rateLimitError);
|
edgeLogger.error('Rate limit check failed', { action: 'export_rate_limit', requestId: tracking.requestId, error: rateLimitError });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recentExports && recentExports.length > 0) {
|
if (recentExports && recentExports.length > 0) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -38,7 +38,7 @@ serve(async (req) => {
|
|||||||
throw new Error('Action must be either "add" or "remove"');
|
throw new Error('Action must be either "add" or "remove"');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${action === 'add' ? 'Adding' : 'Removing'} user ${userId} ${action === 'add' ? 'to' : 'from'} moderator topics`, { requestId: tracking.requestId });
|
edgeLogger.info(`${action === 'add' ? 'Adding' : 'Removing'} user ${userId} ${action === 'add' ? 'to' : 'from'} moderator topics`, { action: 'manage_moderator_topic', requestId: tracking.requestId, userId, operation: action });
|
||||||
|
|
||||||
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
|
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
|
||||||
const results = [];
|
const results = [];
|
||||||
@@ -50,18 +50,24 @@ serve(async (req) => {
|
|||||||
await novu.topics.addSubscribers(topicKey, {
|
await novu.topics.addSubscribers(topicKey, {
|
||||||
subscribers: [userId],
|
subscribers: [userId],
|
||||||
});
|
});
|
||||||
console.log(`Added ${userId} to topic ${topicKey}`);
|
edgeLogger.info('Added user to topic', { action: 'manage_moderator_topic', requestId: tracking.requestId, userId, topicKey });
|
||||||
results.push({ topic: topicKey, action: 'added', success: true });
|
results.push({ topic: topicKey, action: 'added', success: true });
|
||||||
} else {
|
} else {
|
||||||
// Remove subscriber from topic
|
// Remove subscriber from topic
|
||||||
await novu.topics.removeSubscribers(topicKey, {
|
await novu.topics.removeSubscribers(topicKey, {
|
||||||
subscribers: [userId],
|
subscribers: [userId],
|
||||||
});
|
});
|
||||||
console.log(`Removed ${userId} from topic ${topicKey}`);
|
edgeLogger.info('Removed user from topic', { action: 'manage_moderator_topic', requestId: tracking.requestId, userId, topicKey });
|
||||||
results.push({ topic: topicKey, action: 'removed', success: true });
|
results.push({ topic: topicKey, action: 'removed', success: true });
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error ${action}ing user ${userId} ${action === 'add' ? 'to' : 'from'} topic ${topicKey}:`, error);
|
edgeLogger.error(`Error ${action}ing user ${userId} ${action === 'add' ? 'to' : 'from'} topic ${topicKey}`, {
|
||||||
|
action: 'manage_moderator_topic',
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
userId,
|
||||||
|
topicKey,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
results.push({
|
results.push({
|
||||||
topic: topicKey,
|
topic: topicKey,
|
||||||
action: action === 'add' ? 'added' : 'removed',
|
action: action === 'add' ? 'added' : 'removed',
|
||||||
@@ -93,7 +99,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error managing moderator topic:', error);
|
edgeLogger.error('Error managing moderator topic', { action: 'manage_moderator_topic', requestId: tracking.requestId, error: error.message });
|
||||||
|
|
||||||
endRequest(tracking, 500, error.message);
|
endRequest(tracking, 500, error.message);
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -156,7 +156,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error migrating Novu users:', error);
|
edgeLogger.error('Error migrating Novu users', { action: 'migrate_novu_users', requestId: tracking.requestId, error: error.message });
|
||||||
|
|
||||||
endRequest(tracking, 500, error.message);
|
endRequest(tracking, 500, error.message);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -33,7 +33,8 @@ serve(async (req) => {
|
|||||||
|
|
||||||
const payload: NotificationPayload = await req.json();
|
const payload: NotificationPayload = await req.json();
|
||||||
|
|
||||||
console.log('Processing report notification:', {
|
edgeLogger.info('Processing report notification', {
|
||||||
|
action: 'notify_moderators_report',
|
||||||
reportId: payload.reportId,
|
reportId: payload.reportId,
|
||||||
reportType: payload.reportType,
|
reportType: payload.reportType,
|
||||||
reportedEntityType: payload.reportedEntityType,
|
reportedEntityType: payload.reportedEntityType,
|
||||||
@@ -78,12 +79,12 @@ serve(async (req) => {
|
|||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (templateError) {
|
if (templateError) {
|
||||||
console.error('Error fetching workflow:', templateError);
|
edgeLogger.error('Error fetching workflow', { action: 'notify_moderators_report', requestId: tracking.requestId, error: templateError });
|
||||||
throw new Error(`Failed to fetch workflow: ${templateError.message}`);
|
throw new Error(`Failed to fetch workflow: ${templateError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
console.warn('No active report-alert workflow found');
|
edgeLogger.warn('No active report-alert workflow found', { action: 'notify_moderators_report', requestId: tracking.requestId });
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -126,7 +127,7 @@ serve(async (req) => {
|
|||||||
reportedEntityName = submission?.content?.name || 'Submission';
|
reportedEntityName = submission?.content?.name || 'Submission';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Could not fetch entity name:', error);
|
edgeLogger.warn('Could not fetch entity name', { action: 'notify_moderators_report', requestId: tracking.requestId, error });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build enhanced notification payload
|
// Build enhanced notification payload
|
||||||
@@ -145,7 +146,7 @@ serve(async (req) => {
|
|||||||
priority,
|
priority,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Triggering notification with payload:', notificationPayload);
|
edgeLogger.info('Triggering notification with payload', { action: 'notify_moderators_report', requestId: tracking.requestId });
|
||||||
|
|
||||||
// Invoke the trigger-notification function
|
// Invoke the trigger-notification function
|
||||||
const { data: result, error: notifyError } = await supabase.functions.invoke(
|
const { data: result, error: notifyError } = await supabase.functions.invoke(
|
||||||
@@ -160,11 +161,11 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (notifyError) {
|
if (notifyError) {
|
||||||
console.error('Error triggering notification:', notifyError);
|
edgeLogger.error('Error triggering notification', { action: 'notify_moderators_report', requestId: tracking.requestId, error: notifyError });
|
||||||
throw notifyError;
|
throw notifyError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Notification triggered successfully:', result);
|
edgeLogger.info('Notification triggered successfully', { action: 'notify_moderators_report', requestId: tracking.requestId, result });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in notify-moderators-report:', error);
|
edgeLogger.error('Error in notify-moderators-report', { action: 'notify_moderators_report', requestId: tracking.requestId, error: error.message });
|
||||||
|
|
||||||
endRequest(tracking, 500, error.message);
|
endRequest(tracking, 500, error.message);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -70,7 +70,8 @@ serve(async (req) => {
|
|||||||
throw new Error('Invalid severity level. Must be: info, warning, or critical');
|
throw new Error('Invalid severity level. Must be: info, warning, or critical');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Processing system announcement:', {
|
edgeLogger.info('Processing system announcement', {
|
||||||
|
action: 'notify_system_announcement',
|
||||||
title: payload.title,
|
title: payload.title,
|
||||||
severity: payload.severity,
|
severity: payload.severity,
|
||||||
publishedBy: profile?.username || 'unknown',
|
publishedBy: profile?.username || 'unknown',
|
||||||
@@ -86,12 +87,12 @@ serve(async (req) => {
|
|||||||
.maybeSingle();
|
.maybeSingle();
|
||||||
|
|
||||||
if (templateError) {
|
if (templateError) {
|
||||||
console.error('Error fetching workflow:', templateError);
|
edgeLogger.error('Error fetching workflow', { action: 'notify_system_announcement', requestId: tracking.requestId, error: templateError });
|
||||||
throw new Error(`Failed to fetch workflow: ${templateError.message}`);
|
throw new Error(`Failed to fetch workflow: ${templateError.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!template) {
|
if (!template) {
|
||||||
console.warn('No active system-announcement workflow found');
|
edgeLogger.warn('No active system-announcement workflow found', { action: 'notify_system_announcement', requestId: tracking.requestId });
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: false,
|
success: false,
|
||||||
@@ -120,7 +121,7 @@ serve(async (req) => {
|
|||||||
publishedBy,
|
publishedBy,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Triggering announcement to all users via "users" topic');
|
edgeLogger.info('Triggering announcement to all users via "users" topic', { action: 'notify_system_announcement', requestId: tracking.requestId });
|
||||||
|
|
||||||
// Invoke the trigger-notification function with users topic
|
// Invoke the trigger-notification function with users topic
|
||||||
const { data: result, error: notifyError } = await supabase.functions.invoke(
|
const { data: result, error: notifyError } = await supabase.functions.invoke(
|
||||||
@@ -135,11 +136,11 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (notifyError) {
|
if (notifyError) {
|
||||||
console.error('Error triggering notification:', notifyError);
|
edgeLogger.error('Error triggering notification', { action: 'notify_system_announcement', requestId: tracking.requestId, error: notifyError });
|
||||||
throw notifyError;
|
throw notifyError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('System announcement triggered successfully:', result);
|
edgeLogger.info('System announcement triggered successfully', { action: 'notify_system_announcement', requestId: tracking.requestId, result });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -161,7 +162,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Error in notify-system-announcement:', error);
|
edgeLogger.error('Error in notify-system-announcement', { action: 'notify_system_announcement', requestId: tracking.requestId, error: error.message });
|
||||||
|
|
||||||
endRequest(tracking, error.message.includes('Unauthorized') ? 403 : 500, error.message);
|
endRequest(tracking, error.message.includes('Unauthorized') ? 403 : 500, error.message);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -176,7 +176,8 @@ serve(async (req) => {
|
|||||||
is_duplicate: true
|
is_duplicate: true
|
||||||
}).eq('id', existingLog.id);
|
}).eq('id', existingLog.id);
|
||||||
|
|
||||||
console.log('Duplicate notification prevented:', {
|
edgeLogger.info('Duplicate notification prevented', {
|
||||||
|
action: 'notify_user_submission_status',
|
||||||
userId: user_id,
|
userId: user_id,
|
||||||
idempotencyKey,
|
idempotencyKey,
|
||||||
submissionId: submission_id,
|
submissionId: submission_id,
|
||||||
@@ -203,7 +204,8 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Sending notification to user:', {
|
edgeLogger.info('Sending notification to user', {
|
||||||
|
action: 'notify_user_submission_status',
|
||||||
userId: user_id,
|
userId: user_id,
|
||||||
workflowId,
|
workflowId,
|
||||||
entityName,
|
entityName,
|
||||||
@@ -241,7 +243,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('User notification sent successfully:', notificationResult);
|
edgeLogger.info('User notification sent successfully', { action: 'notify_user_submission_status', requestId: tracking.requestId, result: notificationResult });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -262,7 +264,7 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
console.error('Error notifying user about submission status:', errorMessage);
|
edgeLogger.error('Error notifying user about submission status', { action: 'notify_user_submission_status', requestId: tracking.requestId, error: errorMessage });
|
||||||
|
|
||||||
endRequest(tracking, 500, errorMessage);
|
endRequest(tracking, 500, errorMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
||||||
|
import { edgeLogger } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -35,16 +36,16 @@ Deno.serve(async (req) => {
|
|||||||
.lte('ban_expires_at', now);
|
.lte('ban_expires_at', now);
|
||||||
|
|
||||||
if (fetchError) {
|
if (fetchError) {
|
||||||
console.error('Error fetching expired bans:', fetchError);
|
edgeLogger.error('Error fetching expired bans', { action: 'process_expired_bans', error: fetchError });
|
||||||
throw fetchError;
|
throw fetchError;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${expiredBans?.length || 0} expired bans to process`);
|
edgeLogger.info(`Found ${expiredBans?.length || 0} expired bans to process`, { action: 'process_expired_bans', count: expiredBans?.length || 0 });
|
||||||
|
|
||||||
// Unban users with expired bans
|
// Unban users with expired bans
|
||||||
const unbannedUsers: string[] = [];
|
const unbannedUsers: string[] = [];
|
||||||
for (const profile of expiredBans || []) {
|
for (const profile of expiredBans || []) {
|
||||||
console.log(`Unbanning user: ${profile.username} (${profile.user_id})`);
|
edgeLogger.info('Unbanning user', { action: 'process_expired_bans', username: profile.username, userId: profile.user_id });
|
||||||
|
|
||||||
const { error: unbanError } = await supabaseAdmin
|
const { error: unbanError } = await supabaseAdmin
|
||||||
.from('profiles')
|
.from('profiles')
|
||||||
@@ -56,7 +57,7 @@ Deno.serve(async (req) => {
|
|||||||
.eq('user_id', profile.user_id);
|
.eq('user_id', profile.user_id);
|
||||||
|
|
||||||
if (unbanError) {
|
if (unbanError) {
|
||||||
console.error(`Failed to unban ${profile.username}:`, unbanError);
|
edgeLogger.error('Failed to unban user', { action: 'process_expired_bans', username: profile.username, error: unbanError });
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,13 +75,13 @@ Deno.serve(async (req) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (logError) {
|
if (logError) {
|
||||||
console.error(`Failed to log auto-unban for ${profile.username}:`, logError);
|
edgeLogger.error('Failed to log auto-unban', { action: 'process_expired_bans', username: profile.username, error: logError });
|
||||||
}
|
}
|
||||||
|
|
||||||
unbannedUsers.push(profile.username);
|
unbannedUsers.push(profile.username);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Successfully unbanned ${unbannedUsers.length} users`);
|
edgeLogger.info('Successfully unbanned users', { action: 'process_expired_bans', count: unbannedUsers.length });
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -96,7 +97,7 @@ Deno.serve(async (req) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in process-expired-bans:', error);
|
edgeLogger.error('Error in process-expired-bans', { action: 'process_expired_bans', error });
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
error: error instanceof Error ? error.message : 'Unknown error',
|
error: error instanceof Error ? error.message : 'Unknown error',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { startRequest, endRequest } from '../_shared/logger.ts';
|
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -12,11 +12,14 @@ const CLOUDFLARE_API_TOKEN = Deno.env.get('CLOUDFLARE_IMAGES_API_TOKEN');
|
|||||||
|
|
||||||
// Validate configuration at startup
|
// Validate configuration at startup
|
||||||
if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
|
if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
|
||||||
console.error('[OAuth Profile] Missing Cloudflare configuration:', {
|
edgeLogger.error('Missing Cloudflare configuration', {
|
||||||
|
action: 'oauth_profile_init',
|
||||||
hasAccountId: !!CLOUDFLARE_ACCOUNT_ID,
|
hasAccountId: !!CLOUDFLARE_ACCOUNT_ID,
|
||||||
hasApiToken: !!CLOUDFLARE_API_TOKEN,
|
hasApiToken: !!CLOUDFLARE_API_TOKEN,
|
||||||
});
|
});
|
||||||
console.error('[OAuth Profile] Please configure CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_IMAGES_API_TOKEN in Supabase Edge Function secrets');
|
edgeLogger.error('Please configure CLOUDFLARE_ACCOUNT_ID and CLOUDFLARE_IMAGES_API_TOKEN in Supabase Edge Function secrets', {
|
||||||
|
action: 'oauth_profile_init'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GoogleUserMetadata {
|
interface GoogleUserMetadata {
|
||||||
@@ -96,14 +99,14 @@ Deno.serve(async (req) => {
|
|||||||
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
const { data: { user }, error: authError } = await supabase.auth.getUser(token);
|
||||||
|
|
||||||
if (authError || !user) {
|
if (authError || !user) {
|
||||||
console.error('[OAuth Profile] Authentication failed:', authError);
|
edgeLogger.error('Authentication failed', { action: 'oauth_profile', error: authError, requestId: tracking.requestId });
|
||||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||||
status: 401,
|
status: 401,
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[OAuth Profile] Processing profile for user:', user.id);
|
edgeLogger.info('Processing profile for user', { action: 'oauth_profile', userId: user.id, requestId: tracking.requestId });
|
||||||
|
|
||||||
// CRITICAL: Check ban status immediately
|
// CRITICAL: Check ban status immediately
|
||||||
const { data: banProfile } = await supabase
|
const { data: banProfile } = await supabase
|
||||||
@@ -118,7 +121,8 @@ Deno.serve(async (req) => {
|
|||||||
? `Your account has been suspended. Reason: ${banProfile.ban_reason}`
|
? `Your account has been suspended. Reason: ${banProfile.ban_reason}`
|
||||||
: 'Your account has been suspended. Contact support for assistance.';
|
: 'Your account has been suspended. Contact support for assistance.';
|
||||||
|
|
||||||
console.log('[OAuth Profile] User is banned, rejecting authentication', {
|
edgeLogger.info('User is banned, rejecting authentication', {
|
||||||
|
action: 'oauth_profile_banned',
|
||||||
requestId: tracking.requestId,
|
requestId: tracking.requestId,
|
||||||
duration,
|
duration,
|
||||||
hasBanReason: !!banProfile.ban_reason
|
hasBanReason: !!banProfile.ban_reason
|
||||||
@@ -144,7 +148,9 @@ Deno.serve(async (req) => {
|
|||||||
if (discordIdentity) {
|
if (discordIdentity) {
|
||||||
userMetadata = discordIdentity.identity_data || {};
|
userMetadata = discordIdentity.identity_data || {};
|
||||||
|
|
||||||
console.log('[OAuth Profile] Discord identity_data:', {
|
edgeLogger.info('Discord identity_data', {
|
||||||
|
action: 'oauth_profile_discord',
|
||||||
|
requestId: tracking.requestId,
|
||||||
hasAvatarUrl: !!(userMetadata as DiscordUserMetadata).avatar_url,
|
hasAvatarUrl: !!(userMetadata as DiscordUserMetadata).avatar_url,
|
||||||
hasFullName: !!(userMetadata as DiscordUserMetadata).full_name,
|
hasFullName: !!(userMetadata as DiscordUserMetadata).full_name,
|
||||||
hasGlobalName: !!(userMetadata as DiscordUserMetadata).custom_claims?.global_name,
|
hasGlobalName: !!(userMetadata as DiscordUserMetadata).custom_claims?.global_name,
|
||||||
@@ -152,7 +158,7 @@ Deno.serve(async (req) => {
|
|||||||
hasEmail: !!(userMetadata as DiscordUserMetadata).email
|
hasEmail: !!(userMetadata as DiscordUserMetadata).email
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.warn('[OAuth Profile] Discord provider found but no Discord identity in user.identities');
|
edgeLogger.warn('Discord provider found but no Discord identity in user.identities', { action: 'oauth_profile_discord', requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +172,7 @@ Deno.serve(async (req) => {
|
|||||||
avatarUrl = googleData.picture || null;
|
avatarUrl = googleData.picture || null;
|
||||||
displayName = googleData.name || null;
|
displayName = googleData.name || null;
|
||||||
usernameBase = googleData.email?.split('@')[0] || null;
|
usernameBase = googleData.email?.split('@')[0] || null;
|
||||||
console.log('[OAuth Profile] Google user:', { avatarUrl, displayName, usernameBase });
|
edgeLogger.info('Google user', { action: 'oauth_profile_google', requestId: tracking.requestId, avatarUrl, displayName, usernameBase });
|
||||||
} else if (provider === 'discord') {
|
} else if (provider === 'discord') {
|
||||||
const discordData = userMetadata as DiscordUserMetadata;
|
const discordData = userMetadata as DiscordUserMetadata;
|
||||||
|
|
||||||
@@ -187,15 +193,17 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
// Validation logging
|
// Validation logging
|
||||||
if (!discordId) {
|
if (!discordId) {
|
||||||
console.error('[OAuth Profile] Discord user ID missing from provider_id/sub - OAuth data incomplete');
|
edgeLogger.error('Discord user ID missing from provider_id/sub - OAuth data incomplete', { action: 'oauth_profile_discord', requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!usernameBase) {
|
if (!usernameBase) {
|
||||||
console.warn('[OAuth Profile] Discord username missing - using ID as fallback');
|
edgeLogger.warn('Discord username missing - using ID as fallback', { action: 'oauth_profile_discord', requestId: tracking.requestId });
|
||||||
usernameBase = discordId;
|
usernameBase = discordId;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[OAuth Profile] Discord user (Supabase format):', {
|
edgeLogger.info('Discord user (Supabase format)', {
|
||||||
|
action: 'oauth_profile_discord',
|
||||||
|
requestId: tracking.requestId,
|
||||||
avatarUrl,
|
avatarUrl,
|
||||||
displayName,
|
displayName,
|
||||||
usernameBase,
|
usernameBase,
|
||||||
@@ -205,7 +213,7 @@ Deno.serve(async (req) => {
|
|||||||
source: discordData.avatar_url ? 'avatar_url' : discordData.picture ? 'picture' : 'none'
|
source: discordData.avatar_url ? 'avatar_url' : discordData.picture ? 'picture' : 'none'
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('[OAuth Profile] Unsupported provider:', provider);
|
edgeLogger.info('Unsupported provider', { action: 'oauth_profile', provider, requestId: tracking.requestId });
|
||||||
return new Response(JSON.stringify({ success: true, message: 'Provider not supported' }), {
|
return new Response(JSON.stringify({ success: true, message: 'Provider not supported' }), {
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
@@ -220,7 +228,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
if (profile?.avatar_image_id) {
|
if (profile?.avatar_image_id) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.log('[OAuth Profile] Avatar already exists, skipping', { requestId: tracking.requestId, duration });
|
edgeLogger.info('Avatar already exists, skipping', { action: 'oauth_profile', requestId: tracking.requestId, duration });
|
||||||
return new Response(JSON.stringify({ success: true, message: 'Avatar already exists', requestId: tracking.requestId }), {
|
return new Response(JSON.stringify({ success: true, message: 'Avatar already exists', requestId: tracking.requestId }), {
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
|
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
|
||||||
});
|
});
|
||||||
@@ -233,14 +241,19 @@ Deno.serve(async (req) => {
|
|||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
// Validate secrets before attempting upload
|
// Validate secrets before attempting upload
|
||||||
if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
|
if (!CLOUDFLARE_ACCOUNT_ID || !CLOUDFLARE_API_TOKEN) {
|
||||||
console.warn('[OAuth Profile] Cloudflare secrets not configured, skipping avatar upload');
|
edgeLogger.warn('Cloudflare secrets not configured, skipping avatar upload', {
|
||||||
console.warn('[OAuth Profile] Missing:', {
|
action: 'oauth_profile_upload',
|
||||||
|
requestId: tracking.requestId
|
||||||
|
});
|
||||||
|
edgeLogger.warn('Missing Cloudflare configuration', {
|
||||||
|
action: 'oauth_profile_upload',
|
||||||
|
requestId: tracking.requestId,
|
||||||
accountId: !CLOUDFLARE_ACCOUNT_ID,
|
accountId: !CLOUDFLARE_ACCOUNT_ID,
|
||||||
apiToken: !CLOUDFLARE_API_TOKEN,
|
apiToken: !CLOUDFLARE_API_TOKEN,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
console.log('[OAuth Profile] Downloading avatar from:', avatarUrl);
|
edgeLogger.info('Downloading avatar', { action: 'oauth_profile_upload', avatarUrl, requestId: tracking.requestId });
|
||||||
|
|
||||||
// Download image with timeout
|
// Download image with timeout
|
||||||
const controller = new AbortController();
|
const controller = new AbortController();
|
||||||
@@ -262,7 +275,9 @@ Deno.serve(async (req) => {
|
|||||||
throw new Error('Image too large (max 10MB)');
|
throw new Error('Image too large (max 10MB)');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[OAuth Profile] Downloaded image:', {
|
edgeLogger.info('Downloaded image', {
|
||||||
|
action: 'oauth_profile_upload',
|
||||||
|
requestId: tracking.requestId,
|
||||||
size: imageBlob.size,
|
size: imageBlob.size,
|
||||||
type: imageBlob.type,
|
type: imageBlob.type,
|
||||||
});
|
});
|
||||||
@@ -285,7 +300,7 @@ Deno.serve(async (req) => {
|
|||||||
const uploadData = await uploadUrlResponse.json();
|
const uploadData = await uploadUrlResponse.json();
|
||||||
const uploadURL = uploadData.result.uploadURL;
|
const uploadURL = uploadData.result.uploadURL;
|
||||||
|
|
||||||
console.log('[OAuth Profile] Got Cloudflare upload URL');
|
edgeLogger.info('Got Cloudflare upload URL', { action: 'oauth_profile_upload', requestId: tracking.requestId });
|
||||||
|
|
||||||
// Upload to Cloudflare
|
// Upload to Cloudflare
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
@@ -305,12 +320,14 @@ Deno.serve(async (req) => {
|
|||||||
if (result.success) {
|
if (result.success) {
|
||||||
cloudflareImageId = result.result.id;
|
cloudflareImageId = result.result.id;
|
||||||
cloudflareImageUrl = `https://cdn.thrillwiki.com/images/${cloudflareImageId}/avatar`;
|
cloudflareImageUrl = `https://cdn.thrillwiki.com/images/${cloudflareImageId}/avatar`;
|
||||||
console.log('[OAuth Profile] Uploaded to Cloudflare:', { cloudflareImageId, cloudflareImageUrl });
|
edgeLogger.info('Uploaded to Cloudflare', { action: 'oauth_profile_upload', requestId: tracking.requestId, cloudflareImageId, cloudflareImageUrl });
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Cloudflare upload failed');
|
throw new Error('Cloudflare upload failed');
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[OAuth Profile] Avatar upload failed:', {
|
edgeLogger.error('Avatar upload failed', {
|
||||||
|
action: 'oauth_profile_upload',
|
||||||
|
requestId: tracking.requestId,
|
||||||
error: error.message,
|
error: error.message,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
accountId: CLOUDFLARE_ACCOUNT_ID,
|
accountId: CLOUDFLARE_ACCOUNT_ID,
|
||||||
@@ -338,7 +355,7 @@ Deno.serve(async (req) => {
|
|||||||
if (usernameBase && profile?.username?.startsWith('user_')) {
|
if (usernameBase && profile?.username?.startsWith('user_')) {
|
||||||
const newUsername = await ensureUniqueUsername(supabase, usernameBase, user.id);
|
const newUsername = await ensureUniqueUsername(supabase, usernameBase, user.id);
|
||||||
updateData.username = newUsername;
|
updateData.username = newUsername;
|
||||||
console.log('[OAuth Profile] Updating generic username from', profile.username, 'to', newUsername);
|
edgeLogger.info('Updating generic username', { action: 'oauth_profile', requestId: tracking.requestId, oldUsername: profile.username, newUsername });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only update if we have data to update
|
// Only update if we have data to update
|
||||||
@@ -349,18 +366,18 @@ Deno.serve(async (req) => {
|
|||||||
.eq('user_id', user.id);
|
.eq('user_id', user.id);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('[OAuth Profile] Failed to update profile:', updateError);
|
edgeLogger.error('Failed to update profile', { action: 'oauth_profile', requestId: tracking.requestId, error: updateError });
|
||||||
return new Response(JSON.stringify({ error: 'Failed to update profile' }), {
|
return new Response(JSON.stringify({ error: 'Failed to update profile' }), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('[OAuth Profile] Profile updated successfully', { requestId: tracking.requestId });
|
edgeLogger.info('Profile updated successfully', { action: 'oauth_profile', requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.log('[OAuth Profile] Processing complete', { requestId: tracking.requestId, duration });
|
edgeLogger.info('Processing complete', { action: 'oauth_profile', requestId: tracking.requestId, duration });
|
||||||
|
|
||||||
return new Response(JSON.stringify({
|
return new Response(JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -373,7 +390,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.error('[OAuth Profile] Error:', error, { requestId: tracking.requestId, duration });
|
edgeLogger.error('Error in oauth profile processing', { action: 'oauth_profile', requestId: tracking.requestId, duration, error: error.message });
|
||||||
return new Response(JSON.stringify({ error: error.message, requestId: tracking.requestId }), {
|
return new Response(JSON.stringify({ error: error.message, requestId: tracking.requestId }), {
|
||||||
status: 500,
|
status: 500,
|
||||||
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
|
headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId },
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts';
|
||||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2';
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -21,7 +21,7 @@ serve(async (req) => {
|
|||||||
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') ?? ''
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log('Processing scheduled account deletions...', { requestId: tracking.requestId });
|
edgeLogger.info('Processing scheduled account deletions', { action: 'scheduled_deletions', requestId: tracking.requestId });
|
||||||
|
|
||||||
// Find confirmed deletion requests that are past their scheduled date
|
// Find confirmed deletion requests that are past their scheduled date
|
||||||
const { data: pendingDeletions, error: fetchError } = await supabaseAdmin
|
const { data: pendingDeletions, error: fetchError } = await supabaseAdmin
|
||||||
@@ -35,7 +35,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!pendingDeletions || pendingDeletions.length === 0) {
|
if (!pendingDeletions || pendingDeletions.length === 0) {
|
||||||
console.log('No deletions to process');
|
edgeLogger.info('No deletions to process', { action: 'scheduled_deletions', requestId: tracking.requestId });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -57,14 +57,14 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Found ${pendingDeletions.length} deletion(s) to process`);
|
edgeLogger.info('Found deletions to process', { action: 'scheduled_deletions', count: pendingDeletions.length, requestId: tracking.requestId });
|
||||||
|
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
let errorCount = 0;
|
let errorCount = 0;
|
||||||
|
|
||||||
for (const deletion of pendingDeletions) {
|
for (const deletion of pendingDeletions) {
|
||||||
try {
|
try {
|
||||||
console.log(`Processing deletion for user: ${deletion.user_id}`);
|
edgeLogger.info('Processing deletion for user', { action: 'scheduled_deletions', userId: deletion.user_id });
|
||||||
|
|
||||||
// Get user email for confirmation email
|
// Get user email for confirmation email
|
||||||
const { data: userData } = await supabaseAdmin.auth.admin.getUserById(deletion.user_id);
|
const { data: userData } = await supabaseAdmin.auth.admin.getUserById(deletion.user_id);
|
||||||
@@ -100,7 +100,7 @@ serve(async (req) => {
|
|||||||
|
|
||||||
if (cloudflareAccountId && cloudflareApiToken) {
|
if (cloudflareAccountId && cloudflareApiToken) {
|
||||||
try {
|
try {
|
||||||
console.log(`Deleting avatar image: ${profile.avatar_image_id}`);
|
edgeLogger.info('Deleting avatar image', { action: 'scheduled_deletions', avatarId: profile.avatar_image_id });
|
||||||
const deleteResponse = await fetch(
|
const deleteResponse = await fetch(
|
||||||
`https://api.cloudflare.com/client/v4/accounts/${cloudflareAccountId}/images/v1/${profile.avatar_image_id}`,
|
`https://api.cloudflare.com/client/v4/accounts/${cloudflareAccountId}/images/v1/${profile.avatar_image_id}`,
|
||||||
{
|
{
|
||||||
@@ -112,12 +112,12 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (!deleteResponse.ok) {
|
if (!deleteResponse.ok) {
|
||||||
console.error('Failed to delete avatar from Cloudflare:', await deleteResponse.text());
|
edgeLogger.error('Failed to delete avatar from Cloudflare', { action: 'scheduled_deletions', error: await deleteResponse.text() });
|
||||||
} else {
|
} else {
|
||||||
console.log('Avatar deleted from Cloudflare successfully');
|
edgeLogger.info('Avatar deleted from Cloudflare successfully', { action: 'scheduled_deletions' });
|
||||||
}
|
}
|
||||||
} catch (avatarError) {
|
} catch (avatarError) {
|
||||||
console.error('Error deleting avatar from Cloudflare:', avatarError);
|
edgeLogger.error('Error deleting avatar from Cloudflare', { action: 'scheduled_deletions', error: avatarError });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -130,7 +130,7 @@ serve(async (req) => {
|
|||||||
|
|
||||||
// Remove from Novu before deleting auth user
|
// Remove from Novu before deleting auth user
|
||||||
try {
|
try {
|
||||||
console.log(`Removing Novu subscriber: ${deletion.user_id}`);
|
edgeLogger.info('Removing Novu subscriber', { action: 'scheduled_deletions', userId: deletion.user_id });
|
||||||
|
|
||||||
const { error: novuError } = await supabaseAdmin.functions.invoke(
|
const { error: novuError } = await supabaseAdmin.functions.invoke(
|
||||||
'remove-novu-subscriber',
|
'remove-novu-subscriber',
|
||||||
@@ -143,13 +143,13 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (novuError) {
|
if (novuError) {
|
||||||
console.error('Failed to remove Novu subscriber:', novuError);
|
edgeLogger.error('Failed to remove Novu subscriber', { action: 'scheduled_deletions', error: novuError });
|
||||||
} else {
|
} else {
|
||||||
console.log('Novu subscriber removed successfully');
|
edgeLogger.info('Novu subscriber removed successfully', { action: 'scheduled_deletions' });
|
||||||
}
|
}
|
||||||
} catch (novuError) {
|
} catch (novuError) {
|
||||||
// Non-blocking - log but continue with deletion
|
// Non-blocking - log but continue with deletion
|
||||||
console.error('Error removing Novu subscriber:', novuError);
|
edgeLogger.error('Error removing Novu subscriber', { action: 'scheduled_deletions', error: novuError });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update deletion request status
|
// Update deletion request status
|
||||||
@@ -190,20 +190,20 @@ serve(async (req) => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('Failed to send confirmation email:', emailError);
|
edgeLogger.error('Failed to send confirmation email', { action: 'scheduled_deletions', error: emailError });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
successCount++;
|
successCount++;
|
||||||
console.log(`Successfully deleted account for user: ${deletion.user_id}`);
|
edgeLogger.info('Successfully deleted account for user', { action: 'scheduled_deletions', userId: deletion.user_id });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorCount++;
|
errorCount++;
|
||||||
console.error(`Failed to delete account for user ${deletion.user_id}:`, error);
|
edgeLogger.error('Failed to delete account for user', { action: 'scheduled_deletions', userId: deletion.user_id, error });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Processed ${successCount} deletion(s) successfully, ${errorCount} error(s)`);
|
edgeLogger.info('Processed deletions', { action: 'scheduled_deletions', successCount, errorCount, requestId: tracking.requestId });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -225,7 +225,7 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing scheduled deletions:', error);
|
edgeLogger.error('Error processing scheduled deletions', { action: 'scheduled_deletions', error: error.message, requestId: tracking.requestId });
|
||||||
|
|
||||||
endRequest(tracking, 500, error.message);
|
endRequest(tracking, 500, error.message);
|
||||||
|
|
||||||
|
|||||||
@@ -501,7 +501,7 @@ serve(withRateLimit(async (req) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (setUserIdError) {
|
if (setUserIdError) {
|
||||||
console.error('[APPROVAL] Failed to set user context:', { error: setUserIdError.message });
|
edgeLogger.error('Failed to set user context', { action: 'approval_set_context', error: setUserIdError.message, requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set submission ID for version tracking
|
// Set submission ID for version tracking
|
||||||
@@ -512,7 +512,7 @@ serve(withRateLimit(async (req) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (setSubmissionIdError) {
|
if (setSubmissionIdError) {
|
||||||
console.error('[APPROVAL] Failed to set submission context:', { error: setSubmissionIdError.message });
|
edgeLogger.error('Failed to set submission context', { action: 'approval_set_context', error: setSubmissionIdError.message, requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve dependencies in item data
|
// Resolve dependencies in item data
|
||||||
@@ -702,7 +702,7 @@ serve(withRateLimit(async (req) => {
|
|||||||
.eq('id', submissionId);
|
.eq('id', submissionId);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('[APPROVAL] Failed to update submission status:', { error: updateError.message });
|
edgeLogger.error('Failed to update submission status', { action: 'approval_update_status', error: updateError.message, requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log audit trail for submission action
|
// Log audit trail for submission action
|
||||||
@@ -731,7 +731,7 @@ serve(withRateLimit(async (req) => {
|
|||||||
});
|
});
|
||||||
} catch (auditError) {
|
} catch (auditError) {
|
||||||
// Log but don't fail the operation
|
// Log but don't fail the operation
|
||||||
console.error('[AUDIT] Failed to log admin action:', auditError);
|
edgeLogger.error('Failed to log admin action', { action: 'approval_audit_log', error: auditError, requestId: tracking.requestId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
@@ -754,10 +754,10 @@ serve(withRateLimit(async (req) => {
|
|||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
const errorMessage = error instanceof Error ? error.message : 'An unexpected error occurred';
|
||||||
console.error('[APPROVAL ERROR] Process failed:', {
|
edgeLogger.error('Approval process failed', {
|
||||||
|
action: 'approval_process_error',
|
||||||
error: errorMessage,
|
error: errorMessage,
|
||||||
userId: authenticatedUserId,
|
userId: authenticatedUserId,
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
requestId: tracking.requestId,
|
requestId: tracking.requestId,
|
||||||
duration
|
duration
|
||||||
});
|
});
|
||||||
@@ -1059,7 +1059,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Check if this is an edit (has park_id) or a new creation
|
// Check if this is an edit (has park_id) or a new creation
|
||||||
if (data.park_id) {
|
if (data.park_id) {
|
||||||
console.log(`Updating existing park ${data.park_id}`);
|
edgeLogger.info('Updating existing park', { action: 'approval_update_park', parkId: data.park_id });
|
||||||
parkId = data.park_id;
|
parkId = data.park_id;
|
||||||
delete data.park_id; // Remove ID from update data
|
delete data.park_id; // Remove ID from update data
|
||||||
|
|
||||||
@@ -1073,7 +1073,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
if (error) throw new Error(`Failed to update park: ${error.message}`);
|
if (error) throw new Error(`Failed to update park: ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log('Creating new park');
|
edgeLogger.info('Creating new park', { action: 'approval_create_park' });
|
||||||
const normalizedData = normalizeStatusValue(data);
|
const normalizedData = normalizeStatusValue(data);
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
const filteredData = filterDatabaseFields(sanitizedData, PARK_FIELDS);
|
||||||
@@ -1089,7 +1089,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Insert photos into photos table
|
// Insert photos into photos table
|
||||||
if (uploadedPhotos.length > 0 && submitterId) {
|
if (uploadedPhotos.length > 0 && submitterId) {
|
||||||
console.log(`Inserting ${uploadedPhotos.length} photos for park ${parkId}`);
|
edgeLogger.info('Inserting photos for park', { action: 'approval_insert_photos', photoCount: uploadedPhotos.length, parkId });
|
||||||
for (let i = 0; i < uploadedPhotos.length; i++) {
|
for (let i = 0; i < uploadedPhotos.length; i++) {
|
||||||
const photo = uploadedPhotos[i];
|
const photo = uploadedPhotos[i];
|
||||||
if (photo.cloudflare_id && photo.url) {
|
if (photo.cloudflare_id && photo.url) {
|
||||||
@@ -1106,7 +1106,7 @@ async function createPark(supabase: any, data: any): Promise<string> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (photoError) {
|
if (photoError) {
|
||||||
console.error(`Failed to insert photo ${i}:`, photoError);
|
edgeLogger.error('Failed to insert photo', { action: 'approval_insert_photo', photoIndex: i, error: photoError.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1160,7 +1160,7 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Check if this is an edit (has ride_id) or a new creation
|
// Check if this is an edit (has ride_id) or a new creation
|
||||||
if (data.ride_id) {
|
if (data.ride_id) {
|
||||||
console.log(`Updating existing ride ${data.ride_id}`);
|
edgeLogger.info('Updating existing ride', { action: 'approval_update_ride', rideId: data.ride_id });
|
||||||
rideId = data.ride_id;
|
rideId = data.ride_id;
|
||||||
delete data.ride_id; // Remove ID from update data
|
delete data.ride_id; // Remove ID from update data
|
||||||
|
|
||||||
@@ -1176,17 +1176,17 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Update park ride counts after successful ride update
|
// Update park ride counts after successful ride update
|
||||||
if (parkId) {
|
if (parkId) {
|
||||||
console.log(`Updating ride counts for park ${parkId}`);
|
edgeLogger.info('Updating ride counts for park', { action: 'approval_update_counts', parkId });
|
||||||
const { error: countError } = await supabase.rpc('update_park_ride_counts', {
|
const { error: countError } = await supabase.rpc('update_park_ride_counts', {
|
||||||
target_park_id: parkId
|
target_park_id: parkId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (countError) {
|
if (countError) {
|
||||||
console.error('Failed to update park counts:', countError);
|
edgeLogger.error('Failed to update park counts', { action: 'approval_update_counts', error: countError.message, parkId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('Creating new ride');
|
edgeLogger.info('Creating new ride', { action: 'approval_create_ride' });
|
||||||
const normalizedData = normalizeStatusValue(data);
|
const normalizedData = normalizeStatusValue(data);
|
||||||
const sanitizedData = sanitizeDateFields(normalizedData);
|
const sanitizedData = sanitizeDateFields(normalizedData);
|
||||||
const filteredData = filterDatabaseFields(sanitizedData, RIDE_FIELDS);
|
const filteredData = filterDatabaseFields(sanitizedData, RIDE_FIELDS);
|
||||||
@@ -1201,20 +1201,20 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Update park ride counts after successful ride creation
|
// Update park ride counts after successful ride creation
|
||||||
if (parkId) {
|
if (parkId) {
|
||||||
console.log(`Updating ride counts for park ${parkId}`);
|
edgeLogger.info('Updating ride counts for park', { action: 'approval_update_counts', parkId });
|
||||||
const { error: countError } = await supabase.rpc('update_park_ride_counts', {
|
const { error: countError } = await supabase.rpc('update_park_ride_counts', {
|
||||||
target_park_id: parkId
|
target_park_id: parkId
|
||||||
});
|
});
|
||||||
|
|
||||||
if (countError) {
|
if (countError) {
|
||||||
console.error('Failed to update park counts:', countError);
|
edgeLogger.error('Failed to update park counts', { action: 'approval_update_counts', error: countError.message, parkId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert photos into photos table
|
// Insert photos into photos table
|
||||||
if (uploadedPhotos.length > 0 && submitterId) {
|
if (uploadedPhotos.length > 0 && submitterId) {
|
||||||
console.log(`Inserting ${uploadedPhotos.length} photos for ride ${rideId}`);
|
edgeLogger.info('Inserting photos for ride', { action: 'approval_insert_photos', photoCount: uploadedPhotos.length, rideId });
|
||||||
for (let i = 0; i < uploadedPhotos.length; i++) {
|
for (let i = 0; i < uploadedPhotos.length; i++) {
|
||||||
const photo = uploadedPhotos[i];
|
const photo = uploadedPhotos[i];
|
||||||
if (photo.cloudflare_id && photo.url) {
|
if (photo.cloudflare_id && photo.url) {
|
||||||
@@ -1231,7 +1231,7 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (photoError) {
|
if (photoError) {
|
||||||
console.error(`Failed to insert photo ${i}:`, photoError);
|
edgeLogger.error('Failed to insert photo', { action: 'approval_insert_photo', photoIndex: i, error: photoError.message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1239,7 +1239,7 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Insert technical specifications
|
// Insert technical specifications
|
||||||
if (technicalSpecifications.length > 0) {
|
if (technicalSpecifications.length > 0) {
|
||||||
console.log(`Inserting ${technicalSpecifications.length} technical specs for ride ${rideId}`);
|
edgeLogger.info('Inserting technical specs for ride', { action: 'approval_insert_specs', specCount: technicalSpecifications.length, rideId });
|
||||||
const techSpecsToInsert = technicalSpecifications.map((spec: any) => ({
|
const techSpecsToInsert = technicalSpecifications.map((spec: any) => ({
|
||||||
ride_id: rideId,
|
ride_id: rideId,
|
||||||
spec_name: spec.spec_name,
|
spec_name: spec.spec_name,
|
||||||
@@ -1254,13 +1254,13 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
.insert(techSpecsToInsert);
|
.insert(techSpecsToInsert);
|
||||||
|
|
||||||
if (techSpecError) {
|
if (techSpecError) {
|
||||||
console.error('Failed to insert technical specifications:', techSpecError);
|
edgeLogger.error('Failed to insert technical specifications', { action: 'approval_insert_specs', error: techSpecError.message, rideId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert coaster statistics
|
// Insert coaster statistics
|
||||||
if (coasterStatistics.length > 0) {
|
if (coasterStatistics.length > 0) {
|
||||||
console.log(`Inserting ${coasterStatistics.length} coaster stats for ride ${rideId}`);
|
edgeLogger.info('Inserting coaster stats for ride', { action: 'approval_insert_stats', statCount: coasterStatistics.length, rideId });
|
||||||
const statsToInsert = coasterStatistics.map((stat: any) => ({
|
const statsToInsert = coasterStatistics.map((stat: any) => ({
|
||||||
ride_id: rideId,
|
ride_id: rideId,
|
||||||
stat_name: stat.stat_name,
|
stat_name: stat.stat_name,
|
||||||
@@ -1276,13 +1276,13 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
.insert(statsToInsert);
|
.insert(statsToInsert);
|
||||||
|
|
||||||
if (statsError) {
|
if (statsError) {
|
||||||
console.error('Failed to insert coaster statistics:', statsError);
|
edgeLogger.error('Failed to insert coaster statistics', { action: 'approval_insert_stats', error: statsError.message, rideId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert name history
|
// Insert name history
|
||||||
if (nameHistory.length > 0) {
|
if (nameHistory.length > 0) {
|
||||||
console.log(`Inserting ${nameHistory.length} former names for ride ${rideId}`);
|
edgeLogger.info('Inserting name history for ride', { action: 'approval_insert_names', nameCount: nameHistory.length, rideId });
|
||||||
const namesToInsert = nameHistory.map((name: any) => ({
|
const namesToInsert = nameHistory.map((name: any) => ({
|
||||||
ride_id: rideId,
|
ride_id: rideId,
|
||||||
former_name: name.former_name,
|
former_name: name.former_name,
|
||||||
@@ -1298,7 +1298,7 @@ async function createRide(supabase: any, data: any): Promise<string> {
|
|||||||
.insert(namesToInsert);
|
.insert(namesToInsert);
|
||||||
|
|
||||||
if (namesError) {
|
if (namesError) {
|
||||||
console.error('Failed to insert name history:', namesError);
|
edgeLogger.error('Failed to insert name history', { action: 'approval_insert_names', error: namesError.message, rideId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1332,7 +1332,7 @@ async function createCompany(supabase: any, data: any, companyType: string): Pro
|
|||||||
const companyId = data.company_id || data.id;
|
const companyId = data.company_id || data.id;
|
||||||
|
|
||||||
if (companyId) {
|
if (companyId) {
|
||||||
console.log(`Updating existing company ${companyId}`);
|
edgeLogger.info('Updating existing company', { action: 'approval_update_company', companyId });
|
||||||
const updateData = sanitizeDateFields({ ...data, company_type: companyType });
|
const updateData = sanitizeDateFields({ ...data, company_type: companyType });
|
||||||
delete updateData.company_id;
|
delete updateData.company_id;
|
||||||
delete updateData.id; // Remove ID from update data
|
delete updateData.id; // Remove ID from update data
|
||||||
@@ -1346,7 +1346,7 @@ async function createCompany(supabase: any, data: any, companyType: string): Pro
|
|||||||
if (error) throw new Error(`Failed to update company: ${error.message}`);
|
if (error) throw new Error(`Failed to update company: ${error.message}`);
|
||||||
return companyId;
|
return companyId;
|
||||||
} else {
|
} else {
|
||||||
console.log('Creating new company');
|
edgeLogger.info('Creating new company', { action: 'approval_create_company' });
|
||||||
const companyData = sanitizeDateFields({ ...data, company_type: companyType });
|
const companyData = sanitizeDateFields({ ...data, company_type: companyType });
|
||||||
const filteredData = filterDatabaseFields(companyData, COMPANY_FIELDS);
|
const filteredData = filterDatabaseFields(companyData, COMPANY_FIELDS);
|
||||||
const { data: company, error } = await supabase
|
const { data: company, error } = await supabase
|
||||||
@@ -1371,7 +1371,7 @@ async function createRideModel(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Check if this is an edit (has ride_model_id) or a new creation
|
// Check if this is an edit (has ride_model_id) or a new creation
|
||||||
if (data.ride_model_id) {
|
if (data.ride_model_id) {
|
||||||
console.log(`Updating existing ride model ${data.ride_model_id}`);
|
edgeLogger.info('Updating existing ride model', { action: 'approval_update_model', rideModelId: data.ride_model_id });
|
||||||
rideModelId = data.ride_model_id;
|
rideModelId = data.ride_model_id;
|
||||||
delete data.ride_model_id; // Remove ID from update data
|
delete data.ride_model_id; // Remove ID from update data
|
||||||
|
|
||||||
@@ -1384,7 +1384,7 @@ async function createRideModel(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
if (error) throw new Error(`Failed to update ride model: ${error.message}`);
|
if (error) throw new Error(`Failed to update ride model: ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
console.log('Creating new ride model');
|
edgeLogger.info('Creating new ride model', { action: 'approval_create_model' });
|
||||||
|
|
||||||
// Validate required fields
|
// Validate required fields
|
||||||
if (!data.manufacturer_id) {
|
if (!data.manufacturer_id) {
|
||||||
@@ -1408,7 +1408,7 @@ async function createRideModel(supabase: any, data: any): Promise<string> {
|
|||||||
|
|
||||||
// Insert technical specifications
|
// Insert technical specifications
|
||||||
if (technicalSpecifications.length > 0) {
|
if (technicalSpecifications.length > 0) {
|
||||||
console.log(`Inserting ${technicalSpecifications.length} technical specs for ride model ${rideModelId}`);
|
edgeLogger.info('Inserting technical specs for ride model', { action: 'approval_insert_model_specs', specCount: technicalSpecifications.length, rideModelId });
|
||||||
const techSpecsToInsert = technicalSpecifications.map((spec: any) => ({
|
const techSpecsToInsert = technicalSpecifications.map((spec: any) => ({
|
||||||
ride_model_id: rideModelId,
|
ride_model_id: rideModelId,
|
||||||
spec_name: spec.spec_name,
|
spec_name: spec.spec_name,
|
||||||
@@ -1423,7 +1423,7 @@ async function createRideModel(supabase: any, data: any): Promise<string> {
|
|||||||
.insert(techSpecsToInsert);
|
.insert(techSpecsToInsert);
|
||||||
|
|
||||||
if (techSpecError) {
|
if (techSpecError) {
|
||||||
console.error('Failed to insert technical specifications:', techSpecError);
|
edgeLogger.error('Failed to insert technical specifications', { action: 'approval_insert_model_specs', error: techSpecError.message, rideModelId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1448,7 +1448,7 @@ async function approvePhotos(supabase: any, data: any, submissionItemId: string)
|
|||||||
|
|
||||||
const { error } = await supabase.from('photos').insert(photoData);
|
const { error } = await supabase.from('photos').insert(photoData);
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('Failed to insert photo:', error);
|
edgeLogger.error('Failed to insert photo', { action: 'approval_insert_photo', error: error.message });
|
||||||
throw new Error(`Failed to insert photo: ${error.message}`);
|
throw new Error(`Failed to insert photo: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1460,7 +1460,7 @@ function extractImageId(url: string): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function editPhoto(supabase: any, data: any): Promise<void> {
|
async function editPhoto(supabase: any, data: any): Promise<void> {
|
||||||
console.log(`Editing photo ${data.photo_id}`);
|
edgeLogger.info('Editing photo', { action: 'approval_edit_photo', photoId: data.photo_id });
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('photos')
|
.from('photos')
|
||||||
.update({
|
.update({
|
||||||
@@ -1472,7 +1472,7 @@ async function editPhoto(supabase: any, data: any): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deletePhoto(supabase: any, data: any): Promise<void> {
|
async function deletePhoto(supabase: any, data: any): Promise<void> {
|
||||||
console.log(`Deleting photo ${data.photo_id}`);
|
edgeLogger.info('Deleting photo', { action: 'approval_delete_photo', photoId: data.photo_id });
|
||||||
const { error } = await supabase
|
const { error } = await supabase
|
||||||
.from('photos')
|
.from('photos')
|
||||||
.delete()
|
.delete()
|
||||||
@@ -1493,7 +1493,7 @@ async function createTimelineEvent(
|
|||||||
const eventId = data.id || data.event_id;
|
const eventId = data.id || data.event_id;
|
||||||
|
|
||||||
if (eventId) {
|
if (eventId) {
|
||||||
console.log(`Updating existing timeline event ${eventId}`);
|
edgeLogger.info('Updating existing timeline event', { action: 'approval_update_timeline', eventId });
|
||||||
|
|
||||||
// Prepare update data (exclude ID and audit fields)
|
// Prepare update data (exclude ID and audit fields)
|
||||||
const updateData: any = {
|
const updateData: any = {
|
||||||
@@ -1524,7 +1524,7 @@ async function createTimelineEvent(
|
|||||||
if (error) throw new Error(`Failed to update timeline event: ${error.message}`);
|
if (error) throw new Error(`Failed to update timeline event: ${error.message}`);
|
||||||
return eventId;
|
return eventId;
|
||||||
} else {
|
} else {
|
||||||
console.log('Creating new timeline event');
|
edgeLogger.info('Creating new timeline event', { action: 'approval_create_timeline' });
|
||||||
|
|
||||||
const eventData = {
|
const eventData = {
|
||||||
entity_id: data.entity_id,
|
entity_id: data.entity_id,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -43,27 +43,27 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Removing subscriber from "users" topic:', { subscriberId, requestId: tracking.requestId });
|
edgeLogger.info('Removing subscriber from users topic', { action: 'remove_novu_subscriber', subscriberId, requestId: tracking.requestId });
|
||||||
|
|
||||||
// Remove subscriber from "users" topic
|
// Remove subscriber from "users" topic
|
||||||
try {
|
try {
|
||||||
await novu.topics.removeSubscribers('users', {
|
await novu.topics.removeSubscribers('users', {
|
||||||
subscribers: [subscriberId],
|
subscribers: [subscriberId],
|
||||||
});
|
});
|
||||||
console.log('Successfully removed subscriber from "users" topic', { subscriberId });
|
edgeLogger.info('Successfully removed subscriber from users topic', { action: 'remove_novu_subscriber', subscriberId });
|
||||||
} catch (topicError: any) {
|
} catch (topicError: any) {
|
||||||
console.error('Failed to remove subscriber from "users" topic:', topicError.message, { subscriberId });
|
edgeLogger.error('Failed to remove subscriber from users topic', { action: 'remove_novu_subscriber', subscriberId, error: topicError.message });
|
||||||
// Continue - we still want to delete the subscriber if requested
|
// Continue - we still want to delete the subscriber if requested
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally delete the subscriber entirely from Novu
|
// Optionally delete the subscriber entirely from Novu
|
||||||
if (deleteSubscriber) {
|
if (deleteSubscriber) {
|
||||||
try {
|
try {
|
||||||
console.log('Deleting subscriber from Novu:', { subscriberId });
|
edgeLogger.info('Deleting subscriber from Novu', { action: 'remove_novu_subscriber', subscriberId });
|
||||||
await novu.subscribers.delete(subscriberId);
|
await novu.subscribers.delete(subscriberId);
|
||||||
console.log('Successfully deleted subscriber from Novu', { subscriberId });
|
edgeLogger.info('Successfully deleted subscriber from Novu', { action: 'remove_novu_subscriber', subscriberId });
|
||||||
} catch (deleteError: any) {
|
} catch (deleteError: any) {
|
||||||
console.error('Failed to delete subscriber from Novu:', deleteError.message, { subscriberId });
|
edgeLogger.error('Failed to delete subscriber from Novu', { action: 'remove_novu_subscriber', subscriberId, error: deleteError.message });
|
||||||
throw deleteError;
|
throw deleteError;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,7 +89,7 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
console.error('Error removing Novu subscriber:', errorMessage, { requestId: tracking.requestId });
|
edgeLogger.error('Error removing Novu subscriber', { action: 'remove_novu_subscriber', error: errorMessage, requestId: tracking.requestId });
|
||||||
|
|
||||||
endRequest(tracking, 500, errorMessage);
|
endRequest(tracking, 500, errorMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -164,9 +164,12 @@ serve(async (req) => {
|
|||||||
html: emailPayload.html,
|
html: emailPayload.html,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log('Deletion confirmation email sent');
|
edgeLogger.info('Deletion confirmation email sent', { requestId: tracking.requestId });
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('Failed to send email:', emailError);
|
edgeLogger.error('Failed to send email', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: emailError.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,9 +126,12 @@ serve(async (req) => {
|
|||||||
`,
|
`,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
console.log('New confirmation code email sent');
|
edgeLogger.info('New confirmation code email sent', { requestId: tracking.requestId });
|
||||||
} catch (emailError) {
|
} catch (emailError) {
|
||||||
console.error('Failed to send email:', emailError);
|
edgeLogger.error('Failed to send email', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: emailError.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.57.4';
|
||||||
|
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -89,7 +90,7 @@ async function registerTestEntity(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (error && error.code !== '23505') { // Ignore unique constraint violations
|
if (error && error.code !== '23505') { // Ignore unique constraint violations
|
||||||
console.error(`Error registering ${entityType} ${slug}:`, error);
|
edgeLogger.error(`Error registering ${entityType} ${slug}`, { error: error.message, entityType, slug });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +104,7 @@ async function getExistingTestEntities(
|
|||||||
.eq('entity_type', entityType);
|
.eq('entity_type', entityType);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(`Error fetching existing ${entityType}:`, error);
|
edgeLogger.error(`Error fetching existing ${entityType}`, { error: error.message, entityType });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,7 +122,7 @@ async function getPendingSubmissionItems(
|
|||||||
.eq('status', 'pending');
|
.eq('status', 'pending');
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error(`Error fetching pending ${itemType} items:`, error);
|
edgeLogger.error(`Error fetching pending ${itemType} items`, { error: error.message, itemType });
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +130,8 @@ async function getPendingSubmissionItems(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Deno.serve(async (req) => {
|
Deno.serve(async (req) => {
|
||||||
|
const tracking = startRequest();
|
||||||
|
|
||||||
if (req.method === 'OPTIONS') {
|
if (req.method === 'OPTIONS') {
|
||||||
return new Response(null, { headers: corsHeaders });
|
return new Response(null, { headers: corsHeaders });
|
||||||
}
|
}
|
||||||
@@ -176,12 +179,17 @@ Deno.serve(async (req) => {
|
|||||||
stage
|
stage
|
||||||
}: SeedOptions = await req.json();
|
}: SeedOptions = await req.json();
|
||||||
|
|
||||||
console.info('=== SEED DATA GENERATION DEBUG ===');
|
edgeLogger.info('Seed data generation started', {
|
||||||
console.info('Received entityTypes:', JSON.stringify(entityTypes));
|
requestId: tracking.requestId,
|
||||||
console.info('entityTypes is array:', Array.isArray(entityTypes));
|
entityTypes,
|
||||||
console.info('entityTypes length:', entityTypes?.length);
|
preset,
|
||||||
console.info('Preset:', preset);
|
fieldDensity,
|
||||||
console.info('===================================');
|
includeDependencies,
|
||||||
|
includeConflicts,
|
||||||
|
includeVersionChains,
|
||||||
|
includeEscalated,
|
||||||
|
includeExpiredLocks
|
||||||
|
});
|
||||||
|
|
||||||
const plan = PRESETS[preset];
|
const plan = PRESETS[preset];
|
||||||
if (!plan) {
|
if (!plan) {
|
||||||
@@ -212,7 +220,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
// Load existing test entities from registry if includeDependencies is true
|
// Load existing test entities from registry if includeDependencies is true
|
||||||
if (includeDependencies) {
|
if (includeDependencies) {
|
||||||
console.log('Loading existing test entities from registry...');
|
edgeLogger.info('Loading existing test entities from registry', { requestId: tracking.requestId });
|
||||||
|
|
||||||
const existingOperators = await getExistingTestEntities(supabase, 'operator');
|
const existingOperators = await getExistingTestEntities(supabase, 'operator');
|
||||||
const existingOwners = await getExistingTestEntities(supabase, 'property_owner');
|
const existingOwners = await getExistingTestEntities(supabase, 'property_owner');
|
||||||
@@ -260,8 +268,19 @@ Deno.serve(async (req) => {
|
|||||||
pendingParks.forEach(item => createdSubmissionItems.park.push(item.id));
|
pendingParks.forEach(item => createdSubmissionItems.park.push(item.id));
|
||||||
pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id));
|
pendingRideModels.forEach(item => createdSubmissionItems.ride_model.push(item.id));
|
||||||
|
|
||||||
console.log(`Loaded ${existingOperators.length} operators, ${existingOwners.length} owners, ${existingManufacturers.length} manufacturers, ${existingDesigners.length} designers, ${existingParks.length} parks`);
|
edgeLogger.info('Loaded existing entities', {
|
||||||
console.log(`Loaded ${pendingOperators.length} pending operators, ${pendingOwners.length} pending owners, ${pendingManufacturers.length} pending manufacturers, ${pendingDesigners.length} pending designers, ${pendingParks.length} pending parks`);
|
requestId: tracking.requestId,
|
||||||
|
operators: existingOperators.length,
|
||||||
|
owners: existingOwners.length,
|
||||||
|
manufacturers: existingManufacturers.length,
|
||||||
|
designers: existingDesigners.length,
|
||||||
|
parks: existingParks.length,
|
||||||
|
pendingOperators: pendingOperators.length,
|
||||||
|
pendingOwners: pendingOwners.length,
|
||||||
|
pendingManufacturers: pendingManufacturers.length,
|
||||||
|
pendingDesigners: pendingDesigners.length,
|
||||||
|
pendingParks: pendingParks.length
|
||||||
|
});
|
||||||
|
|
||||||
// Add pending parks' slugs to createdParks for ride generation
|
// Add pending parks' slugs to createdParks for ride generation
|
||||||
pendingParks.forEach(item => {
|
pendingParks.forEach(item => {
|
||||||
@@ -271,7 +290,7 @@ Deno.serve(async (req) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Total parks available for rides: ${createdParks.length}`);
|
edgeLogger.info('Parks available for rides', { requestId: tracking.requestId, count: createdParks.length });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to create submission
|
// Helper to create submission
|
||||||
@@ -316,7 +335,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const { error: subError } = await supabase.from('content_submissions').insert(submissionData);
|
const { error: subError } = await supabase.from('content_submissions').insert(submissionData);
|
||||||
if (subError) {
|
if (subError) {
|
||||||
console.error(`Error inserting content_submission for type ${type}:`, subError);
|
edgeLogger.error('Error inserting content_submission', { type, error: subError.message });
|
||||||
throw subError;
|
throw subError;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -350,8 +369,7 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single();
|
const { data: insertedData, error: typeError } = await supabase.from(table).insert(typeData).select('id').single();
|
||||||
if (typeError) {
|
if (typeError) {
|
||||||
console.error(`Error inserting into ${table} for type ${type}:`, typeError);
|
edgeLogger.error('Error inserting into type table', { table, type, error: typeError.message, data: typeData });
|
||||||
console.error('Data being inserted:', JSON.stringify(typeData, null, 2));
|
|
||||||
throw typeError;
|
throw typeError;
|
||||||
}
|
}
|
||||||
return { submissionId, itemId, typeId: insertedData?.id };
|
return { submissionId, itemId, typeId: insertedData?.id };
|
||||||
@@ -373,10 +391,6 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
// Create companies FIRST so parks can reference operators/owners
|
// Create companies FIRST so parks can reference operators/owners
|
||||||
if (!stage || stage === 'companies') {
|
if (!stage || stage === 'companies') {
|
||||||
console.info('=== COMPANY GENERATION CHECK ===');
|
|
||||||
console.info('Entity types array:', JSON.stringify(entityTypes));
|
|
||||||
console.info('Plan calls for companies:', plan.companies);
|
|
||||||
|
|
||||||
const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner'];
|
const companyTypes = ['manufacturer', 'operator', 'designer', 'property_owner'];
|
||||||
|
|
||||||
// First, determine which company types are selected
|
// First, determine which company types are selected
|
||||||
@@ -384,14 +398,17 @@ Deno.serve(async (req) => {
|
|||||||
entityTypes.includes(pluralizeCompanyType(compType))
|
entityTypes.includes(pluralizeCompanyType(compType))
|
||||||
);
|
);
|
||||||
|
|
||||||
console.info(`✓ Selected company types (${selectedCompanyTypes.length}):`, selectedCompanyTypes);
|
edgeLogger.info('Company generation started', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
entityTypes,
|
||||||
|
planCompanies: plan.companies,
|
||||||
|
selectedCompanyTypes
|
||||||
|
});
|
||||||
|
|
||||||
// Calculate fair distribution: base amount per type + extras
|
// Calculate fair distribution: base amount per type + extras
|
||||||
const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length);
|
const basePerType = Math.floor(plan.companies / selectedCompanyTypes.length);
|
||||||
const extras = plan.companies % selectedCompanyTypes.length;
|
const extras = plan.companies % selectedCompanyTypes.length;
|
||||||
|
|
||||||
console.info(`Distribution plan: ${basePerType} base per type, ${extras} extra(s) to first types`);
|
|
||||||
|
|
||||||
let companiesCreatedTotal = 0;
|
let companiesCreatedTotal = 0;
|
||||||
|
|
||||||
for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) {
|
for (let typeIndex = 0; typeIndex < selectedCompanyTypes.length; typeIndex++) {
|
||||||
@@ -401,10 +418,9 @@ Deno.serve(async (req) => {
|
|||||||
// Each type gets base amount, first N types get +1 extra
|
// Each type gets base amount, first N types get +1 extra
|
||||||
const count = basePerType + (typeIndex < extras ? 1 : 0);
|
const count = basePerType + (typeIndex < extras ? 1 : 0);
|
||||||
|
|
||||||
console.info(`✓ Generating ${count} companies of type ${compType}`);
|
edgeLogger.info('Creating companies', { requestId: tracking.requestId, compType, count });
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
console.info(` Creating company ${i + 1}/${count} (type: ${compType})`);
|
|
||||||
const level = getPopulationLevel(fieldDensity, i);
|
const level = getPopulationLevel(fieldDensity, i);
|
||||||
const companyData: any = {
|
const companyData: any = {
|
||||||
name: `Test ${compType.replace('_', ' ')} ${i + 1}`,
|
name: `Test ${compType.replace('_', ' ')} ${i + 1}`,
|
||||||
@@ -461,8 +477,8 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
// Create parks
|
// Create parks
|
||||||
if ((!stage || stage === 'parks') && entityTypes.includes('parks')) {
|
if ((!stage || stage === 'parks') && entityTypes.includes('parks')) {
|
||||||
|
edgeLogger.info('Creating parks', { requestId: tracking.requestId, count: plan.parks });
|
||||||
for (let i = 0; i < plan.parks; i++) {
|
for (let i = 0; i < plan.parks; i++) {
|
||||||
console.log(` Creating park ${i + 1}/${plan.parks}`);
|
|
||||||
const level = getPopulationLevel(fieldDensity, i);
|
const level = getPopulationLevel(fieldDensity, i);
|
||||||
const shouldConflict = includeConflicts && createdParkSlugs.length > 0 && Math.random() < 0.15;
|
const shouldConflict = includeConflicts && createdParkSlugs.length > 0 && Math.random() < 0.15;
|
||||||
const shouldVersionChain = includeVersionChains && createdParkSlugs.length > 0 && Math.random() < 0.15;
|
const shouldVersionChain = includeVersionChains && createdParkSlugs.length > 0 && Math.random() < 0.15;
|
||||||
@@ -824,20 +840,33 @@ Deno.serve(async (req) => {
|
|||||||
|
|
||||||
const executionTime = Date.now() - startTime;
|
const executionTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
edgeLogger.info('Seed data generation completed', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration: executionTime,
|
||||||
|
summary,
|
||||||
|
stage: stage || 'all'
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
success: true,
|
success: true,
|
||||||
summary,
|
summary,
|
||||||
time: (executionTime / 1000).toFixed(2),
|
time: (executionTime / 1000).toFixed(2),
|
||||||
stage: stage || 'all'
|
stage: stage || 'all',
|
||||||
|
requestId: tracking.requestId
|
||||||
}),
|
}),
|
||||||
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||||
);
|
);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Seed error:', error);
|
const duration = endRequest(tracking);
|
||||||
|
edgeLogger.error('Seed error', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({ error: error.message }),
|
JSON.stringify({ error: error.message, requestId: tracking.requestId }),
|
||||||
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
{ status: 500, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
|
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -12,10 +13,6 @@ interface EscalationRequest {
|
|||||||
escalatedBy: string;
|
escalatedBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple request tracking
|
|
||||||
const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() });
|
|
||||||
const endRequest = (tracking: { start: number }) => Date.now() - tracking.start;
|
|
||||||
|
|
||||||
serve(async (req) => {
|
serve(async (req) => {
|
||||||
const tracking = startRequest();
|
const tracking = startRequest();
|
||||||
|
|
||||||
@@ -31,7 +28,12 @@ serve(async (req) => {
|
|||||||
|
|
||||||
const { submissionId, escalationReason, escalatedBy }: EscalationRequest = await req.json();
|
const { submissionId, escalationReason, escalatedBy }: EscalationRequest = await req.json();
|
||||||
|
|
||||||
console.log('Processing escalation notification:', { submissionId, escalationReason, escalatedBy });
|
edgeLogger.info('Processing escalation notification', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
submissionId,
|
||||||
|
escalatedBy,
|
||||||
|
action: 'send_escalation'
|
||||||
|
});
|
||||||
|
|
||||||
// Fetch submission details
|
// Fetch submission details
|
||||||
const { data: submission, error: submissionError } = await supabase
|
const { data: submission, error: submissionError } = await supabase
|
||||||
@@ -52,7 +54,11 @@ serve(async (req) => {
|
|||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (escalatorError) {
|
if (escalatorError) {
|
||||||
console.error('Failed to fetch escalator profile:', escalatorError);
|
edgeLogger.error('Failed to fetch escalator profile', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: escalatorError.message,
|
||||||
|
escalatedBy
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch submission items count
|
// Fetch submission items count
|
||||||
@@ -62,7 +68,11 @@ serve(async (req) => {
|
|||||||
.eq('submission_id', submissionId);
|
.eq('submission_id', submissionId);
|
||||||
|
|
||||||
if (countError) {
|
if (countError) {
|
||||||
console.error('Failed to fetch items count:', countError);
|
edgeLogger.error('Failed to fetch items count', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: countError.message,
|
||||||
|
submissionId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare email content
|
// Prepare email content
|
||||||
@@ -185,7 +195,10 @@ serve(async (req) => {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
} catch (fetchError) {
|
} catch (fetchError) {
|
||||||
console.error('Network error sending email:', fetchError);
|
edgeLogger.error('Network error sending email', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: fetchError.message
|
||||||
|
});
|
||||||
throw new Error('Network error: Unable to reach email service');
|
throw new Error('Network error: Unable to reach email service');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +209,11 @@ serve(async (req) => {
|
|||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
errorText = 'Unable to parse error response';
|
errorText = 'Unable to parse error response';
|
||||||
}
|
}
|
||||||
console.error('ForwardEmail API error:', errorText);
|
edgeLogger.error('ForwardEmail API error', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
status: emailResponse.status,
|
||||||
|
errorText
|
||||||
|
});
|
||||||
throw new Error(`Failed to send email: ${emailResponse.status} - ${errorText}`);
|
throw new Error(`Failed to send email: ${emailResponse.status} - ${errorText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,10 +221,16 @@ serve(async (req) => {
|
|||||||
try {
|
try {
|
||||||
emailResult = await emailResponse.json();
|
emailResult = await emailResponse.json();
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
console.error('Failed to parse email API response:', parseError);
|
edgeLogger.error('Failed to parse email API response', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: parseError.message
|
||||||
|
});
|
||||||
throw new Error('Invalid response from email service');
|
throw new Error('Invalid response from email service');
|
||||||
}
|
}
|
||||||
console.log('Email sent successfully:', emailResult);
|
edgeLogger.info('Email sent successfully', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
emailId: emailResult.id
|
||||||
|
});
|
||||||
|
|
||||||
// Update submission with notification status
|
// Update submission with notification status
|
||||||
const { error: updateError } = await supabase
|
const { error: updateError } = await supabase
|
||||||
@@ -221,11 +244,20 @@ serve(async (req) => {
|
|||||||
.eq('id', submissionId);
|
.eq('id', submissionId);
|
||||||
|
|
||||||
if (updateError) {
|
if (updateError) {
|
||||||
console.error('Failed to update submission escalation status:', updateError);
|
edgeLogger.error('Failed to update submission escalation status', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: updateError.message,
|
||||||
|
submissionId
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.log('Escalation notification sent', { requestId: tracking.requestId, duration, emailId: emailResult.id });
|
edgeLogger.info('Escalation notification sent', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration,
|
||||||
|
emailId: emailResult.id,
|
||||||
|
submissionId
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -242,7 +274,11 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.error('Error in send-escalation-notification:', error, { requestId: tracking.requestId, duration });
|
edgeLogger.error('Error in send-escalation-notification', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown error'
|
||||||
|
});
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ serve(async (req) => {
|
|||||||
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com';
|
const fromEmail = Deno.env.get('FROM_EMAIL_ADDRESS') || 'noreply@thrillwiki.com';
|
||||||
|
|
||||||
if (!forwardEmailKey) {
|
if (!forwardEmailKey) {
|
||||||
console.error('FORWARDEMAIL_API_KEY not configured');
|
edgeLogger.error('FORWARDEMAIL_API_KEY not configured', { requestId: tracking.requestId });
|
||||||
throw new Error('Email service not configured');
|
throw new Error('Email service not configured');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,11 @@ serve(async (req) => {
|
|||||||
|
|
||||||
if (!emailResponse.ok) {
|
if (!emailResponse.ok) {
|
||||||
const errorText = await emailResponse.text();
|
const errorText = await emailResponse.text();
|
||||||
console.error('ForwardEmail API error:', errorText);
|
edgeLogger.error('ForwardEmail API error', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
status: emailResponse.status,
|
||||||
|
errorText
|
||||||
|
});
|
||||||
throw new Error(`Failed to send email: ${emailResponse.statusText}`);
|
throw new Error(`Failed to send email: ${emailResponse.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
|
import { edgeLogger, startRequest, endRequest } from '../_shared/logger.ts';
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -12,10 +13,6 @@ const TOPICS = {
|
|||||||
MODERATION_REPORTS: 'moderation-reports',
|
MODERATION_REPORTS: 'moderation-reports',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Simple request tracking
|
|
||||||
const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() });
|
|
||||||
const endRequest = (tracking: { start: number }) => Date.now() - tracking.start;
|
|
||||||
|
|
||||||
serve(async (req) => {
|
serve(async (req) => {
|
||||||
const tracking = startRequest();
|
const tracking = startRequest();
|
||||||
|
|
||||||
@@ -35,7 +32,10 @@ serve(async (req) => {
|
|||||||
const novu = new Novu({ secretKey: novuApiKey });
|
const novu = new Novu({ secretKey: novuApiKey });
|
||||||
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
const supabase = createClient(supabaseUrl, supabaseServiceKey);
|
||||||
|
|
||||||
console.log('Starting moderator sync to Novu topics...');
|
edgeLogger.info('Starting moderator sync to Novu topics', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
action: 'sync_moderators'
|
||||||
|
});
|
||||||
|
|
||||||
// Get all moderators, admins, and superusers
|
// Get all moderators, admins, and superusers
|
||||||
const { data: moderatorRoles, error: rolesError } = await supabase
|
const { data: moderatorRoles, error: rolesError } = await supabase
|
||||||
@@ -50,7 +50,10 @@ serve(async (req) => {
|
|||||||
// Get unique user IDs (a user might have multiple moderator-level roles)
|
// Get unique user IDs (a user might have multiple moderator-level roles)
|
||||||
const uniqueUserIds = [...new Set(moderatorRoles.map(r => r.user_id))];
|
const uniqueUserIds = [...new Set(moderatorRoles.map(r => r.user_id))];
|
||||||
|
|
||||||
console.log(`Found ${uniqueUserIds.length} unique moderators to sync`);
|
edgeLogger.info('Found unique moderators to sync', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
count: uniqueUserIds.length
|
||||||
|
});
|
||||||
|
|
||||||
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
|
const topics = [TOPICS.MODERATION_SUBMISSIONS, TOPICS.MODERATION_REPORTS];
|
||||||
const results = {
|
const results = {
|
||||||
@@ -62,11 +65,15 @@ serve(async (req) => {
|
|||||||
try {
|
try {
|
||||||
// Ensure topic exists (Novu will create it if it doesn't)
|
// Ensure topic exists (Novu will create it if it doesn't)
|
||||||
await novu.topics.create({ key: topicKey, name: topicKey });
|
await novu.topics.create({ key: topicKey, name: topicKey });
|
||||||
console.log(`Topic ${topicKey} ready`);
|
edgeLogger.info('Topic ready', { requestId: tracking.requestId, topicKey });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Topic might already exist, which is fine
|
// Topic might already exist, which is fine
|
||||||
if (!error.message?.includes('already exists')) {
|
if (!error.message?.includes('already exists')) {
|
||||||
console.warn(`Note about topic ${topicKey}:`, error.message);
|
edgeLogger.warn('Note about topic', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
topicKey,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,10 +90,19 @@ serve(async (req) => {
|
|||||||
subscribers: batch,
|
subscribers: batch,
|
||||||
});
|
});
|
||||||
successCount += batch.length;
|
successCount += batch.length;
|
||||||
console.log(`Added batch of ${batch.length} users to ${topicKey}`);
|
edgeLogger.info('Added batch of users to topic', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
topicKey,
|
||||||
|
batchSize: batch.length
|
||||||
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
errorCount += batch.length;
|
errorCount += batch.length;
|
||||||
console.error(`Error adding batch to ${topicKey}:`, error.message);
|
edgeLogger.error('Error adding batch to topic', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
topicKey,
|
||||||
|
batchSize: batch.length,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +114,11 @@ serve(async (req) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.log('Sync completed:', results, { requestId: tracking.requestId, duration });
|
edgeLogger.info('Sync completed', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration,
|
||||||
|
results
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
@@ -118,7 +138,11 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
const duration = endRequest(tracking);
|
const duration = endRequest(tracking);
|
||||||
console.error('Error syncing moderators to topics:', error, { requestId: tracking.requestId, duration });
|
edgeLogger.error('Error syncing moderators to topics', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
duration,
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -42,7 +42,12 @@ serve(async (req) => {
|
|||||||
? { subscriberId }
|
? { subscriberId }
|
||||||
: { topicKey: topicKey! };
|
: { topicKey: topicKey! };
|
||||||
|
|
||||||
console.log('Triggering notification:', { workflowId, recipient, requestId: tracking.requestId });
|
edgeLogger.info('Triggering notification', {
|
||||||
|
workflowId,
|
||||||
|
recipient,
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
action: 'trigger_notification'
|
||||||
|
});
|
||||||
|
|
||||||
const result = await novu.trigger({
|
const result = await novu.trigger({
|
||||||
to: recipient,
|
to: recipient,
|
||||||
@@ -51,7 +56,10 @@ serve(async (req) => {
|
|||||||
overrides,
|
overrides,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Notification triggered successfully:', result.data);
|
edgeLogger.info('Notification triggered successfully', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
transactionId: result.data.transactionId
|
||||||
|
});
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -72,7 +80,10 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
console.error('Error triggering notification:', errorMessage);
|
edgeLogger.error('Error triggering notification', {
|
||||||
|
requestId: tracking.requestId,
|
||||||
|
error: errorMessage
|
||||||
|
});
|
||||||
|
|
||||||
endRequest(tracking, 500, errorMessage);
|
endRequest(tracking, 500, errorMessage);
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
||||||
import { Novu } from "npm:@novu/api@1.6.0";
|
import { Novu } from "npm:@novu/api@1.6.0";
|
||||||
import { startRequest, endRequest } from "../_shared/logger.ts";
|
import { edgeLogger, startRequest, endRequest } from "../_shared/logger.ts";
|
||||||
|
|
||||||
const corsHeaders = {
|
const corsHeaders = {
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
@@ -35,7 +35,7 @@ serve(async (req) => {
|
|||||||
data?: Record<string, unknown>;
|
data?: Record<string, unknown>;
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('Updating Novu subscriber:', { subscriberId, email, firstName, requestId: tracking.requestId });
|
edgeLogger.info('Updating Novu subscriber', { action: 'update_novu_subscriber', subscriberId, email, firstName, requestId: tracking.requestId });
|
||||||
|
|
||||||
const subscriber = await novu.subscribers.update(subscriberId, {
|
const subscriber = await novu.subscribers.update(subscriberId, {
|
||||||
email,
|
email,
|
||||||
@@ -46,7 +46,7 @@ serve(async (req) => {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Subscriber updated successfully:', subscriber.data);
|
edgeLogger.info('Subscriber updated successfully', { action: 'update_novu_subscriber', subscriberId: subscriber.data._id });
|
||||||
|
|
||||||
endRequest(tracking, 200);
|
endRequest(tracking, 200);
|
||||||
|
|
||||||
@@ -67,7 +67,7 @@ serve(async (req) => {
|
|||||||
);
|
);
|
||||||
} catch (error: unknown) {
|
} catch (error: unknown) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||||
console.error('Error updating Novu subscriber:', errorMessage);
|
edgeLogger.error('Error updating Novu subscriber', { action: 'update_novu_subscriber', error: errorMessage, requestId: tracking.requestId });
|
||||||
|
|
||||||
endRequest(tracking, 500, errorMessage);
|
endRequest(tracking, 500, errorMessage);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user