Refactor code structure and remove redundant changes

This commit is contained in:
pacnpal
2025-11-09 16:31:34 -05:00
parent 2884bc23ce
commit eb68cf40c6
1080 changed files with 27361 additions and 56687 deletions

View File

@@ -0,0 +1,209 @@
/**
* TypeScript types for relational audit tables
* Replaces JSONB columns with proper relational structures
*/
// ============================================================================
// ADMIN AUDIT DETAILS (replaces admin_audit_log.details)
// ============================================================================
export interface AdminAuditDetail {
id: string;
audit_log_id: string;
detail_key: string;
detail_value: string;
created_at: string;
}
export interface AdminAuditDetailInsert {
audit_log_id: string;
detail_key: string;
detail_value: string;
}
// ============================================================================
// MODERATION AUDIT METADATA (replaces moderation_audit_log.metadata)
// ============================================================================
export interface ModerationAuditMetadata {
id: string;
audit_log_id: string;
metadata_key: string;
metadata_value: string;
created_at: string;
}
export interface ModerationAuditMetadataInsert {
audit_log_id: string;
metadata_key: string;
metadata_value: string;
}
// ============================================================================
// PROFILE CHANGE FIELDS (replaces profile_audit_log.changes)
// ============================================================================
export interface ProfileChangeField {
id: string;
audit_log_id: string;
field_name: string;
old_value: string | null;
new_value: string | null;
created_at: string;
}
export interface ProfileChangeFieldInsert {
audit_log_id: string;
field_name: string;
old_value?: string | null;
new_value?: string | null;
}
// ============================================================================
// ITEM CHANGE FIELDS (replaces item_edit_history.changes)
// ============================================================================
export interface ItemChangeField {
id: string;
edit_history_id: string;
field_name: string;
old_value: string | null;
new_value: string | null;
created_at: string;
}
export interface ItemChangeFieldInsert {
edit_history_id: string;
field_name: string;
old_value?: string | null;
new_value?: string | null;
}
// ============================================================================
// REQUEST BREADCRUMBS (replaces request_metadata.breadcrumbs)
// ============================================================================
export interface RequestBreadcrumb {
id: string;
request_id: string;
timestamp: string;
category: string;
message: string;
level: 'debug' | 'info' | 'warn' | 'error';
sequence_order: number;
created_at: string;
}
export interface RequestBreadcrumbInsert {
request_id: string;
timestamp: string;
category: string;
message: string;
level?: 'debug' | 'info' | 'warn' | 'error';
sequence_order: number;
}
// ============================================================================
// SUBMISSION METADATA (replaces content_submissions.content)
// ============================================================================
export interface SubmissionMetadata {
id: string;
submission_id: string;
metadata_key: string;
metadata_value: string;
value_type: 'string' | 'number' | 'boolean' | 'date' | 'url' | 'json';
display_order: number;
created_at: string;
updated_at: string;
}
export interface SubmissionMetadataInsert {
submission_id: string;
metadata_key: string;
metadata_value: string;
value_type?: 'string' | 'number' | 'boolean' | 'date' | 'url' | 'json';
display_order?: number;
}
// ============================================================================
// REVIEW PHOTOS (replaces reviews.photos)
// ============================================================================
export interface ReviewPhoto {
id: string;
review_id: string;
cloudflare_image_id: string;
cloudflare_image_url: string;
caption: string | null;
order_index: number;
created_at: string;
updated_at: string;
}
export interface ReviewPhotoInsert {
review_id: string;
cloudflare_image_id: string;
cloudflare_image_url: string;
caption?: string | null;
order_index?: number;
}
// ============================================================================
// NOTIFICATION EVENT DATA (replaces notification_logs.payload)
// ============================================================================
export interface NotificationEventData {
id: string;
notification_log_id: string;
event_key: string;
event_value: string;
created_at: string;
}
export interface NotificationEventDataInsert {
notification_log_id: string;
event_key: string;
event_value: string;
}
// ============================================================================
// CONFLICT DETAIL FIELDS (replaces conflict_resolutions.conflict_details)
// ============================================================================
export interface ConflictDetailField {
id: string;
conflict_resolution_id: string;
field_name: string;
conflicting_value_1: string | null;
conflicting_value_2: string | null;
resolved_value: string | null;
created_at: string;
}
export interface ConflictDetailFieldInsert {
conflict_resolution_id: string;
field_name: string;
conflicting_value_1?: string | null;
conflicting_value_2?: string | null;
resolved_value?: string | null;
}
// ============================================================================
// HELPER TYPES
// ============================================================================
/**
* Generic key-value structure for reading audit details
*/
export type AuditDetailsRecord = Record<string, string>;
/**
* Generic change structure for reading change fields
*/
export interface ChangeRecord {
old_value: string | null;
new_value: string | null;
}
export type ChangesRecord = Record<string, ChangeRecord>;

122
src-old/types/auth.ts Normal file
View File

@@ -0,0 +1,122 @@
import type { Session, User } from '@supabase/supabase-js';
/**
* Authenticator Assurance Levels (AAL)
* - aal1: Basic authentication (password/OAuth/magic link)
* - aal2: Multi-factor authentication completed
*/
export type AALLevel = 'aal1' | 'aal2';
/**
* MFA Factor types supported by Supabase
*/
export type MFAFactorType = 'totp';
/**
* MFA Factor status
*/
export type MFAFactorStatus = 'verified' | 'unverified';
/**
* MFA Factor structure from Supabase
*/
export interface MFAFactor {
id: string;
factor_type: MFAFactorType;
status: MFAFactorStatus;
friendly_name?: string;
created_at: string;
updated_at: string;
}
/**
* Result of AAL step-up check
*/
export interface CheckAalResult {
needsStepUp: boolean;
hasMfaEnrolled: boolean;
currentLevel: AALLevel | null;
hasEnrolledFactors?: boolean;
factorId?: string;
}
/**
* Authentication method types
*/
export type AuthMethod = 'password' | 'oauth' | 'magiclink';
/**
* Authentication session with AAL information
*/
export interface AuthSessionInfo {
session: Session | null;
user: User | null;
aal: AALLevel;
authMethod?: AuthMethod;
}
/**
* MFA Challenge result
*/
export interface MFAChallengeResult {
success: boolean;
error?: string;
newAal?: AALLevel;
}
/**
* Auth service response
*/
export interface AuthServiceResponse<T = void> {
success: boolean;
data?: T;
error?: string;
}
/**
* Authentication session from Supabase with hashed IP
*/
export interface AuthSession {
id: string;
created_at: string;
updated_at: string;
refreshed_at: string | null;
user_agent: string | null;
ip: string | null; // Pre-hashed by database function
not_after: string | null;
aal: string | null; // Changed to string to match get_my_sessions() return type
}
/**
* Security-sensitive operations that may require additional verification
*/
export type SecurityOperation =
| 'password_change'
| 'identity_disconnect'
| 'identity_connect'
| 'session_revoke'
| 'mfa_enroll'
| 'mfa_unenroll';
/**
* Rate limit information for security operations
*/
export interface RateLimitInfo {
operation: SecurityOperation;
allowed: boolean;
attemptsRemaining: number;
resetAt: Date;
currentAttempts: number;
maxAttempts: number;
}
/**
* Security operation context for logging
*/
export interface SecurityContext {
operation: SecurityOperation;
userId?: string;
targetUserId?: string;
requiresMFA?: boolean;
metadata?: Record<string, any>;
}

View File

@@ -0,0 +1,84 @@
/**
* Company Data Types
*
* Type-safe interfaces for company database records and form data.
* These types prevent `as any` casts when working with company entities.
*/
export interface CompanyDatabaseRecord {
id: string;
name: string;
slug: string;
description?: string | null;
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
person_type: 'company' | 'individual' | 'firm' | 'organization';
website_url?: string | null;
founded_year?: number | null;
headquarters_location?: string | null;
logo_url?: string | null;
banner_image_url?: string | null;
card_image_url?: string | null;
created_at: string;
updated_at: string;
// Stats fields
total_rides?: number;
average_rating?: number;
review_count?: number;
}
export interface TimelineEventDatabaseRecord {
id: string;
entity_id: string;
entity_type: 'park' | 'ride' | 'company' | 'ride_model';
event_type: string;
event_date: string;
event_date_precision: 'day' | 'month' | 'year';
title: string;
description?: string | null;
from_value?: string | null;
to_value?: string | null;
from_entity_id?: string | null;
to_entity_id?: string | null;
from_location_id?: string | null;
to_location_id?: string | null;
display_order: number;
created_by?: string | null;
approved_by?: string | null;
submission_id?: string | null;
created_at: string;
updated_at: string;
}
export interface LocationData {
country?: string;
state_province?: string;
city?: string;
latitude?: number;
longitude?: number;
}
/**
* Type guard for company database records
*/
export function isCompanyRecord(data: unknown): data is CompanyDatabaseRecord {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'company_type' in data
);
}
/**
* Type guard for timeline event records
*/
export function isTimelineEventRecord(data: unknown): data is TimelineEventDatabaseRecord {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'entity_type' in data &&
'event_type' in data
);
}

101
src-old/types/company.ts Normal file
View File

@@ -0,0 +1,101 @@
/**
* Company-related type definitions
*/
import { ImageAssignments } from '@/components/upload/EntityMultiImageUploader';
export interface UploadedImage {
url: string;
cloudflare_id?: string;
file?: File;
isLocal?: boolean;
caption?: string;
}
export interface CompanyFormData {
name: string;
slug: string;
description?: string;
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
person_type: 'company' | 'individual' | 'firm' | 'organization';
website_url?: string;
founded_year?: number;
headquarters_location?: string;
images?: ImageAssignments;
}
export interface TempCompanyData {
name: string;
slug: string;
company_type: 'manufacturer' | 'designer' | 'operator' | 'property_owner';
person_type: 'company' | 'individual' | 'firm' | 'organization';
description?: string;
founded_year?: number;
headquarters_location?: string;
website_url?: string;
}
export interface TempParkData {
name: string;
slug: string;
park_type: string;
status: string;
description?: string;
opening_date?: string;
closing_date?: string;
operator_id?: string;
property_owner_id?: string;
website_url?: string;
phone?: string;
email?: string;
location?: {
name: string;
city?: string;
state_province?: string;
country: string;
postal_code?: string;
latitude: number;
longitude: number;
};
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
_tempNewOperator?: TempCompanyData;
_tempNewPropertyOwner?: TempCompanyData;
}
export interface TempRideModelData {
name: string;
slug: string;
category: string;
ride_type: string;
description?: string;
images?: {
uploaded: UploadedImage[];
banner_assignment?: number | null;
card_assignment?: number | null;
};
_technical_specifications?: TechnicalSpecification[];
}
export interface TechnicalSpecification {
id?: string;
spec_name: string;
spec_value: string;
spec_type: 'string' | 'number' | 'boolean' | 'date';
category?: string;
unit?: string;
display_order: number;
}
export interface CoasterStat {
id?: string;
stat_type: string;
value: string;
unit?: string;
}
export type CoasterStatValue = string | number | null;
export type TechnicalSpecValue = string | number | null;

View File

@@ -0,0 +1,40 @@
/**
* Type definitions for composite submissions
* Used when creating multiple related entities in a single submission
*/
import type { TempCompanyData } from './company';
/**
* Composite submission structure for park creation with related entities
*/
export interface ParkCompositeSubmission {
park: {
name: string;
slug: string;
description?: string;
park_type: string;
status: string;
opening_date?: string;
closing_date?: string;
location_id?: string;
website_url?: string;
phone?: string;
email?: string;
operator_id?: string | null;
property_owner_id?: string | null;
[key: string]: unknown;
};
new_operator?: TempCompanyData;
new_property_owner?: TempCompanyData;
}
/**
* Generic composite submission content type
* Supports any entity type with optional related entities
*/
export interface CompositeSubmissionContent {
[entityKey: string]: {
[key: string]: unknown;
} | TempCompanyData | undefined;
}

View File

@@ -0,0 +1,135 @@
/**
* Data Export Type Definitions
*
* Types for user data export, statistics, and activity logs.
*/
/**
* User statistics aggregated from various tables
*/
export interface UserStatistics {
ride_count: number;
coaster_count: number;
park_count: number;
review_count: number;
reputation_score: number;
photo_count: number;
list_count: number;
submission_count: number;
account_created: string;
last_updated: string;
}
/**
* Individual activity log entry from profile_audit_log
*/
export interface ActivityLogEntry {
id: string;
action: string;
changes: Record<string, any>;
created_at: string;
changed_by: string;
ip_address_hash?: string;
user_agent?: string;
}
/**
* Profile data for export
*/
export interface ExportProfileData {
username: string;
display_name: string | null;
bio: string | null;
preferred_pronouns: string | null;
personal_location: string | null;
timezone: string;
preferred_language: string;
theme_preference: string;
privacy_level: string;
created_at: string;
updated_at: string;
}
/**
* Review data for export
*/
export interface ExportReviewData {
id: string;
rating: number;
review_text: string | null;
ride_name?: string;
park_name?: string;
created_at: string;
}
/**
* User list data for export
*/
export interface ExportListData {
id: string;
name: string;
description: string | null;
is_public: boolean;
item_count: number;
created_at: string;
}
/**
* Complete export data structure
*/
export interface ExportDataStructure {
export_date: string;
user_id: string;
profile: ExportProfileData;
statistics: UserStatistics;
reviews: ExportReviewData[];
lists: ExportListData[];
activity_log: ActivityLogEntry[];
preferences: {
unit_preferences: any;
accessibility_options: any;
notification_preferences: any;
privacy_settings: any;
};
metadata: {
export_version: string;
data_retention_info: string;
instructions: string;
};
}
/**
* Export options
*/
export interface ExportOptions {
include_reviews: boolean;
include_lists: boolean;
include_activity_log: boolean;
include_preferences: boolean;
format: 'json';
}
/**
* Export progress tracking
*/
export interface ExportProgress {
stage: 'starting' | 'fetching_profile' | 'fetching_reviews' | 'fetching_lists' | 'fetching_activity' | 'packaging' | 'complete';
progress: number;
message: string;
}
/**
* Data categories available for export
*/
export type DataCategory = 'profile' | 'reviews' | 'lists' | 'activity_log' | 'preferences';
/**
* Export request result
*/
export interface ExportRequestResult {
success: boolean;
data?: ExportDataStructure;
error?: string;
rate_limited?: boolean;
next_available_at?: string;
}

479
src-old/types/database.ts Normal file
View File

@@ -0,0 +1,479 @@
export interface Location {
id: string;
name: string;
country: string;
state_province?: string | null;
city?: string | null;
postal_code?: string | null;
latitude?: number | null;
longitude?: number | null;
timezone?: string | null;
}
export interface Company {
id: string;
name: string;
slug: string;
description?: string | null;
company_type: string; // Allow any string from database
person_type?: string | null; // Database returns string, validated at form level
website_url?: string | null;
founded_year?: number | null; // Legacy field
founded_date?: string | null;
founded_date_precision?: string | null;
headquarters_location?: string | null;
logo_url?: string | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
average_rating: number | null;
review_count: number | null;
}
export interface Park {
id: string;
name: string;
slug: string;
description?: string | null;
status: string; // Allow any string from database
park_type: string; // Allow any string from database
opening_date?: string | null;
opening_date_precision?: string | null;
closing_date?: string | null;
closing_date_precision?: string | null;
website_url?: string | null;
phone?: string | null;
email?: string | null;
location?: Location | null;
operator?: Company | null;
property_owner?: Company | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
average_rating: number | null;
review_count: number | null;
ride_count: number | null;
coaster_count: number | null;
created_at: string;
updated_at: string;
}
export interface RideTechnicalSpec {
id: string;
ride_id: string;
spec_name: string;
spec_value: string;
spec_type: 'string' | 'number' | 'boolean' | 'date';
category?: string;
unit?: string;
display_order: number;
created_at: string;
}
export interface RideCoasterStat {
id: string;
ride_id: string;
stat_name: string;
stat_value: number;
unit?: string;
category?: string;
description?: string;
display_order: number;
created_at: string;
}
export interface RideNameHistory {
id: string;
ride_id: string;
former_name: string;
date_changed?: string;
date_changed_precision?: string;
reason?: string;
from_year?: number;
to_year?: number;
order_index: number;
created_at: string;
}
export interface RideModelTechnicalSpec {
id: string;
ride_model_id: string;
spec_name: string;
spec_value: string;
spec_type: 'string' | 'number' | 'boolean' | 'date';
category?: string;
unit?: string;
display_order: number;
created_at: string;
}
export interface RideModel {
id: string;
name: string;
slug: string;
manufacturer_id: string;
manufacturer?: Company;
category: 'roller_coaster' | 'flat_ride' | 'water_ride' | 'dark_ride' | 'kiddie_ride' | 'transportation';
ride_type?: string;
description?: string;
// Note: technical_specs deprecated - use ride_model_technical_specifications table
// Load via useTechnicalSpecifications hook instead
banner_image_url?: string;
banner_image_id?: string;
card_image_url?: string;
card_image_id?: string;
created_at?: string;
updated_at?: string;
}
export interface Ride {
id: string;
name: string;
slug: string;
description?: string | null;
park?: Park | { name: string; slug: string; location?: Location }; // Allow partial park data
ride_model?: RideModel;
manufacturer?: Company | null;
designer?: Company | null;
category: string; // Allow any string from database
ride_sub_type?: string | null; // Sub-category like "Flying Coaster", "Inverted Coaster", etc.
status: string; // Allow any string from database
opening_date?: string | null;
opening_date_precision?: string | null;
closing_date?: string | null;
closing_date_precision?: string | null;
height_requirement?: number | null;
age_requirement?: number | null;
capacity_per_hour?: number | null;
duration_seconds?: number | null;
max_speed_kmh?: number | null;
max_height_meters?: number | null;
length_meters?: number | null;
inversions?: number | null;
technical_specifications?: RideTechnicalSpec[];
coaster_statistics?: RideCoasterStat[];
name_history?: RideNameHistory[];
average_rating: number | null;
review_count: number | null;
image_url?: string | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
// Roller coaster specific fields
coaster_type?: string | null;
seating_type?: string | null;
intensity_level?: string | null;
track_material?: string[] | null;
support_material?: string[] | null;
propulsion_method?: string[] | null;
drop_height_meters?: number | null;
max_g_force?: number | null;
// Water ride specific fields
water_depth_cm?: number | null;
splash_height_meters?: number | null;
wetness_level?: string | null;
flume_type?: string | null;
boat_capacity?: number | null;
// Dark ride specific fields
theme_name?: string | null;
story_description?: string | null;
show_duration_seconds?: number | null;
animatronics_count?: number | null;
projection_type?: string | null;
ride_system?: string | null;
scenes_count?: number | null;
// Flat ride specific fields
rotation_type?: string | null;
motion_pattern?: string | null;
platform_count?: number | null;
swing_angle_degrees?: number | null;
rotation_speed_rpm?: number | null;
arm_length_meters?: number | null;
max_height_reached_meters?: number | null;
// Kiddie ride specific fields
min_age?: number | null;
max_age?: number | null;
educational_theme?: string | null;
character_theme?: string | null;
// Transportation specific fields
transport_type?: string | null;
route_length_meters?: number | null;
stations_count?: number | null;
vehicle_capacity?: number | null;
vehicles_count?: number | null;
round_trip_duration_seconds?: number | null;
}
export interface Profile {
id: string;
user_id: string;
username: string;
display_name?: string;
bio?: string;
avatar_url?: string;
avatar_image_id?: string;
preferred_pronouns?: string;
show_pronouns?: boolean;
timezone?: string;
preferred_language?: string;
location?: Location;
location_id?: string;
personal_location?: string;
home_park_id?: string;
date_of_birth?: string;
privacy_level: 'public' | 'friends' | 'private';
theme_preference: 'light' | 'dark' | 'system';
ride_count: number;
coaster_count: number;
park_count: number;
review_count: number;
reputation_score: number;
banned: boolean;
deactivated: boolean;
deactivated_at?: string;
deactivation_reason?: string;
oauth_provider?: string;
created_at: string;
updated_at: string;
}
export interface AccountDeletionRequest {
id: string;
user_id: string;
requested_at: string;
scheduled_deletion_at: string;
confirmation_code: string;
confirmation_code_sent_at?: string;
status: 'pending' | 'confirmed' | 'cancelled' | 'completed'; // Account deletion specific status
cancelled_at?: string;
completed_at?: string;
cancellation_reason?: string;
created_at: string;
updated_at: string;
}
export interface Review {
id: string;
user_id: string;
park?: Park;
ride?: Ride;
rating: number;
title?: string;
content?: string;
visit_date?: string;
wait_time_minutes?: number;
photos?: any;
helpful_votes: number;
total_votes: number;
moderation_status: 'pending' | 'approved' | 'rejected' | 'flagged'; // Review moderation status
created_at: string;
updated_at: string;
}
export interface UserTopList {
id: string;
user_id: string;
title: string;
description?: string | null;
list_type: 'parks' | 'rides' | 'coasters' | 'companies' | 'mixed';
is_public: boolean | null;
created_at: string;
updated_at: string;
items?: UserTopListItem[]; // New relational data
}
export interface UserTopListItem {
id: string;
list_id: string;
entity_type: 'park' | 'ride' | 'company';
entity_id: string;
position: number;
notes?: string | null;
created_at: string;
updated_at: string;
// Populated via joins
entity?: Park | Ride | Company;
}
// Extended company interface with aggregated stats
export interface CompanyWithStats extends Company {
ride_count?: number;
coaster_count?: number;
model_count?: number;
park_count?: number;
}
// Audit log entry - matches actual profile_audit_log table structure
export interface AuditLogEntry {
id: string;
user_id: string;
changed_by: string;
action: string;
changes: Record<string, unknown> | null;
ip_address_hash: string;
user_agent: string;
created_at: string;
}
// Relational data structures (NO JSONB)
export interface RideCoasterStat {
id: string;
ride_id: string;
stat_name: string;
stat_value: number;
unit?: string;
category?: string;
description?: string;
display_order: number;
created_at: string;
updated_at: string;
}
export interface RideTechnicalSpecification {
id: string;
ride_id: string;
spec_name: string;
spec_value: string;
spec_type: string;
category?: string | null;
unit?: string | null;
display_order: number;
created_at: string;
}
export interface RideModelTechnicalSpecification {
id: string;
ride_model_id: string;
spec_name: string;
spec_value: string;
spec_type: string;
category?: string | null;
unit?: string | null;
display_order: number;
created_at: string;
}
export interface ListItem {
id: string;
list_id: string;
entity_type: 'park' | 'ride' | 'coaster';
entity_id: string;
position: number;
notes?: string | null;
created_at: string;
updated_at: string;
}
// User ride credit tracking
export interface UserRideCredit {
id: string;
user_id: string;
ride_id: string;
first_ride_date?: string;
last_ride_date?: string;
ride_count: number;
sort_order?: number;
personal_notes?: string;
personal_rating?: number;
personal_photo_id?: string;
created_at: string;
updated_at: string;
rides?: {
id: string;
name: string;
slug: string;
category: string;
status: string;
coaster_type?: string;
seating_type?: string;
intensity_level?: string;
track_material?: string;
max_speed_kmh?: number;
max_height_meters?: number;
length_meters?: number;
inversions?: number;
card_image_url?: string;
average_rating: number;
review_count: number;
parks?: {
id: string;
name: string;
slug: string;
park_type: string;
locations?: {
country: string;
state_province?: string;
city?: string;
};
};
manufacturer?: {
id: string;
name: string;
};
designer?: {
id: string;
name: string;
};
};
}
// Activity entry - discriminated union for different activity types
export type ActivityEntry =
| ReviewActivity
| SubmissionActivity
| RankingActivity
| GenericActivity;
export interface ReviewActivity {
id: string;
type: 'review';
created_at: string;
rating?: number;
parks?: { slug?: string; name?: string } | null;
rides?: { slug?: string; name?: string; parks?: { slug?: string; name?: string } | null } | null;
content?: string;
title?: string;
description?: string;
}
export interface SubmissionActivity {
id: string;
type: 'submission';
created_at: string;
status?: string;
submission_type?: string;
entity_type?: string;
action?: string;
content?: {
name?: string;
slug?: string;
description?: string;
entity_id?: string;
entity_slug?: string;
park_slug?: string;
park_name?: string;
action?: string;
};
photo_count?: number;
photo_preview?: string;
}
export interface RankingActivity {
id: string;
type: 'ranking';
created_at: string;
title?: string;
description?: string;
list_type?: string;
is_public?: boolean;
}
export interface GenericActivity {
id: string;
type?: string;
created_at: string;
[key: string]: unknown;
}

37
src-old/types/identity.ts Normal file
View File

@@ -0,0 +1,37 @@
/**
* Type definitions for user identity and OAuth provider management
*/
export interface UserIdentity {
id: string;
user_id: string;
identity_data: {
email?: string;
full_name?: string;
avatar_url?: string;
[key: string]: unknown;
};
provider: 'google' | 'discord' | 'email' | 'github' | string;
created_at: string;
updated_at: string;
last_sign_in_at: string;
}
export type OAuthProvider = 'google' | 'discord';
export interface IdentitySafetyCheck {
canDisconnect: boolean;
reason?: 'last_identity' | 'no_password_backup' | 'safe';
hasPasswordAuth: boolean;
totalIdentities: number;
oauthIdentities: number;
}
export interface IdentityOperationResult {
success: boolean;
error?: string;
needsRelogin?: boolean;
needsEmailConfirmation?: boolean;
email?: string;
requiresAAL2?: boolean;
}

84
src-old/types/location.ts Normal file
View File

@@ -0,0 +1,84 @@
/**
* Location & Info Type Definitions
*
* Centralized types for location, personal info, accessibility, and unit preferences.
*/
import type { UnitPreferences } from '@/lib/units';
/**
* Accessibility options stored in user_preferences.accessibility_options
*/
export interface AccessibilityOptions {
font_size: 'small' | 'medium' | 'large';
high_contrast: boolean;
reduced_motion: boolean;
}
/**
* Profile location and personal information
*/
export interface ProfileLocationInfo {
personal_location: string | null;
home_park_id: string | null;
timezone: string;
preferred_language: string;
preferred_pronouns: string | null;
}
/**
* Combined form data for Location tab
*/
export interface LocationFormData extends ProfileLocationInfo {}
/**
* Park data for home park selection
*/
export interface ParkOption {
id: string;
name: string;
location?: {
city?: string;
state_province?: string;
country: string;
};
}
/**
* Complete location & info settings
*/
export interface LocationInfoSettings {
profile: ProfileLocationInfo;
accessibility: AccessibilityOptions;
unitPreferences: UnitPreferences;
}
/**
* Location data structure
*/
export interface LocationData {
street_address?: string;
country?: string;
state_province?: string;
city?: string;
latitude?: number;
longitude?: number;
postal_code?: string;
}
/**
* Type guard for location data
*/
export function isLocationData(data: unknown): data is LocationData {
if (typeof data !== 'object' || data === null) return false;
const loc = data as Record<string, unknown>;
return (
(loc.street_address === undefined || typeof loc.street_address === 'string') &&
(loc.country === undefined || typeof loc.country === 'string') &&
(loc.state_province === undefined || typeof loc.state_province === 'string') &&
(loc.city === undefined || typeof loc.city === 'string') &&
(loc.latitude === undefined || typeof loc.latitude === 'number') &&
(loc.longitude === undefined || typeof loc.longitude === 'number') &&
(loc.postal_code === undefined || typeof loc.postal_code === 'string')
);
}

328
src-old/types/moderation.ts Normal file
View File

@@ -0,0 +1,328 @@
/**
* Moderation Queue Type Definitions
*
* This file contains all TypeScript types and interfaces used by the moderation queue system.
* Extracted from ModerationQueue.tsx to improve maintainability and reusability.
*/
/**
* Photo display interface for moderation queue
*/
export interface PhotoForDisplay {
id: string;
url: string;
cloudflare_image_url: string;
filename: string;
caption?: string | null;
title?: string | null;
date_taken?: string | null;
order_index: number;
}
/**
* Location data interface
*/
export interface LocationData {
id: string;
city?: string | null;
state_province?: string | null;
country: string;
formatted_address?: string | null;
}
/**
* Park submission item data
*/
export interface ParkItemData {
park_id?: string;
name: string;
slug: string;
description?: string;
park_type: string;
status: string;
location_id?: string;
operator_id?: string;
property_owner_id?: string;
opening_date?: string;
closing_date?: string;
source_url?: string;
submission_notes?: string;
}
/**
* Ride submission item data
*/
export interface RideItemData {
ride_id?: string;
name: string;
slug: string;
park_id: string;
manufacturer_id?: string;
designer_id?: string;
model_id?: string;
ride_type: string;
status: string;
opening_date?: string;
closing_date?: string;
source_url?: string;
submission_notes?: string;
}
/**
* Company submission item data
*/
export interface CompanyItemData {
company_id?: string;
name: string;
slug: string;
company_type: string;
country?: string;
founded_year?: number;
source_url?: string;
submission_notes?: string;
}
/**
* Ride model submission item data
*/
export interface RideModelItemData {
model_id?: string;
name: string;
slug: string;
manufacturer_id: string;
model_type: string;
source_url?: string;
submission_notes?: string;
}
/**
* Photo submission item data
*/
export interface PhotoItemData {
photo_url: string;
cloudflare_image_id?: string;
caption?: string;
title?: string;
order_index?: number;
source_url?: string;
submission_notes?: string;
}
/**
* Union type for all submission item data
*/
export type SubmissionItemData =
| ParkItemData
| RideItemData
| CompanyItemData
| RideModelItemData
| PhotoItemData;
/**
* Submission item with resolved entity data from database view
*/
export interface SubmissionItem {
id: string;
item_type: string;
action_type: 'create' | 'edit' | 'delete';
status: string;
order_index: number;
depends_on: string | null;
approved_entity_id: string | null;
rejection_reason: string | null;
// Typed foreign key columns (one will be populated based on item_type)
park_submission_id?: string | null;
ride_submission_id?: string | null;
photo_submission_id?: string | null;
company_submission_id?: string | null;
ride_model_submission_id?: string | null;
timeline_event_submission_id?: string | null;
// Entity data from dynamic join (pre-loaded from view)
entity_data?: {
id: string;
submission_id: string;
name: string;
slug: string;
// Other fields vary by entity type
[key: string]: any;
} | null;
// Legacy support for moderation actions
item_data?: SubmissionItemData;
original_data?: SubmissionItemData;
}
/**
* Represents a single item in the moderation queue.
* Can be either a review or a content submission.
*/
export interface ModerationItem {
/** Unique identifier for the item */
id: string;
/** Type of moderation item */
type: 'review' | 'content_submission';
/** Raw content data (structure varies by type) */
content: any;
/** Timestamp when the item was created (primary field) */
created_at: string;
/** Timestamp when the item was submitted (same as created_at) */
submitted_at?: string;
/** Timestamp when the item was last updated */
updated_at?: string;
/** ID of the user who submitted this item */
user_id: string;
/** ID of the submitter (from view, same as user_id) */
submitter_id?: string;
/** Current status of the item */
status: string;
/** Type of submission (e.g., 'park', 'ride', 'review') */
submission_type?: string;
/** Pre-loaded submitter profile from view (new structure) */
submitter_profile?: {
user_id: string;
username: string;
display_name?: string | null;
avatar_url?: string | null;
};
/** Pre-loaded reviewer profile from view (new structure) */
reviewer_profile?: {
user_id: string;
username: string;
display_name?: string | null;
avatar_url?: string | null;
};
/** Pre-loaded assigned moderator profile from view */
assigned_profile?: {
user_id: string;
username: string;
display_name?: string | null;
avatar_url?: string | null;
};
/** Legacy: Submitter (old field name for backward compatibility) */
submitter?: {
user_id: string;
username: string;
display_name?: string;
avatar_url?: string;
};
/** Legacy: Reviewer (old field name for backward compatibility) */
reviewer?: {
user_id: string;
username: string;
display_name?: string;
avatar_url?: string;
};
/** Legacy: Profile information of the submitting user */
user_profile?: {
username: string;
display_name?: string | null;
avatar_url?: string | null;
};
/** Display name of the entity being modified */
entity_name?: string;
/** Display name of the park (for ride submissions) */
park_name?: string;
/** Timestamp when the item was reviewed */
reviewed_at?: string;
/** ID of the moderator who reviewed this item */
reviewer_id?: string;
/** Username of the moderator who reviewed this item */
reviewed_by?: string;
/** Notes left by the reviewing moderator */
reviewer_notes?: string;
/** Whether this submission has been escalated for senior review */
escalated?: boolean;
/** ID of the moderator this item is assigned to */
assigned_to?: string;
/** Timestamp until which this item is locked by a moderator */
locked_until?: string;
/** Internal flag indicating item is being removed (optimistic update) */
_removing?: boolean;
/** Pre-loaded submission items with entity data from view */
submission_items?: SubmissionItem[];
/** Pre-loaded review photos from relational review_photos table */
review_photos?: Array<{
id: string;
url: string;
caption?: string | null;
order_index: number;
}>;
}
/**
* Filter options for entity types in the moderation queue
*/
export type EntityFilter = 'all' | 'reviews' | 'submissions' | 'photos';
/**
* Filter options for submission status in the moderation queue
*/
export type StatusFilter = 'all' | 'pending' | 'partially_approved' | 'flagged' | 'approved' | 'rejected';
/**
* Available tabs in the moderation interface
*/
export type QueueTab = 'mainQueue' | 'archive';
/**
* Available fields for sorting the moderation queue
*/
export type SortField = 'created_at' | 'submission_type' | 'status';
/**
* Sort direction
*/
export type SortDirection = 'asc' | 'desc';
/**
* Configuration for sorting the moderation queue
*/
export interface SortConfig {
/** Field to sort by */
field: SortField;
/** Sort direction */
direction: SortDirection;
}
/**
* Loading states for the moderation queue
*/
export type LoadingState = 'initial' | 'loading' | 'refreshing' | 'ready';
/**
* Ref interface for the ModerationQueue component
* Allows parent components to trigger actions imperatively
*/
export interface ModerationQueueRef {
/** Manually refresh the queue data */
refresh: () => void;
}

View File

@@ -0,0 +1,78 @@
/**
* Notification Type Definitions
*
* Centralized types for notification system.
* Follows project patterns for type safety and validation.
*/
/**
* Channel preferences for receiving notifications
*/
export interface ChannelPreferences {
in_app: boolean;
email: boolean;
push: boolean;
sms: boolean;
}
/**
* Per-workflow notification preferences
*/
export interface WorkflowPreferences {
[workflowId: string]: boolean;
}
/**
* Notification frequency control settings
*/
export interface FrequencySettings {
digest: 'realtime' | 'hourly' | 'daily' | 'weekly';
max_per_hour: number;
}
/**
* Complete notification preferences structure
*/
export interface NotificationPreferences {
channelPreferences: ChannelPreferences;
workflowPreferences: WorkflowPreferences;
frequencySettings: FrequencySettings;
}
/**
* Notification template from database
*/
export interface NotificationTemplate {
id: string;
workflow_id: string;
novu_workflow_id: string | null;
name: string;
description: string | null;
category: string;
is_active: boolean;
created_at: string;
updated_at: string;
}
/**
* Subscriber data for Novu
*/
export interface SubscriberData {
subscriberId: string;
email?: string;
firstName?: string;
lastName?: string;
phone?: string;
avatar?: string;
data?: Record<string, any>;
}
/**
* Notification payload for triggering workflows
*/
export interface NotificationPayload {
workflowId: string;
subscriberId: string;
payload: Record<string, any>;
overrides?: Record<string, any>;
}

View File

@@ -0,0 +1,46 @@
// TypeScript interfaces for the new photo submission relational tables
export interface PhotoSubmission {
id: string;
submission_id: string;
entity_type: 'park' | 'ride' | 'manufacturer' | 'operator' | 'designer' | 'property_owner';
entity_id: string;
parent_id?: string;
title?: string;
created_at: string;
updated_at: string;
}
export interface PhotoSubmissionItem {
id: string;
photo_submission_id: string;
cloudflare_image_id: string;
cloudflare_image_url: string;
caption?: string | null;
title?: string | null;
filename?: string | null;
order_index: number;
file_size?: number | null;
mime_type?: string | null;
date_taken?: string | null;
date_taken_precision?: string | null;
created_at: string;
}
export interface ReviewPhoto {
id: string;
review_id: string;
cloudflare_image_id: string;
cloudflare_image_url: string;
caption?: string;
order_index: number;
created_at: string;
}
export interface PhotoSubmissionWithItems extends PhotoSubmission {
items: PhotoSubmissionItem[];
submission?: {
user_id: string;
status: string;
};
}

53
src-old/types/photos.ts Normal file
View File

@@ -0,0 +1,53 @@
/**
* Photo-related type definitions
*/
import type { PhotoSubmissionItem } from './photo-submissions';
// Re-export for convenience
export type { PhotoSubmissionItem } from './photo-submissions';
// Core photo display interface
export interface PhotoItem {
id: string;
url: string;
filename: string;
caption?: string | null;
size?: number;
type?: string;
title?: string | null;
date_taken?: string | null;
}
// Normalized photo for consistent display
export interface NormalizedPhoto {
id: string;
url: string;
filename: string;
caption?: string;
title?: string;
date_taken?: string;
order_index: number;
}
// Photo data source types
export type PhotoDataSource =
| { type: 'review'; photos: PhotoItem[] }
| { type: 'submission_jsonb'; photos: PhotoItem[] }
| { type: 'submission_items'; items: PhotoSubmissionItem[] };
// Type guard for photo arrays
export function isPhotoItem(data: unknown): data is PhotoItem {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'url' in data &&
'filename' in data
);
}
// Type guard for photo arrays
export function isPhotoItemArray(data: unknown): data is PhotoItem[] {
return Array.isArray(data) && (data.length === 0 || isPhotoItem(data[0]));
}

58
src-old/types/privacy.ts Normal file
View File

@@ -0,0 +1,58 @@
/**
* Privacy Types
*
* Centralized type definitions for user privacy settings.
*
* Storage:
* - ProfilePrivacySettings: stored in profiles table
* - PrivacySettings: stored in user_preferences.privacy_settings (JSONB)
*
* Security:
* - All privacy settings are validated with Zod before database writes
* - Changes are logged to profile_audit_log for compliance
* - RLS policies ensure users can only modify their own settings
*/
/**
* Privacy settings stored in user_preferences.privacy_settings
*/
export interface PrivacySettings {
activity_visibility: 'public' | 'private';
search_visibility: boolean;
show_location: boolean;
show_age: boolean;
show_avatar: boolean;
show_bio: boolean;
show_activity_stats: boolean;
show_home_park: boolean;
}
/**
* Profile-level privacy settings
*/
export interface ProfilePrivacySettings {
privacy_level: 'public' | 'private';
show_pronouns: boolean;
}
/**
* Combined form data for privacy tab
*/
export interface PrivacyFormData extends ProfilePrivacySettings, PrivacySettings {}
/**
* User block information
*/
export interface UserBlock {
id: string;
blocker_id: string;
blocked_id: string;
reason?: string;
created_at: string;
blocked_profile?: {
user_id: string;
username: string;
display_name?: string;
avatar_url?: string;
};
}

55
src-old/types/recharts.ts Normal file
View File

@@ -0,0 +1,55 @@
/**
* Type definitions for Recharts payloads
* Provides type-safe alternatives to `any` in chart components
*/
import type { ReactNode } from 'react';
/**
* Generic chart payload item structure
*/
export interface ChartPayloadItem<T = unknown> {
value?: number | string;
name?: string;
dataKey?: string;
color?: string;
fill?: string;
payload?: T;
[key: string]: unknown;
}
/**
* Tooltip content props from Recharts
*/
export interface TooltipProps<T = unknown> {
active?: boolean;
payload?: ChartPayloadItem<T>[];
label?: string | number;
labelFormatter?: (value: unknown, payload: ChartPayloadItem<T>[]) => ReactNode;
formatter?: (
value: number | string,
name: string,
item: ChartPayloadItem<T>,
index: number,
payload: unknown
) => ReactNode;
className?: string;
indicator?: 'line' | 'dot' | 'dashed';
hideLabel?: boolean;
hideIndicator?: boolean;
color?: string;
nameKey?: string;
labelKey?: string;
labelClassName?: string;
}
/**
* Legend content props from Recharts
*/
export interface LegendProps<T = unknown> {
payload?: ChartPayloadItem<T>[];
className?: string;
hideIcon?: boolean;
nameKey?: string;
verticalAlign?: 'top' | 'bottom';
}

View File

@@ -0,0 +1,65 @@
export interface RideCreditFilters {
// Search
searchQuery?: string;
selectedSearchItems?: string[]; // Format: "ride:id", "park:id", "manufacturer:id"
// Category (Multi-select)
categories?: string[];
// Geographic
countries?: string[];
statesProvinces?: string[];
cities?: string[];
// Park
parks?: string[];
parkTypes?: string[];
operators?: string[];
propertyOwners?: string[];
// Ride Details
manufacturers?: string[];
designers?: string[];
rideModels?: string[];
coasterTypes?: string[];
seatingTypes?: string[];
intensityLevels?: string[];
trackMaterial?: string[];
// Statistics
minRideCount?: number;
maxRideCount?: number;
minSpeed?: number;
maxSpeed?: number;
minHeight?: number;
maxHeight?: number;
minLength?: number;
maxLength?: number;
minInversions?: number;
hasInversions?: boolean;
// Dates
firstRideDateFrom?: Date;
firstRideDateTo?: Date;
lastRideDateFrom?: Date;
lastRideDateTo?: Date;
rideOpeningYearFrom?: number;
rideOpeningYearTo?: number;
// User Experience
hasNotes?: boolean;
hasPhotos?: boolean;
hasRating?: boolean;
minUserRating?: number;
// Ride Status
rideStatuses?: string[];
}
export type FilterPreset =
| 'mostRidden'
| 'recentlyAdded'
| 'singleRides'
| 'needRating'
| 'highlyRated'
| 'all';

View File

@@ -0,0 +1,27 @@
/**
* Ride Former Names Types
* Relational replacement for JSONB former_names field
*/
export interface RideFormerName {
id: string;
ride_id: string;
name: string;
used_from: string | null;
used_until: string | null;
created_at: string;
updated_at: string;
}
export interface RideFormerNameInsert {
ride_id: string;
name: string;
used_from?: string | null;
used_until?: string | null;
}
export interface RideFormerNameUpdate {
name?: string;
used_from?: string | null;
used_until?: string | null;
}

164
src-old/types/statuses.ts Normal file
View File

@@ -0,0 +1,164 @@
/**
* Type-Safe Status Enums
* Provides exhaustive type checking for all entity statuses
*/
import { z } from 'zod';
// Submission statuses
export type SubmissionStatus =
| 'draft'
| 'pending'
| 'locked'
| 'reviewing'
| 'partially_approved'
| 'approved'
| 'rejected'
| 'escalated';
export const SubmissionStatusSchema = z.enum([
'draft',
'pending',
'locked',
'reviewing',
'partially_approved',
'approved',
'rejected',
'escalated'
]);
// Review statuses for individual items
export type ReviewStatus =
| 'pending'
| 'approved'
| 'rejected'
| 'flagged'
| 'skipped';
export const ReviewStatusSchema = z.enum([
'pending',
'approved',
'rejected',
'flagged',
'skipped'
]);
// Park operational statuses
export type ParkStatus =
| 'operating'
| 'closed_permanently'
| 'closed_temporarily'
| 'under_construction'
| 'planned'
| 'abandoned';
export const ParkStatusSchema = z.enum([
'operating',
'closed_permanently',
'closed_temporarily',
'under_construction',
'planned',
'abandoned'
]);
// Ride operational statuses
export type RideStatus =
| 'operating'
| 'closed_permanently'
| 'closed_temporarily'
| 'under_construction'
| 'relocated'
| 'stored'
| 'demolished';
export const RideStatusSchema = z.enum([
'operating',
'closed_permanently',
'closed_temporarily',
'under_construction',
'relocated',
'stored',
'demolished'
]);
// Account statuses
export type AccountStatus =
| 'active'
| 'suspended'
| 'pending_deletion'
| 'deleted';
export const AccountStatusSchema = z.enum([
'active',
'suspended',
'pending_deletion',
'deleted'
]);
// Lock statuses
export type LockStatus =
| 'unlocked'
| 'locked'
| 'expired';
export const LockStatusSchema = z.enum([
'unlocked',
'locked',
'expired'
]);
// Helper type guards
export function isSubmissionStatus(value: unknown): value is SubmissionStatus {
return SubmissionStatusSchema.safeParse(value).success;
}
export function isReviewStatus(value: unknown): value is ReviewStatus {
return ReviewStatusSchema.safeParse(value).success;
}
export function isParkStatus(value: unknown): value is ParkStatus {
return ParkStatusSchema.safeParse(value).success;
}
export function isRideStatus(value: unknown): value is RideStatus {
return RideStatusSchema.safeParse(value).success;
}
// Status display helpers
export const SUBMISSION_STATUS_LABELS: Record<SubmissionStatus, string> = {
draft: 'Draft',
pending: 'Pending Review',
locked: 'Locked',
reviewing: 'Under Review',
partially_approved: 'Partially Approved',
approved: 'Approved',
rejected: 'Rejected',
escalated: 'Escalated'
};
export const REVIEW_STATUS_LABELS: Record<ReviewStatus, string> = {
pending: 'Pending',
approved: 'Approved',
rejected: 'Rejected',
flagged: 'Flagged',
skipped: 'Skipped'
};
export const PARK_STATUS_LABELS: Record<ParkStatus, string> = {
operating: 'Operating',
closed_permanently: 'Closed Permanently',
closed_temporarily: 'Closed Temporarily',
under_construction: 'Under Construction',
planned: 'Planned',
abandoned: 'Abandoned'
};
export const RIDE_STATUS_LABELS: Record<RideStatus, string> = {
operating: 'Operating',
closed_permanently: 'Closed Permanently',
closed_temporarily: 'Closed Temporarily',
under_construction: 'Under Construction',
relocated: 'Relocated',
stored: 'Stored',
demolished: 'Demolished'
};

View File

@@ -0,0 +1,166 @@
/**
* Type definitions for submission data structures
* These replace the `any` types in entityTransformers.ts
*/
import type { LocationData } from './location';
export interface ParkSubmissionData {
id?: string; // park_submission.id for location lookup
name: string;
slug: string;
description?: string | null;
park_type: string;
status: string;
opening_date?: string | null;
opening_date_precision?: string | null;
closing_date?: string | null;
closing_date_precision?: string | null;
website_url?: string | null;
phone?: string | null;
email?: string | null;
operator_id?: string | null;
property_owner_id?: string | null;
location_id?: string | null;
temp_location_data?: LocationData | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
source_url?: string;
submission_notes?: string;
is_test_data?: boolean;
}
export interface RideSubmissionData {
name: string;
slug: string;
description?: string | null;
category: string;
ride_sub_type?: string | null;
status: string;
park_id: string;
ride_model_id?: string | null;
manufacturer_id?: string | null;
designer_id?: string | null;
opening_date?: string | null;
opening_date_precision?: string | null;
closing_date?: string | null;
closing_date_precision?: string | null;
height_requirement?: number | null;
age_requirement?: number | null;
capacity_per_hour?: number | null;
duration_seconds?: number | null;
max_speed_kmh?: number | null;
max_height_meters?: number | null;
length_meters?: number | null;
drop_height_meters?: number | null;
inversions?: number | null;
max_g_force?: number | null;
coaster_type?: string | null;
seating_type?: string | null;
intensity_level?: string | null;
track_material?: string[] | null;
support_material?: string[] | null;
propulsion_method?: string[] | null;
// Water ride specific
water_depth_cm?: number | null;
splash_height_meters?: number | null;
wetness_level?: string | null;
flume_type?: string | null;
boat_capacity?: number | null;
// Dark ride specific
theme_name?: string | null;
story_description?: string | null;
show_duration_seconds?: number | null;
animatronics_count?: number | null;
projection_type?: string | null;
ride_system?: string | null;
scenes_count?: number | null;
// Flat ride specific
rotation_type?: string | null;
motion_pattern?: string | null;
platform_count?: number | null;
swing_angle_degrees?: number | null;
rotation_speed_rpm?: number | null;
arm_length_meters?: number | null;
max_height_reached_meters?: number | null;
// Kiddie ride specific
min_age?: number | null;
max_age?: number | null;
educational_theme?: string | null;
character_theme?: string | null;
// Transportation ride specific
transport_type?: string | null;
route_length_meters?: number | null;
stations_count?: number | null;
vehicle_capacity?: number | null;
vehicles_count?: number | null;
round_trip_duration_seconds?: number | null;
// Images
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
image_url?: string | null;
source_url?: string;
submission_notes?: string;
is_test_data?: boolean;
}
export interface CompanySubmissionData {
name: string;
slug: string;
company_type: 'manufacturer' | 'operator' | 'designer' | 'property_owner';
description?: string | null;
person_type?: 'company' | 'individual' | 'firm' | 'organization';
founded_year?: number | null; // Legacy field
founded_date?: string | null;
founded_date_precision?: string | null;
defunct_date?: string | null;
defunct_date_precision?: string | null;
headquarters_location?: string | null;
website_url?: string | null;
logo_url?: string | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
source_url?: string;
submission_notes?: string;
is_test_data?: boolean;
}
export interface RideModelSubmissionData {
name: string;
slug: string;
manufacturer_id: string;
category: string;
ride_type?: string | null;
description?: string | null;
banner_image_url?: string | null;
banner_image_id?: string | null;
card_image_url?: string | null;
card_image_id?: string | null;
source_url?: string;
submission_notes?: string;
is_test_data?: boolean;
}
export interface TimelineEventItemData {
entity_id: string;
entity_type: 'park' | 'ride' | 'company' | 'ride_model';
event_type: string;
event_date: string; // ISO date
event_date_precision: 'day' | 'month' | 'year';
title: string;
description?: string | null;
from_value?: string | null;
to_value?: string | null;
from_entity_id?: string | null;
to_entity_id?: string | null;
from_location_id?: string | null;
to_location_id?: string | null;
display_order?: number;
is_public?: boolean;
}

View File

@@ -0,0 +1,131 @@
/**
* Type-safe helpers for accessing submission item data
* Provides type guards and type narrowing for Json fields
*/
import type { Json } from '@/integrations/supabase/types';
/**
* Base structure that all item_data objects should have
*/
export interface BaseItemData {
name?: string;
slug?: string;
description?: string;
}
/**
* Type guard to safely check if Json is an object with a name property
*/
export function hasName(data: Json): data is { name: string } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'name' in data && typeof (data as Record<string, Json>).name === 'string';
}
/**
* Type guard to check if Json is a photos array
*/
export function hasPhotos(data: Json): data is { photos: Array<Record<string, Json>> } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'photos' in data && Array.isArray((data as Record<string, Json>).photos);
}
/**
* Type guard for manufacturer data
*/
export function hasManufacturer(data: Json): data is { manufacturer_id: string; manufacturer_name: string } & Record<string, Json> {
return (
typeof data === 'object' &&
data !== null &&
!Array.isArray(data) &&
'manufacturer_id' in data &&
'manufacturer_name' in data
);
}
/**
* Type guard for park data
*/
export function hasParkId(data: Json): data is { park_id: string } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'park_id' in data;
}
/**
* Type guard for ride data
*/
export function hasRideId(data: Json): data is { ride_id: string } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'ride_id' in data;
}
/**
* Type guard for company data
*/
export function hasCompanyId(data: Json): data is { company_id: string } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'company_id' in data;
}
/**
* Type guard for ride model data
*/
export function hasRideModelId(data: Json): data is { ride_model_id: string } & Record<string, Json> {
return typeof data === 'object' && data !== null && !Array.isArray(data) && 'ride_model_id' in data;
}
/**
* Safely get name from item_data or entity_data
* Works with both Json (legacy) and pre-loaded entity_data from view
*/
export function getItemName(data: Json | Record<string, any> | null | undefined): string {
if (!data) return 'Unnamed';
// Handle entity_data object (from view)
if (typeof data === 'object' && !Array.isArray(data) && 'name' in data) {
return String(data.name);
}
// Handle Json (legacy)
if (hasName(data)) {
return data.name;
}
return 'Unnamed';
}
/**
* Safely get photos from item_data or entity_data
*/
export function getItemPhotos(data: Json | Record<string, any> | null | undefined): Array<Record<string, Json>> {
if (!data) return [];
// Handle entity_data object (from view)
if (typeof data === 'object' && !Array.isArray(data) && 'photos' in data && Array.isArray(data.photos)) {
return data.photos as Array<Record<string, Json>>;
}
// Handle Json (legacy)
if (hasPhotos(data)) {
return data.photos;
}
return [];
}
/**
* Convert Json to a record for form usage
* Only use when you need to pass data to form components
*/
export function jsonToRecord(data: Json): Record<string, Json> {
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
return data as Record<string, Json>;
}
return {};
}
/**
* Type-safe way to access nested Json properties
*/
export function getProperty<T = Json>(data: Json, key: string): T | undefined {
if (typeof data === 'object' && data !== null && !Array.isArray(data)) {
const obj = data as Record<string, Json>;
return obj[key] as T | undefined;
}
return undefined;
}

View File

@@ -0,0 +1,153 @@
export type EntityType =
| 'park'
| 'ride'
| 'manufacturer'
| 'operator'
| 'designer'
| 'property_owner'
| 'photo_edit'
| 'photo_delete'
| 'milestone'
| 'timeline_event';
export interface PhotoSubmission {
url: string;
caption?: string;
title?: string;
date?: string;
order: number;
}
export interface PhotoSubmissionContent {
title?: string;
photos: PhotoSubmission[];
context: EntityType;
entity_id: string;
// Legacy support
park_id?: string;
ride_id?: string;
company_id?: string;
}
export interface SubmissionItemData {
id: string;
submission_id: string;
item_type: EntityType | 'photo' | 'ride_model';
action_type: 'create' | 'edit' | 'delete';
item_data: Record<string, unknown>;
original_data?: Record<string, unknown>;
status: 'pending' | 'approved' | 'rejected' | 'flagged' | 'skipped'; // Use ReviewStatus type
depends_on: string | null;
order_index: number;
approved_entity_id: string | null;
rejection_reason: string | null;
created_at: string;
updated_at: string;
}
export interface EntityPhotoGalleryProps {
entityId: string;
entityType: EntityType;
entityName: string;
parentId?: string; // e.g., parkId for a ride
}
export interface UppyPhotoSubmissionUploadProps {
onSubmissionComplete?: () => void;
entityId: string;
entityType: EntityType;
parentId?: string; // Optional parent (e.g., parkId for rides)
}
/**
* Enforces minimal content structure for content_submissions.content
* This type prevents accidental JSON blob storage
*
* RULE: content should ONLY contain action + max 2 reference IDs
*/
export interface ContentSubmissionContent {
action: 'create' | 'edit' | 'delete';
// Only reference IDs allowed - no actual data
[key: string]: string | null | undefined;
}
/**
* Type guard to validate content structure
* Prevents JSON blob violations at runtime
*/
export function isValidSubmissionContent(content: any): content is ContentSubmissionContent {
if (!content || typeof content !== 'object') {
// Security: Use logger instead of console.error to prevent PII exposure
import('@/lib/logger').then(({ logger }) => {
logger.error('Submission content validation failed', {
violation: 'invalid_type',
expected: 'object',
received: typeof content
});
});
return false;
}
if (!['create', 'edit', 'delete'].includes(content.action)) {
import('@/lib/logger').then(({ logger }) => {
logger.error('Submission content validation failed', {
violation: 'invalid_action',
expected: 'create | edit | delete',
received: content.action
});
});
return false;
}
const keys = Object.keys(content);
if (keys.length > 3) {
import('@/lib/logger').then(({ logger }) => {
logger.error('Submission content validation failed', {
violation: 'too_many_fields',
count: keys.length,
limit: 3
});
});
return false;
}
// Check for common violations
const forbiddenKeys = ['name', 'description', 'photos', 'data', 'items', 'metadata'];
const violations = keys.filter(k => forbiddenKeys.includes(k));
if (violations.length > 0) {
import('@/lib/logger').then(({ logger }) => {
logger.error('Submission content validation failed', {
violation: 'forbidden_keys',
forbiddenKeys: violations,
message: 'These should be in submission_items.item_data instead'
});
});
return false;
}
return true;
}
/**
* Helper to create safe submission content
* Use this to ensure compliance with the pattern
*/
export function createSubmissionContent(
action: 'create' | 'edit' | 'delete',
referenceIds: Record<string, string> = {}
): ContentSubmissionContent {
const idKeys = Object.keys(referenceIds);
if (idKeys.length > 2) {
throw new Error(
`Too many reference IDs (${idKeys.length}). Maximum is 2. ` +
`Put additional data in submission_items.item_data instead.`
);
}
return {
action,
...referenceIds
};
}

View File

@@ -0,0 +1,24 @@
/**
* Extended Supabase Auth Types
* Supabase auth methods accept options objects not fully typed in official SDK
*/
export interface SignInOptions {
email: string;
password: string;
options?: {
captchaToken?: string;
};
}
export interface SignUpOptions {
email: string;
password: string;
options?: {
data?: {
username?: string;
display_name?: string;
};
captchaToken?: string;
};
}

View File

@@ -0,0 +1,35 @@
/**
* Extended Supabase Session Types
*
* Supabase session objects contain additional properties not in the official types.
* These extended types provide type safety for AAL (Authentication Assurance Level) and other fields.
*/
import type { Session } from '@supabase/supabase-js';
export type AALLevel = 'aal1' | 'aal2';
/**
* Extended session type with AAL property
* Supabase auth sessions include this property but it's not in the official types
*/
export interface ExtendedSession extends Session {
aal?: AALLevel;
}
/**
* Type guard to check if session has AAL property
*/
export function hasAAL(session: Session | null): session is ExtendedSession {
return session !== null && 'aal' in session;
}
/**
* Safely extract AAL level from session
*/
export function getSessionAAL(session: Session | null): AALLevel {
if (hasAAL(session) && session.aal) {
return session.aal;
}
return 'aal1';
}

84
src-old/types/timeline.ts Normal file
View File

@@ -0,0 +1,84 @@
/**
* Timeline Event Types
*
* Type definitions for entity timeline/historical milestone system.
* All timeline events flow through moderation queue before being stored.
*/
export type TimelineEventType =
| 'name_change'
| 'operator_change'
| 'owner_change'
| 'location_change'
| 'status_change'
| 'opening'
| 'closure'
| 'reopening'
| 'renovation'
| 'expansion'
| 'acquisition'
| 'milestone'
| 'other';
export type EntityType = 'park' | 'ride' | 'company' | 'ride_model';
export type DatePrecision = 'day' | 'month' | 'year';
/**
* Timeline event stored in database after approval
*/
export interface TimelineEvent {
id: string;
entity_id: string;
entity_type: EntityType;
event_type: TimelineEventType;
event_date: string;
event_date_precision: DatePrecision;
title: string;
description?: string;
// Type-specific relational data
from_value?: string;
to_value?: string;
from_entity_id?: string;
to_entity_id?: string;
from_location_id?: string;
to_location_id?: string;
// Metadata
display_order: number;
created_by?: string;
approved_by?: string;
submission_id?: string;
created_at: string;
updated_at: string;
}
/**
* Form data for creating/editing timeline events
*/
export interface TimelineEventFormData {
event_type: TimelineEventType;
event_date: Date;
event_date_precision: DatePrecision;
title: string;
description?: string;
// Conditional fields based on event_type
from_value?: string;
to_value?: string;
from_entity_id?: string;
to_entity_id?: string;
from_location_id?: string;
to_location_id?: string;
}
/**
* Complete submission data for timeline events
* Includes entity reference and all form data
*/
export interface TimelineSubmissionData extends TimelineEventFormData {
entity_id: string;
entity_type: EntityType;
}

207
src-old/types/versioning.ts Normal file
View File

@@ -0,0 +1,207 @@
/**
* Universal Versioning System Types
* Pure relational structure, no JSONB storage
*/
export type EntityType = 'park' | 'ride' | 'company' | 'ride_model';
export type ChangeType = 'created' | 'updated' | 'deleted' | 'restored' | 'archived';
/**
* Base version metadata common to all entity types
* NOTE: Using version_id as the primary identifier (not 'id')
* This matches the relational version table structure
*/
export interface BaseVersion {
version_id: string;
version_number: number;
created_at: string;
created_by: string | null;
change_type: ChangeType;
change_reason: string | null;
submission_id: string | null;
is_current: boolean;
}
/**
* Extended version with profile data joined
*/
export interface BaseVersionWithProfile extends BaseVersion {
profiles?: {
username: string | null;
display_name: string | null;
avatar_url: string | null;
} | null;
}
/**
* Park Version - exact mirror of parks table structure
*/
export interface ParkVersion extends BaseVersionWithProfile {
park_id: string;
name: string;
slug: string;
description: string | null;
park_type: string;
status: string;
location_id: string | null;
operator_id: string | null;
property_owner_id: string | null;
opening_date: string | null;
closing_date: string | null;
opening_date_precision: string | null;
closing_date_precision: string | null;
website_url: string | null;
phone: string | null;
email: string | null;
banner_image_url: string | null;
banner_image_id: string | null;
card_image_url: string | null;
card_image_id: string | null;
}
/**
* Ride Version - exact mirror of ride_versions table structure
* FIXED: Added missing fields, removed JSONB former_names
*/
export interface RideVersion extends BaseVersionWithProfile {
ride_id: string;
name: string;
slug: string;
description: string | null;
category: string;
status: string;
park_id: string | null;
manufacturer_id: string | null;
designer_id: string | null;
ride_model_id: string | null;
opening_date: string | null;
closing_date: string | null;
opening_date_precision: string | null;
closing_date_precision: string | null;
height_requirement_cm: number | null;
age_requirement: number | null;
max_speed_kmh: number | null;
duration_seconds: number | null;
capacity_per_hour: number | null;
gforce_max: number | null;
inversions_count: number | null;
length_meters: number | null;
height_meters: number | null;
drop_meters: number | null;
banner_image_url: string | null;
banner_image_id: string | null;
card_image_url: string | null;
card_image_id: string | null;
image_url: string | null;
ride_sub_type: string | null;
coaster_type: string | null;
seating_type: string | null;
intensity_level: string | null;
track_material: string[] | null;
support_material: string[] | null;
propulsion_method: string[] | null;
// Water Ride Fields
water_depth_cm: number | null;
splash_height_meters: number | null;
wetness_level: string | null;
flume_type: string | null;
boat_capacity: number | null;
// Dark Ride Fields
theme_name: string | null;
story_description: string | null;
show_duration_seconds: number | null;
animatronics_count: number | null;
projection_type: string | null;
ride_system: string | null;
scenes_count: number | null;
// Flat Ride Fields
rotation_type: string | null;
motion_pattern: string | null;
platform_count: number | null;
swing_angle_degrees: number | null;
rotation_speed_rpm: number | null;
arm_length_meters: number | null;
max_height_reached_meters: number | null;
// Kiddie Ride Fields
min_age: number | null;
max_age: number | null;
educational_theme: string | null;
character_theme: string | null;
// Transportation Fields
transport_type: string | null;
route_length_meters: number | null;
stations_count: number | null;
vehicle_capacity: number | null;
vehicles_count: number | null;
round_trip_duration_seconds: number | null;
}
/**
* Company Version - exact mirror of companies table structure
*/
export interface CompanyVersion extends BaseVersionWithProfile {
company_id: string;
name: string;
slug: string;
description: string | null;
company_type: string;
person_type: string | null;
founded_year: number | null;
founded_date: string | null;
founded_date_precision: string | null;
headquarters_location: string | null;
website_url: string | null;
logo_url: string | null;
banner_image_url: string | null;
banner_image_id: string | null;
card_image_url: string | null;
card_image_id: string | null;
}
/**
* Ride Model Version - exact mirror of ride_models table structure
*/
export interface RideModelVersion extends BaseVersionWithProfile {
ride_model_id: string;
name: string;
slug: string;
manufacturer_id: string | null;
category: string;
ride_type: string;
description: string | null;
banner_image_url: string | null;
banner_image_id: string | null;
card_image_url: string | null;
card_image_id: string | null;
// Note: technical_specs removed - use ride_model_technical_specifications table
}
/**
* Discriminated union of all entity version types
*/
export type EntityVersion = ParkVersion | RideVersion | CompanyVersion | RideModelVersion;
/**
* Version comparison diff structure
*/
export interface VersionDiff {
[fieldName: string]: {
from: any;
to: any;
};
}
/**
* Version summary for list display
*/
export interface VersionSummary {
version_id: string;
version_number: number;
created_at: string;
created_by: string | null;
change_type: ChangeType;
changed_by_username?: string;
changed_by_display_name?: string;
entity_name: string;
}