mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
- Couple streaks with milestone notifications - Scheduled pack releases with admin UI - Notification preferences screen - Refactored profile settings screens - Partner activity notifications
127 lines
4.3 KiB
PL/PgSQL
127 lines
4.3 KiB
PL/PgSQL
-- App Configuration table for mobile app settings
|
|
-- Stores app behavior settings configurable by super admins
|
|
|
|
CREATE TABLE IF NOT EXISTS app_config (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
|
|
-- Answer Spam Prevention
|
|
-- Maximum questions a user can answer ahead of their partner (0 = disabled)
|
|
answer_gap_threshold integer DEFAULT 10,
|
|
|
|
-- Metadata
|
|
updated_at timestamptz DEFAULT now(),
|
|
updated_by uuid REFERENCES auth.users(id)
|
|
);
|
|
-- Only allow one row in this table (singleton pattern)
|
|
CREATE UNIQUE INDEX IF NOT EXISTS app_config_singleton ON app_config ((true));
|
|
-- Insert default config row
|
|
INSERT INTO app_config (id) VALUES (gen_random_uuid())
|
|
ON CONFLICT DO NOTHING;
|
|
-- Enable RLS
|
|
ALTER TABLE app_config ENABLE ROW LEVEL SECURITY;
|
|
-- All authenticated users can read (needed for mobile app)
|
|
CREATE POLICY "Authenticated users can read app_config"
|
|
ON app_config FOR SELECT
|
|
USING (auth.role() = 'authenticated');
|
|
-- Only super admins can update
|
|
CREATE POLICY "Super admins can update app_config"
|
|
ON app_config FOR UPDATE
|
|
USING (is_super_admin());
|
|
-- Create trigger to auto-update timestamp (reuses existing function)
|
|
CREATE TRIGGER app_config_updated_at
|
|
BEFORE UPDATE ON app_config
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_updated_at();
|
|
-- Add comment for documentation
|
|
COMMENT ON TABLE app_config IS 'Singleton table storing mobile app configuration. Readable by all authenticated users, writable by super admins only.';
|
|
COMMENT ON COLUMN app_config.answer_gap_threshold IS 'Maximum number of questions a user can answer ahead of their partner. Set to 0 to disable answer spam prevention.';
|
|
-- ============================================================================
|
|
-- RPC FUNCTION: get_answer_gap_status
|
|
-- Returns the number of questions user has answered that partner hasn't,
|
|
-- the configured threshold, and whether the user is blocked.
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION get_answer_gap_status()
|
|
RETURNS TABLE(
|
|
unanswered_by_partner integer,
|
|
threshold integer,
|
|
is_blocked boolean
|
|
)
|
|
LANGUAGE plpgsql
|
|
SECURITY DEFINER
|
|
SET search_path = 'public'
|
|
AS $$
|
|
DECLARE
|
|
v_user_id UUID;
|
|
v_couple_id UUID;
|
|
v_partner_id UUID;
|
|
v_gap_count integer;
|
|
v_threshold integer;
|
|
BEGIN
|
|
v_user_id := auth.uid();
|
|
|
|
-- Get user's couple_id
|
|
SELECT p.couple_id INTO v_couple_id
|
|
FROM profiles p
|
|
WHERE p.id = v_user_id;
|
|
|
|
-- No couple = no blocking needed
|
|
IF v_couple_id IS NULL THEN
|
|
RETURN QUERY SELECT 0, 0, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Get partner's ID
|
|
SELECT p.id INTO v_partner_id
|
|
FROM profiles p
|
|
WHERE p.couple_id = v_couple_id AND p.id != v_user_id
|
|
LIMIT 1;
|
|
|
|
-- No partner = no blocking needed
|
|
IF v_partner_id IS NULL THEN
|
|
RETURN QUERY SELECT 0, 0, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Get threshold from app_config
|
|
SELECT COALESCE(ac.answer_gap_threshold, 10) INTO v_threshold
|
|
FROM app_config ac
|
|
LIMIT 1;
|
|
|
|
-- If threshold is 0, feature is disabled
|
|
IF v_threshold = 0 THEN
|
|
RETURN QUERY SELECT 0, 0, false;
|
|
RETURN;
|
|
END IF;
|
|
|
|
-- Count questions in ENABLED packs where:
|
|
-- - Current user HAS responded
|
|
-- - Partner has NOT responded
|
|
WITH enabled_packs AS (
|
|
-- Get packs that the couple has explicitly enabled
|
|
SELECT cp.pack_id
|
|
FROM couple_packs cp
|
|
WHERE cp.couple_id = v_couple_id AND cp.enabled = true
|
|
)
|
|
SELECT COUNT(*)::integer INTO v_gap_count
|
|
FROM responses r
|
|
INNER JOIN questions q ON q.id = r.question_id
|
|
WHERE r.user_id = v_user_id
|
|
AND r.couple_id = v_couple_id
|
|
AND (
|
|
-- Only count if couple has enabled packs AND question is in one of them
|
|
-- If no packs enabled, don't block (couple hasn't set up yet)
|
|
EXISTS (SELECT 1 FROM enabled_packs)
|
|
AND q.pack_id IN (SELECT pack_id FROM enabled_packs)
|
|
)
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM responses pr
|
|
WHERE pr.question_id = r.question_id
|
|
AND pr.user_id = v_partner_id
|
|
);
|
|
|
|
RETURN QUERY SELECT v_gap_count, v_threshold, (v_gap_count >= v_threshold);
|
|
END;
|
|
$$;
|
|
COMMENT ON FUNCTION get_answer_gap_status() IS 'Returns answer gap status for the current user. Used by mobile app to prevent answer spam.';
|