mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-28 10:06:58 -05:00
Compare commits
4 Commits
71b174fe16
...
1180ae2b3b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1180ae2b3b | ||
|
|
949b502ec0 | ||
|
|
26e5ca6dbe | ||
|
|
dbe5ec2a07 |
@@ -162,7 +162,7 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
|
|||||||
const { data, error } = await supabase.rpc('extend_submission_lock', {
|
const { data, error } = await supabase.rpc('extend_submission_lock', {
|
||||||
submission_id: submissionId,
|
submission_id: submissionId,
|
||||||
moderator_id: user.id,
|
moderator_id: user.id,
|
||||||
extension_duration: '15 minutes',
|
extension_duration: 'PT15M', // ISO 8601 format: 15 minutes
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
if (error) throw error;
|
||||||
@@ -325,13 +325,34 @@ export const useModerationQueue = (config?: UseModerationQueueConfig) => {
|
|||||||
|
|
||||||
const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
|
const expiresAt = new Date(Date.now() + 15 * 60 * 1000);
|
||||||
|
|
||||||
const { data, error } = await supabase.rpc('claim_specific_submission', {
|
// Use direct fetch to force read-write transaction
|
||||||
p_submission_id: submissionId,
|
const supabaseUrl = 'https://api.thrillwiki.com';
|
||||||
p_moderator_id: user.id,
|
const supabaseKey = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InlkdnRtbnJzenlicW5iY3FiZGN5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTgzMjYzNTYsImV4cCI6MjA3MzkwMjM1Nn0.DM3oyapd_omP5ZzIlrT0H9qBsiQBxBRgw2tYuqgXKX4';
|
||||||
p_lock_duration: '15 minutes',
|
|
||||||
|
const { data: sessionData } = await supabase.auth.getSession();
|
||||||
|
const token = sessionData.session?.access_token;
|
||||||
|
|
||||||
|
const response = await fetch(`${supabaseUrl}/rest/v1/rpc/claim_specific_submission`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'apikey': supabaseKey,
|
||||||
|
'Authorization': `Bearer ${token}`,
|
||||||
|
'Prefer': 'tx=commit', // Force read-write transaction
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
p_submission_id: submissionId,
|
||||||
|
p_moderator_id: user.id,
|
||||||
|
p_lock_duration: 'PT15M', // ISO 8601 format: 15 minutes
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) throw error;
|
if (!response.ok) {
|
||||||
|
const errorData = await response.json().catch(() => ({ message: 'Failed to claim submission' }));
|
||||||
|
throw new Error(errorData.message || 'Failed to claim submission');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
throw new Error('Submission is already claimed or no longer available');
|
throw new Error('Submission is already claimed or no longer available');
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
-- Update claim_specific_submission function to bypass RLS
|
||||||
|
CREATE OR REPLACE FUNCTION public.claim_specific_submission(
|
||||||
|
p_submission_id UUID,
|
||||||
|
p_moderator_id UUID,
|
||||||
|
p_lock_duration INTERVAL DEFAULT '15 minutes'
|
||||||
|
) RETURNS BOOLEAN
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path = public
|
||||||
|
AS $$
|
||||||
|
DECLARE
|
||||||
|
rows_updated INTEGER;
|
||||||
|
BEGIN
|
||||||
|
-- Temporarily disable RLS for this transaction
|
||||||
|
SET LOCAL row_security = off;
|
||||||
|
|
||||||
|
-- Atomically update the submission if it's unclaimed or lock expired
|
||||||
|
UPDATE content_submissions
|
||||||
|
SET
|
||||||
|
assigned_to = p_moderator_id,
|
||||||
|
assigned_at = NOW(),
|
||||||
|
locked_until = NOW() + p_lock_duration,
|
||||||
|
first_reviewed_at = COALESCE(first_reviewed_at, NOW())
|
||||||
|
WHERE id = p_submission_id
|
||||||
|
AND (
|
||||||
|
assigned_to IS NULL
|
||||||
|
OR locked_until < NOW()
|
||||||
|
)
|
||||||
|
AND status = 'pending';
|
||||||
|
|
||||||
|
GET DIAGNOSTICS rows_updated = ROW_COUNT;
|
||||||
|
|
||||||
|
-- Re-enable RLS (will auto-reset at end of transaction anyway)
|
||||||
|
SET LOCAL row_security = on;
|
||||||
|
|
||||||
|
-- Log the action if successful
|
||||||
|
IF rows_updated > 0 THEN
|
||||||
|
BEGIN
|
||||||
|
PERFORM log_admin_action(
|
||||||
|
p_moderator_id,
|
||||||
|
(SELECT user_id FROM content_submissions WHERE id = p_submission_id),
|
||||||
|
'submission_claimed',
|
||||||
|
jsonb_build_object(
|
||||||
|
'submission_id', p_submission_id,
|
||||||
|
'claim_type', 'specific'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
EXCEPTION WHEN OTHERS THEN
|
||||||
|
RAISE WARNING 'Failed to log submission claim audit: %', SQLERRM;
|
||||||
|
END;
|
||||||
|
|
||||||
|
RETURN TRUE;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
RETURN FALSE;
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
-- Fix notify_moderators_on_new_submission trigger to work with relational data model
|
||||||
|
-- Removes references to non-existent NEW.content column
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION public.notify_moderators_on_new_submission()
|
||||||
|
RETURNS trigger
|
||||||
|
LANGUAGE plpgsql
|
||||||
|
SECURITY DEFINER
|
||||||
|
SET search_path TO 'public'
|
||||||
|
AS $function$
|
||||||
|
DECLARE
|
||||||
|
submitter_profile record;
|
||||||
|
base_url text;
|
||||||
|
edge_function_url text;
|
||||||
|
content_preview text;
|
||||||
|
has_photos boolean := false;
|
||||||
|
item_count integer := 0;
|
||||||
|
BEGIN
|
||||||
|
-- Get submitter's username or display name
|
||||||
|
SELECT username, display_name INTO submitter_profile
|
||||||
|
FROM public.profiles
|
||||||
|
WHERE user_id = NEW.user_id;
|
||||||
|
|
||||||
|
-- Build simple content preview based on submission type
|
||||||
|
content_preview := CASE NEW.submission_type
|
||||||
|
WHEN 'park' THEN 'New park submission'
|
||||||
|
WHEN 'ride' THEN 'New ride submission'
|
||||||
|
WHEN 'company' THEN 'New company submission'
|
||||||
|
WHEN 'ride_model' THEN 'New ride model submission'
|
||||||
|
WHEN 'photo' THEN 'New photo submission'
|
||||||
|
ELSE 'New submission'
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Check if this submission has photos
|
||||||
|
has_photos := EXISTS (
|
||||||
|
SELECT 1 FROM photo_submissions ps
|
||||||
|
WHERE ps.submission_id = NEW.id
|
||||||
|
);
|
||||||
|
|
||||||
|
-- Count submission items
|
||||||
|
SELECT COALESCE(COUNT(*), 0)::integer INTO item_count
|
||||||
|
FROM submission_items
|
||||||
|
WHERE submission_id = NEW.id;
|
||||||
|
|
||||||
|
-- Get base URL from settings
|
||||||
|
SELECT setting_value::text INTO base_url
|
||||||
|
FROM admin_settings
|
||||||
|
WHERE setting_key = 'supabase_api_url';
|
||||||
|
|
||||||
|
base_url := trim(both '"' from base_url);
|
||||||
|
|
||||||
|
IF base_url IS NULL THEN
|
||||||
|
base_url := 'https://api.thrillwiki.com';
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
edge_function_url := base_url || '/functions/v1/notify-moderators-submission';
|
||||||
|
|
||||||
|
-- Call the edge function via pg_net
|
||||||
|
PERFORM public.pg_net.http_post(
|
||||||
|
edge_function_url,
|
||||||
|
jsonb_build_object(
|
||||||
|
'submission_id', NEW.id,
|
||||||
|
'submission_type', NEW.submission_type,
|
||||||
|
'submitter_name', COALESCE(submitter_profile.display_name, submitter_profile.username, 'Unknown'),
|
||||||
|
'action', 'create',
|
||||||
|
'content_preview', content_preview,
|
||||||
|
'submitted_at', NEW.created_at,
|
||||||
|
'has_photos', has_photos,
|
||||||
|
'item_count', item_count,
|
||||||
|
'is_escalated', false
|
||||||
|
)::text,
|
||||||
|
'application/json'
|
||||||
|
);
|
||||||
|
|
||||||
|
RETURN NEW;
|
||||||
|
EXCEPTION
|
||||||
|
WHEN OTHERS THEN
|
||||||
|
-- Log error but don't fail the submission
|
||||||
|
RAISE NOTICE 'Failed to notify moderators: %', SQLERRM;
|
||||||
|
RETURN NEW;
|
||||||
|
END;
|
||||||
|
$function$;
|
||||||
Reference in New Issue
Block a user