mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 18:26:58 -05:00
Compare commits
10 Commits
bdd4e046f5
...
4ee6419865
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ee6419865 | ||
|
|
6cc08de96c | ||
|
|
00b2ea2192 | ||
|
|
c0a4a8dc9c | ||
|
|
4d571e4f12 | ||
|
|
a168007e23 | ||
|
|
bd3bffcc20 | ||
|
|
d998225315 | ||
|
|
45a5dadd29 | ||
|
|
3f95e447bb |
@@ -52,13 +52,6 @@ export function Header() {
|
||||
Explore
|
||||
</h3>
|
||||
</div>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Parks
|
||||
</Link>
|
||||
<Link
|
||||
to="/rides"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
@@ -66,6 +59,13 @@ export function Header() {
|
||||
>
|
||||
Rides
|
||||
</Link>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
Parks
|
||||
</Link>
|
||||
<Link
|
||||
to="/manufacturers"
|
||||
className="px-3 py-2.5 text-base font-medium hover:bg-accent hover:text-accent-foreground rounded-md transition-colors"
|
||||
@@ -129,20 +129,7 @@ export function Header() {
|
||||
<NavigationMenuItem>
|
||||
<NavigationMenuTrigger className="h-9">Explore</NavigationMenuTrigger>
|
||||
<NavigationMenuContent>
|
||||
<ul className="grid w-[400px] gap-3 p-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Parks</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Browse theme parks around the world
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<ul className="grid min-w-[320px] max-w-[500px] w-fit gap-3 p-4">
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
@@ -156,6 +143,19 @@ export function Header() {
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
to="/parks"
|
||||
className="block select-none space-y-1 rounded-md p-3 leading-none no-underline outline-none transition-colors hover:bg-accent/20 focus:bg-accent/20"
|
||||
>
|
||||
<div className="text-sm font-medium leading-none">Parks</div>
|
||||
<p className="line-clamp-2 text-sm leading-snug text-muted-foreground">
|
||||
Browse theme parks around the world
|
||||
</p>
|
||||
</Link>
|
||||
</NavigationMenuLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavigationMenuLink asChild>
|
||||
<Link
|
||||
|
||||
@@ -80,7 +80,7 @@ const NavigationMenuViewport = React.forwardRef<
|
||||
<div className={cn("absolute left-0 top-full flex justify-center")}>
|
||||
<NavigationMenuPrimitive.Viewport
|
||||
className={cn(
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
|
||||
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)] transition-all duration-300 ease-in-out",
|
||||
className,
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -139,12 +139,12 @@ export function useModerationActions(config: ModerationActionsConfig): Moderatio
|
||||
.select(`
|
||||
id,
|
||||
item_type,
|
||||
park_submission:park_submissions!park_submission_id(*),
|
||||
ride_submission:ride_submissions!ride_submission_id(*),
|
||||
company_submission:company_submissions!company_submission_id(*),
|
||||
ride_model_submission:ride_model_submissions!ride_model_submission_id(*),
|
||||
timeline_event_submission:timeline_event_submissions!timeline_event_submission_id(*),
|
||||
photo_submission:photo_submissions!photo_submission_id(*)
|
||||
park_submission:park_submissions!submission_items_park_submission_id_fkey(*),
|
||||
ride_submission:ride_submissions!submission_items_ride_submission_id_fkey(*),
|
||||
company_submission:company_submissions!submission_items_company_submission_id_fkey(*),
|
||||
ride_model_submission:ride_model_submissions!submission_items_ride_model_submission_id_fkey(*),
|
||||
timeline_event_submission:timeline_event_submissions!submission_items_timeline_event_submission_id_fkey(*),
|
||||
photo_submission:photo_submissions!submission_items_photo_submission_id_fkey(*)
|
||||
`)
|
||||
.eq('submission_id', item.id)
|
||||
.in('status', ['pending', 'rejected']);
|
||||
|
||||
@@ -730,14 +730,15 @@ serve(withRateLimit(async (req) => {
|
||||
.from('submission_items')
|
||||
.select(`
|
||||
*,
|
||||
park_submission:park_submissions!item_data_id(*),
|
||||
ride_submission:ride_submissions!item_data_id(*),
|
||||
company_submission:company_submissions!item_data_id(*),
|
||||
ride_model_submission:ride_model_submissions!item_data_id(*),
|
||||
photo_submission:photo_submissions!item_data_id(
|
||||
park_submission:park_submissions!park_submission_id(*),
|
||||
ride_submission:ride_submissions!ride_submission_id(*),
|
||||
company_submission:company_submissions!company_submission_id(*),
|
||||
ride_model_submission:ride_model_submissions!ride_model_submission_id(*),
|
||||
photo_submission:photo_submissions!photo_submission_id(
|
||||
*,
|
||||
photo_items:photo_submission_items(*)
|
||||
)
|
||||
),
|
||||
timeline_event_submission:timeline_event_submissions!timeline_event_submission_id(*)
|
||||
`)
|
||||
.in('id', itemIds);
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
-- Add foreign key constraints to submission_items table
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_park_submission
|
||||
FOREIGN KEY (park_submission_id)
|
||||
REFERENCES park_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_ride_submission
|
||||
FOREIGN KEY (ride_submission_id)
|
||||
REFERENCES ride_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_company_submission
|
||||
FOREIGN KEY (company_submission_id)
|
||||
REFERENCES company_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_ride_model_submission
|
||||
FOREIGN KEY (ride_model_submission_id)
|
||||
REFERENCES ride_model_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_photo_submission
|
||||
FOREIGN KEY (photo_submission_id)
|
||||
REFERENCES photo_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE submission_items
|
||||
ADD CONSTRAINT fk_submission_items_timeline_event_submission
|
||||
FOREIGN KEY (timeline_event_submission_id)
|
||||
REFERENCES timeline_event_submissions(id)
|
||||
ON DELETE CASCADE;
|
||||
|
||||
-- Add indexes for better query performance
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_park_submission_id
|
||||
ON submission_items(park_submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_ride_submission_id
|
||||
ON submission_items(ride_submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_company_submission_id
|
||||
ON submission_items(company_submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_ride_model_submission_id
|
||||
ON submission_items(ride_model_submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_photo_submission_id
|
||||
ON submission_items(photo_submission_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_submission_items_timeline_event_submission_id
|
||||
ON submission_items(timeline_event_submission_id);
|
||||
@@ -0,0 +1,44 @@
|
||||
-- Fix search_path security vulnerability in update_content_submissions_updated_at
|
||||
-- This addresses the function_search_path_mutable linter warning
|
||||
|
||||
DROP FUNCTION IF EXISTS public.update_content_submissions_updated_at() CASCADE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_content_submissions_updated_at()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = 'public'
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Only update updated_at if actual content has changed
|
||||
-- Ignore changes to: updated_at, assigned_to, assigned_at, locked_until, priority, review_count, first_reviewed_at, resolved_at, submitted_at
|
||||
IF (
|
||||
NEW.status IS DISTINCT FROM OLD.status OR
|
||||
NEW.reviewer_id IS DISTINCT FROM OLD.reviewer_id OR
|
||||
NEW.reviewer_notes IS DISTINCT FROM OLD.reviewer_notes OR
|
||||
NEW.escalated IS DISTINCT FROM OLD.escalated OR
|
||||
NEW.escalation_reason IS DISTINCT FROM OLD.escalation_reason OR
|
||||
NEW.approval_mode IS DISTINCT FROM OLD.approval_mode OR
|
||||
NEW.user_id IS DISTINCT FROM OLD.user_id OR
|
||||
NEW.submission_type IS DISTINCT FROM OLD.submission_type OR
|
||||
NEW.escalated_by IS DISTINCT FROM OLD.escalated_by OR
|
||||
NEW.escalated_at IS DISTINCT FROM OLD.escalated_at OR
|
||||
NEW.original_submission_id IS DISTINCT FROM OLD.original_submission_id
|
||||
) THEN
|
||||
NEW.updated_at = NOW();
|
||||
ELSE
|
||||
-- Keep the old updated_at timestamp if only metadata changed
|
||||
NEW.updated_at = OLD.updated_at;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Recreate trigger for content_submissions
|
||||
CREATE TRIGGER update_content_submissions_updated_at
|
||||
BEFORE UPDATE ON public.content_submissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_content_submissions_updated_at();
|
||||
|
||||
COMMENT ON FUNCTION public.update_content_submissions_updated_at() IS
|
||||
'SECURITY HARDENED: Trigger function to update updated_at timestamp only when meaningful fields change. Includes SET search_path to prevent search path injection attacks.';
|
||||
@@ -0,0 +1,44 @@
|
||||
-- Fix search_path security vulnerability in update_content_submissions_updated_at
|
||||
-- This addresses the function_search_path_mutable linter warning
|
||||
|
||||
DROP FUNCTION IF EXISTS public.update_content_submissions_updated_at() CASCADE;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.update_content_submissions_updated_at()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
SET search_path = 'public'
|
||||
AS $$
|
||||
BEGIN
|
||||
-- Only update updated_at if actual content has changed
|
||||
-- Ignore changes to: updated_at, assigned_to, assigned_at, locked_until, priority, review_count, first_reviewed_at, resolved_at, submitted_at
|
||||
IF (
|
||||
NEW.status IS DISTINCT FROM OLD.status OR
|
||||
NEW.reviewer_id IS DISTINCT FROM OLD.reviewer_id OR
|
||||
NEW.reviewer_notes IS DISTINCT FROM OLD.reviewer_notes OR
|
||||
NEW.escalated IS DISTINCT FROM OLD.escalated OR
|
||||
NEW.escalation_reason IS DISTINCT FROM OLD.escalation_reason OR
|
||||
NEW.approval_mode IS DISTINCT FROM OLD.approval_mode OR
|
||||
NEW.user_id IS DISTINCT FROM OLD.user_id OR
|
||||
NEW.submission_type IS DISTINCT FROM OLD.submission_type OR
|
||||
NEW.escalated_by IS DISTINCT FROM OLD.escalated_by OR
|
||||
NEW.escalated_at IS DISTINCT FROM OLD.escalated_at OR
|
||||
NEW.original_submission_id IS DISTINCT FROM OLD.original_submission_id
|
||||
) THEN
|
||||
NEW.updated_at = NOW();
|
||||
ELSE
|
||||
-- Keep the old updated_at timestamp if only metadata changed
|
||||
NEW.updated_at = OLD.updated_at;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- Recreate trigger for content_submissions
|
||||
CREATE TRIGGER update_content_submissions_updated_at
|
||||
BEFORE UPDATE ON public.content_submissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.update_content_submissions_updated_at();
|
||||
|
||||
COMMENT ON FUNCTION public.update_content_submissions_updated_at() IS
|
||||
'SECURITY HARDENED: Trigger function to update updated_at timestamp only when meaningful fields change. Includes SET search_path to prevent search path injection attacks.';
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Remove duplicate foreign key constraints added in migration 20251105193953
|
||||
-- Keep the original _fkey constraints that were already working
|
||||
|
||||
-- Drop the duplicate park_submission constraint (keep submission_items_park_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_park_submission;
|
||||
|
||||
-- Drop the duplicate ride_submission constraint (keep submission_items_ride_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_ride_submission;
|
||||
|
||||
-- Drop the duplicate company_submission constraint (keep submission_items_company_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_company_submission;
|
||||
|
||||
-- Drop the duplicate ride_model_submission constraint (keep submission_items_ride_model_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_ride_model_submission;
|
||||
|
||||
-- Drop the duplicate photo_submission constraint (keep submission_items_photo_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_photo_submission;
|
||||
|
||||
-- Drop the duplicate timeline_event_submission constraint (keep submission_items_timeline_event_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_timeline_event_submission;
|
||||
|
||||
COMMENT ON TABLE submission_items IS
|
||||
'Submission items with single foreign key constraints to various submission types. Original _fkey constraints maintained.';
|
||||
@@ -0,0 +1,29 @@
|
||||
-- Remove duplicate foreign key constraints added in migration 20251105193953
|
||||
-- Keep the original _fkey constraints that were already working
|
||||
|
||||
-- Drop the duplicate park_submission constraint (keep submission_items_park_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_park_submission;
|
||||
|
||||
-- Drop the duplicate ride_submission constraint (keep submission_items_ride_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_ride_submission;
|
||||
|
||||
-- Drop the duplicate company_submission constraint (keep submission_items_company_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_company_submission;
|
||||
|
||||
-- Drop the duplicate ride_model_submission constraint (keep submission_items_ride_model_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_ride_model_submission;
|
||||
|
||||
-- Drop the duplicate photo_submission constraint (keep submission_items_photo_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_photo_submission;
|
||||
|
||||
-- Drop the duplicate timeline_event_submission constraint (keep submission_items_timeline_event_submission_id_fkey)
|
||||
ALTER TABLE submission_items
|
||||
DROP CONSTRAINT IF EXISTS fk_submission_items_timeline_event_submission;
|
||||
|
||||
COMMENT ON TABLE submission_items IS
|
||||
'Submission items with single foreign key constraints to various submission types. Original _fkey constraints maintained.';
|
||||
@@ -0,0 +1,268 @@
|
||||
-- Comprehensive fix: Add SET search_path to all remaining SECURITY DEFINER functions
|
||||
-- This prevents search_path injection attacks
|
||||
|
||||
-- ============================================================================
|
||||
-- RATING SYSTEM FUNCTIONS (from migration 20250920125706)
|
||||
-- ============================================================================
|
||||
|
||||
-- 1. Fix update_park_ratings
|
||||
CREATE OR REPLACE FUNCTION public.update_park_ratings(target_park_id UUID)
|
||||
RETURNS void AS $$
|
||||
DECLARE
|
||||
avg_rating DECIMAL(3,2);
|
||||
review_cnt INTEGER;
|
||||
BEGIN
|
||||
SELECT
|
||||
COALESCE(AVG(rating), 0)::DECIMAL(3,2),
|
||||
COUNT(*)
|
||||
INTO avg_rating, review_cnt
|
||||
FROM public.reviews
|
||||
WHERE park_id = target_park_id AND moderation_status = 'approved';
|
||||
|
||||
UPDATE public.parks
|
||||
SET
|
||||
average_rating = avg_rating,
|
||||
review_count = review_cnt,
|
||||
updated_at = now()
|
||||
WHERE id = target_park_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- 2. Fix update_ride_ratings
|
||||
CREATE OR REPLACE FUNCTION public.update_ride_ratings(target_ride_id UUID)
|
||||
RETURNS void AS $$
|
||||
DECLARE
|
||||
avg_rating DECIMAL(3,2);
|
||||
review_cnt INTEGER;
|
||||
BEGIN
|
||||
SELECT
|
||||
COALESCE(AVG(rating), 0)::DECIMAL(3,2),
|
||||
COUNT(*)
|
||||
INTO avg_rating, review_cnt
|
||||
FROM public.reviews
|
||||
WHERE ride_id = target_ride_id AND moderation_status = 'approved';
|
||||
|
||||
UPDATE public.rides
|
||||
SET
|
||||
average_rating = avg_rating,
|
||||
review_count = review_cnt,
|
||||
updated_at = now()
|
||||
WHERE id = target_ride_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- 3. Fix update_company_ratings
|
||||
CREATE OR REPLACE FUNCTION public.update_company_ratings(target_company_id UUID)
|
||||
RETURNS void AS $$
|
||||
DECLARE
|
||||
avg_rating DECIMAL(3,2);
|
||||
review_cnt INTEGER;
|
||||
BEGIN
|
||||
-- Calculate ratings from parks operated by this company
|
||||
WITH park_reviews AS (
|
||||
SELECT r.rating
|
||||
FROM public.reviews r
|
||||
JOIN public.parks p ON r.park_id = p.id
|
||||
WHERE (p.operator_id = target_company_id OR p.property_owner_id = target_company_id)
|
||||
AND r.moderation_status = 'approved'
|
||||
),
|
||||
-- Calculate ratings from rides manufactured/designed by this company
|
||||
ride_reviews AS (
|
||||
SELECT r.rating
|
||||
FROM public.reviews r
|
||||
JOIN public.rides rd ON r.ride_id = rd.id
|
||||
WHERE (rd.manufacturer_id = target_company_id OR rd.designer_id = target_company_id)
|
||||
AND r.moderation_status = 'approved'
|
||||
),
|
||||
-- Combine all reviews
|
||||
all_reviews AS (
|
||||
SELECT rating FROM park_reviews
|
||||
UNION ALL
|
||||
SELECT rating FROM ride_reviews
|
||||
)
|
||||
SELECT
|
||||
COALESCE(AVG(rating), 0)::DECIMAL(3,2),
|
||||
COUNT(*)
|
||||
INTO avg_rating, review_cnt
|
||||
FROM all_reviews;
|
||||
|
||||
UPDATE public.companies
|
||||
SET
|
||||
average_rating = avg_rating,
|
||||
review_count = review_cnt,
|
||||
updated_at = now()
|
||||
WHERE id = target_company_id;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- 4. Fix update_all_ratings_for_review (trigger function)
|
||||
CREATE OR REPLACE FUNCTION public.update_all_ratings_for_review()
|
||||
RETURNS trigger AS $$
|
||||
DECLARE
|
||||
company_ids UUID[];
|
||||
BEGIN
|
||||
-- Handle both INSERT/UPDATE and DELETE cases
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
-- Update park rating if this was a park review
|
||||
IF OLD.park_id IS NOT NULL THEN
|
||||
PERFORM public.update_park_ratings(OLD.park_id);
|
||||
|
||||
-- Update related company ratings
|
||||
SELECT ARRAY[p.operator_id, p.property_owner_id]
|
||||
INTO company_ids
|
||||
FROM public.parks p
|
||||
WHERE p.id = OLD.park_id;
|
||||
|
||||
-- Update company ratings for related companies
|
||||
IF company_ids IS NOT NULL THEN
|
||||
FOR i IN 1..array_length(company_ids, 1) LOOP
|
||||
IF company_ids[i] IS NOT NULL THEN
|
||||
PERFORM public.update_company_ratings(company_ids[i]);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Update ride rating if this was a ride review
|
||||
IF OLD.ride_id IS NOT NULL THEN
|
||||
PERFORM public.update_ride_ratings(OLD.ride_id);
|
||||
|
||||
-- Update related company ratings
|
||||
SELECT ARRAY[r.manufacturer_id, r.designer_id]
|
||||
INTO company_ids
|
||||
FROM public.rides r
|
||||
WHERE r.id = OLD.ride_id;
|
||||
|
||||
-- Update company ratings for related companies
|
||||
IF company_ids IS NOT NULL THEN
|
||||
FOR i IN 1..array_length(company_ids, 1) LOOP
|
||||
IF company_ids[i] IS NOT NULL THEN
|
||||
PERFORM public.update_company_ratings(company_ids[i]);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN OLD;
|
||||
ELSE
|
||||
-- Handle INSERT/UPDATE
|
||||
-- Update park rating if this is a park review
|
||||
IF NEW.park_id IS NOT NULL THEN
|
||||
PERFORM public.update_park_ratings(NEW.park_id);
|
||||
|
||||
-- Update related company ratings
|
||||
SELECT ARRAY[p.operator_id, p.property_owner_id]
|
||||
INTO company_ids
|
||||
FROM public.parks p
|
||||
WHERE p.id = NEW.park_id;
|
||||
|
||||
-- Update company ratings for related companies
|
||||
IF company_ids IS NOT NULL THEN
|
||||
FOR i IN 1..array_length(company_ids, 1) LOOP
|
||||
IF company_ids[i] IS NOT NULL THEN
|
||||
PERFORM public.update_company_ratings(company_ids[i]);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
-- Update ride rating if this is a ride review
|
||||
IF NEW.ride_id IS NOT NULL THEN
|
||||
PERFORM public.update_ride_ratings(NEW.ride_id);
|
||||
|
||||
-- Update related company ratings
|
||||
SELECT ARRAY[r.manufacturer_id, r.designer_id]
|
||||
INTO company_ids
|
||||
FROM public.rides r
|
||||
WHERE r.id = NEW.ride_id;
|
||||
|
||||
-- Update company ratings for related companies
|
||||
IF company_ids IS NOT NULL THEN
|
||||
FOR i IN 1..array_length(company_ids, 1) LOOP
|
||||
IF company_ids[i] IS NOT NULL THEN
|
||||
PERFORM public.update_company_ratings(company_ids[i]);
|
||||
END IF;
|
||||
END LOOP;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- ============================================================================
|
||||
-- TICKET SYSTEM FUNCTIONS (from migration 20251028183015)
|
||||
-- ============================================================================
|
||||
|
||||
-- 5. Fix generate_ticket_number
|
||||
CREATE OR REPLACE FUNCTION public.generate_ticket_number()
|
||||
RETURNS TEXT AS $$
|
||||
BEGIN
|
||||
RETURN 'TW-' || LPAD(nextval('contact_ticket_number_seq')::TEXT, 6, '0');
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- 6. Fix set_ticket_number (trigger function)
|
||||
CREATE OR REPLACE FUNCTION public.set_ticket_number()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.ticket_number IS NULL THEN
|
||||
NEW.ticket_number := generate_ticket_number();
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- ============================================================================
|
||||
-- MODERATION QUEUE FUNCTION (from migration 20251103162832)
|
||||
-- ============================================================================
|
||||
|
||||
-- 7. Fix get_submission_item_entity_data
|
||||
CREATE OR REPLACE FUNCTION public.get_submission_item_entity_data(
|
||||
p_item_type text,
|
||||
p_item_data_id uuid
|
||||
) RETURNS jsonb AS $$
|
||||
DECLARE
|
||||
v_result jsonb;
|
||||
BEGIN
|
||||
CASE p_item_type
|
||||
WHEN 'park' THEN
|
||||
SELECT to_jsonb(ps.*) INTO v_result
|
||||
FROM park_submissions ps
|
||||
WHERE ps.id = p_item_data_id;
|
||||
WHEN 'ride' THEN
|
||||
SELECT to_jsonb(rs.*) INTO v_result
|
||||
FROM ride_submissions rs
|
||||
WHERE rs.id = p_item_data_id;
|
||||
WHEN 'manufacturer', 'operator', 'designer', 'property_owner' THEN
|
||||
SELECT to_jsonb(cs.*) INTO v_result
|
||||
FROM company_submissions cs
|
||||
WHERE cs.id = p_item_data_id;
|
||||
WHEN 'ride_model' THEN
|
||||
SELECT to_jsonb(rms.*) INTO v_result
|
||||
FROM ride_model_submissions rms
|
||||
WHERE rms.id = p_item_data_id;
|
||||
WHEN 'photo' THEN
|
||||
SELECT to_jsonb(ps.*) INTO v_result
|
||||
FROM photo_submissions ps
|
||||
WHERE ps.id = p_item_data_id;
|
||||
ELSE
|
||||
v_result := NULL;
|
||||
END CASE;
|
||||
|
||||
RETURN v_result;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql STABLE SECURITY DEFINER SET search_path = 'public';
|
||||
|
||||
-- ============================================================================
|
||||
-- VERIFICATION COMMENTS
|
||||
-- ============================================================================
|
||||
|
||||
COMMENT ON FUNCTION public.update_park_ratings IS 'Recalculates park ratings from approved reviews. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.update_ride_ratings IS 'Recalculates ride ratings from approved reviews. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.update_company_ratings IS 'Recalculates company ratings from associated parks/rides. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.update_all_ratings_for_review IS 'Trigger to update all entity ratings when a review changes. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.generate_ticket_number IS 'Generates unique ticket numbers for contact submissions. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.set_ticket_number IS 'Trigger to auto-assign ticket numbers. Protected with SET search_path.';
|
||||
COMMENT ON FUNCTION public.get_submission_item_entity_data IS 'Retrieves entity data for moderation queue items. Protected with SET search_path.';
|
||||
Reference in New Issue
Block a user