Database Schema¶
!!! info "TL;DR" PostgreSQL 15 with RLS enabled on all tables. Multi-tenant architecture with organization_id columns. Custom auth using HMAC-signed sessions (no Supabase Auth). Denormalized counts via triggers. Org-scoped RLS policies using current_organization_id(), is_org_member(), is_org_admin() functions.
Authentication Model¶
This app uses custom authentication with HMAC-signed session cookies, NOT Supabase Auth. RLS policies use:
current_user_id()— Returns UUID fromx-user-idheader set by Next.js middlewarecurrent_organization_id()— Returns UUID fromx-organization-idheaderis_org_member()— Checks if current user is inorganization_memberstableis_org_admin()— Checks if current user hasadminorownerrole
These functions read request headers passed by PostgREST. All references to auth.users(id), auth.uid(), and auth.role() are incorrect — the actual schema uses the custom functions above.
Multi-Tenancy¶
Nearly every table has an organization_id column. RLS policies enforce org-scoped access:
-- Public read, scoped to current org
CREATE POLICY "submissions_org_read" ON submissions
FOR SELECT USING (organization_id = current_organization_id());
-- Insert: user must be org member
CREATE POLICY "submissions_org_insert" ON submissions
FOR INSERT WITH CHECK (
organization_id = current_organization_id()
AND current_user_id() = author_id
AND is_org_member()
);
-- Admin full access within org
CREATE POLICY "submissions_org_admin_all" ON submissions
FOR ALL USING (
organization_id = current_organization_id()
AND is_org_admin()
);
Identity & Auth Tables¶
users¶
Custom user table for HMAC-based auth. Not Supabase Auth.
idUUID PRIMARY KEYemailTEXT UNIQUE NOT NULLdisplay_nameTEXTcreated_atTIMESTAMPTZ DEFAULT NOW()updated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Users can read/update their own profile
- Org admins can read user profiles
Indexes:
idx_users_emailonemailidx_users_display_nameondisplay_name
auth_magic_links¶
Magic link tokens for passwordless login.
idUUID PRIMARY KEYtokenTEXT UNIQUE NOT NULLemailTEXT NOT NULLusedBOOLEAN DEFAULT FALSEexpires_atTIMESTAMPTZ NOT NULLcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies: None (service role only)
Indexes:
idx_magic_links_tokenontokenidx_magic_links_emailonemailidx_magic_links_expiresonexpires_atWHEREused = FALSE
organizations¶
Multi-tenant organizations with plan limits and integrations.
idUUID PRIMARY KEYslugTEXT UNIQUE NOT NULL — Subdomain identifier (e.g.,acme.canviq.app)nameTEXT NOT NULLplanTEXT DEFAULT 'free'plan_limitsJSONB DEFAULT{"submissions_per_month": 100, "surveys": 1, "responses_per_month": 500, "integrations": [], "mcp_agents": 0}stripe_customer_idTEXTstripe_subscription_idTEXTstripe_subscription_statusTEXTtrial_ends_atTIMESTAMPTZgithub_access_tokenTEXT — Encrypted GitHub personal access tokengithub_repoTEXTjira_credentialsJSONB — Encrypted Jira OAuth credentialsjira_project_keyTEXTbrandingJSONB DEFAULT{"primary_color": "#9333ea", "logo_url": null, "show_powered_by": true, "favicon_url": null}created_atTIMESTAMPTZ DEFAULT NOW()updated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Public read access (needed for subdomain routing)
- Only org admins can update their org settings
Indexes:
UNIQUEconstraint onslug(implicit index)idx_organizations_stripe_customeronstripe_customer_ididx_organizations_brandingGIN index onbranding
organization_members¶
Organization membership and roles (replaces team_members for org-specific access).
idUUID PRIMARY KEYorganization_idUUID REFERENCESorganizations(id)ON DELETE CASCADEuser_idUUID REFERENCESusers(id)ON DELETE CASCADEroleTEXT DEFAULT 'member' — 'owner', 'admin', or 'member'can_manage_billingBOOLEAN DEFAULT falsecan_manage_integrationsBOOLEAN DEFAULT falsecan_invite_membersBOOLEAN DEFAULT falseinvited_byUUID REFERENCESusers(id)joined_atTIMESTAMPTZ DEFAULT NOW()- UNIQUE(
organization_id,user_id)
RLS Policies:
- Members can read their own org's member list
- Only owners and admins can manage members in their org
Indexes:
idx_org_members_orgonorganization_ididx_org_members_useronuser_id
team_members¶
Global admin access control (separate from org membership).
idUUID PRIMARY KEYuser_idUUID UNIQUE REFERENCESusers(id)roleTEXT DEFAULT 'admin'display_nameTEXTavatar_urlTEXTcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Public read access
Feedback Tables¶
submissions¶
User-submitted feedback (ideas and problems).
idUUID PRIMARY KEYtitleTEXT NOT NULLdescriptionTEXT NOT NULLtypesubmission_type NOT NULL DEFAULT 'idea' — ENUM: 'idea', 'problem'statussubmission_status NOT NULL DEFAULT 'open' — ENUM: 'open', 'under_review', 'planned', 'in_progress', 'shipped', 'declined', 'merged'category_idUUID REFERENCEScategories(id)author_idUUID NOT NULL REFERENCESusers(id)merged_into_idUUID REFERENCESsubmissions(id)vote_countINTEGER NOT NULL DEFAULT 0 — Denormalized via triggercomment_countINTEGER NOT NULL DEFAULT 0 — Denormalized via triggerstatus_explanationTEXTgithub_issue_urlTEXTgithub_issue_numberINTEGERlocaleTEXT NOT NULL DEFAULT 'en'organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEsearch_vectortsvector GENERATED — Locale-aware full-text searchcreated_atTIMESTAMPTZ DEFAULT NOW()updated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Public read within current org (excludes merged submissions)
- Org members can insert into their org
- Authors can update their own submissions within their org
- Org admins have full access to all submissions in their org
Indexes:
idx_submissions_searchGIN onsearch_vectoridx_submissions_statusonstatusidx_submissions_categoryoncategory_ididx_submissions_authoronauthor_ididx_submissions_vote_countonvote_count DESCidx_submissions_createdoncreated_at DESCidx_submissions_mergedonmerged_into_idWHEREmerged_into_id IS NOT NULLidx_submissions_organization_idonorganization_ididx_submissions_org_statuson(organization_id, status)idx_submissions_org_createdon(organization_id, created_at DESC)idx_submissions_org_voteson(organization_id, vote_count DESC)idx_submissions_title_trgmGIN ontitle gin_trgm_opsidx_submissions_description_trgmGIN ondescription gin_trgm_ops
votes¶
One vote per user per submission.
idUUID PRIMARY KEYsubmission_idUUID NOT NULL REFERENCESsubmissions(id)ON DELETE CASCADEuser_idUUID NOT NULL REFERENCESusers(id)organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()- UNIQUE(
submission_id,user_id)
Trigger: trg_vote_count updates submissions.vote_count on INSERT/DELETE
RLS Policies:
- Org members can insert votes within their org
- Users can read their own votes within their org
- Users can delete their own votes within their org
Indexes:
idx_votes_submissiononsubmission_ididx_votes_useronuser_ididx_votes_organization_idonorganization_id
comments¶
Discussion threads on submissions (flat, no nested replies).
idUUID PRIMARY KEYsubmission_idUUID NOT NULL REFERENCESsubmissions(id)ON DELETE CASCADEauthor_idUUID NOT NULL REFERENCESusers(id)bodyTEXT NOT NULLis_officialBOOLEAN NOT NULL DEFAULT FALSE — Team responsesorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()updated_atTIMESTAMPTZ DEFAULT NOW()
Note: No parent_id column. Comments are flat.
Trigger: trg_comment_count updates submissions.comment_count on INSERT/DELETE
RLS Policies:
- Public read within current org
- Org members can insert comments
- Authors can update/delete their own comments within their org
Indexes:
idx_comments_submissiononsubmission_ididx_comments_authoronauthor_ididx_comments_organization_idonorganization_ididx_comments_created_atoncreated_at DESC
follows¶
User subscriptions to submission updates.
idUUID PRIMARY KEYsubmission_idUUID NOT NULL REFERENCESsubmissions(id)ON DELETE CASCADEuser_idUUID NOT NULL REFERENCESusers(id)organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()- UNIQUE(
submission_id,user_id)
Trigger: trg_auto_follow auto-creates a follow when user votes
RLS Policies:
- Org members can insert follows within their org
- Users can read their own follows within their org
- Users can delete their own follows within their org
Indexes:
idx_follows_useronuser_ididx_follows_submissiononsubmission_ididx_follows_organization_idonorganization_id
categories¶
Categorize submissions (multilingual JSONB names).
idUUID PRIMARY KEYnameJSONB NOT NULL — Multilingual:{"en": "Feature Requests", "es": "..."}slugTEXT UNIQUE NOT NULLdescriptionJSONBcolorTEXTsort_orderINTEGER DEFAULT 0organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()updated_atTIMESTAMPTZ DEFAULT NOW()
Note: No icon column. Column is sort_order, not order_index.
RLS Policies:
- Public read within current org
- Org admins can manage categories
Indexes:
UNIQUEconstraint onslug(implicit index)idx_categories_organization_idonorganization_id
tags¶
Cross-cutting labels for submissions (multilingual JSONB names).
idUUID PRIMARY KEYnameJSONB NOT NULL — MultilingualslugTEXT UNIQUE NOT NULLorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Public read within current org
- Org admins can manage tags
Indexes:
UNIQUEconstraint onslug(implicit index)idx_tags_organization_idonorganization_id
submission_tags¶
Many-to-many join table for submissions and tags.
submission_idUUID REFERENCESsubmissions(id)ON DELETE CASCADEtag_idUUID REFERENCEStags(id)ON DELETE CASCADEorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADE- PRIMARY KEY (
submission_id,tag_id)
RLS Policies:
- Public read within current org
- Org admins can manage submission tags
Indexes:
idx_submission_tags_organization_idonorganization_id
status_history¶
Audit trail for submission status changes.
idUUID PRIMARY KEYsubmission_idUUID NOT NULL REFERENCESsubmissions(id)ON DELETE CASCADEold_statussubmission_statusnew_statussubmission_status NOT NULLchanged_byUUID NOT NULL REFERENCESusers(id)noteTEXTorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Public read within current org
Indexes:
idx_status_history_submissiononsubmission_ididx_status_history_organization_idonorganization_id
assignments¶
Team member assignment to submissions.
idUUID PRIMARY KEYsubmission_idUUID NOT NULL REFERENCESsubmissions(id)ON DELETE CASCADEassignee_idUUID NOT NULL REFERENCESteam_members(id)assigned_byUUID NOT NULL REFERENCESusers(id)organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Org admins have full access within their org
Indexes:
idx_assignments_submissiononsubmission_ididx_assignments_assigneeonassignee_ididx_assignments_organization_idonorganization_id
moderation_queue¶
Spam and abuse review queue.
idUUID PRIMARY KEYsubmission_idUUID REFERENCESsubmissions(id)ON DELETE CASCADEcomment_idUUID REFERENCEScomments(id)ON DELETE CASCADEreasonTEXT NOT NULLstatusTEXT NOT NULL DEFAULT 'pending'reviewed_byUUID REFERENCESusers(id)reviewed_atTIMESTAMPTZorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()- CHECK (
submission_id IS NOT NULL OR comment_id IS NOT NULL)
RLS Policies:
- Org admins have full access within their org
Indexes:
idx_moderation_statusonstatusWHEREstatus = 'pending'idx_moderation_queue_organization_idonorganization_id
notification_preferences¶
Per-user email digest settings.
user_idUUID PRIMARY KEY REFERENCESusers(id)status_changesBOOLEAN NOT NULL DEFAULT TRUEnew_commentsBOOLEAN NOT NULL DEFAULT TRUEofficial_responsesBOOLEAN NOT NULL DEFAULT TRUEemail_digestTEXT NOT NULL DEFAULT 'instant'organization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEupdated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Users can manage their own preferences within their org
Indexes:
idx_notification_preferences_organization_idonorganization_id
Survey Tables¶
survey_surveys¶
Survey definitions with status lifecycle.
idUUID PRIMARY KEYorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEtitleTEXT NOT NULLdescriptionTEXTstatussurvey_status NOT NULL DEFAULT 'draft' — ENUM: 'draft', 'published', 'paused', 'archived'settingsJSONB DEFAULT '{}'created_byUUID REFERENCESauth.users(id)— Note: Legacy reference, should beusers(id)response_countINTEGER NOT NULL DEFAULT 0 — Denormalized via triggercompletion_rateNUMERIC(5,2) NOT NULL DEFAULT 0published_atTIMESTAMPTZcreated_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
Triggers:
trg_survey_surveys_updated_atauto-updatesupdated_aton UPDATE
RLS Policies:
- Public read for published surveys
- Org admins have full access within their org (uses
auth.uid()— should be updated tocurrent_user_id())
Indexes:
idx_survey_surveys_statusonstatusidx_survey_surveys_created_byoncreated_byidx_survey_surveys_organization_idonorganization_id
survey_questions¶
Questions belonging to surveys with type-specific JSONB config.
idUUID PRIMARY KEYsurvey_idUUID NOT NULL REFERENCESsurvey_surveys(id)ON DELETE CASCADEtypesurvey_question_type NOT NULL — ENUM: 'multiple_choice', 'rating', 'free_text', 'yes_no', 'matrix', 'ranking', 'image_selection', 'file_upload'textTEXT NOT NULLdescriptionTEXTrequiredBOOLEAN NOT NULL DEFAULT falseconfigJSONB DEFAULT '{}' — Question-specific settings (NOTsettings)sort_orderINTEGER NOT NULL DEFAULT 0 — NOTorder_indexconditional_logicJSONBcreated_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
Note: Column is config, not settings. Column is sort_order, not order_index.
Triggers:
trg_survey_questions_updated_atauto-updatesupdated_aton UPDATE
RLS Policies:
- Public read for questions of published surveys
- Org admins have full access (via survey's organization)
Indexes:
idx_survey_questions_survey_idonsurvey_ididx_survey_questions_sort_orderon(survey_id, sort_order)
survey_responses¶
Individual survey response sessions with resume capability.
idUUID PRIMARY KEYsurvey_idUUID NOT NULL REFERENCESsurvey_surveys(id)ON DELETE CASCADEorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEuser_idUUID REFERENCESauth.users(id)— Note: Legacy reference, should beusers(id)statussurvey_response_status NOT NULL DEFAULT 'in_progress' — ENUM: 'in_progress', 'paused', 'completed', 'expired'resume_tokenTEXT UNIQUEresume_expires_atTIMESTAMPTZconsent_recorded_atTIMESTAMPTZprivacy_policy_versionTEXTstarted_atTIMESTAMPTZ NOT NULL DEFAULT now()completed_atTIMESTAMPTZuser_agentTEXTlocaleTEXT
Triggers:
trg_survey_response_countupdatessurvey_surveys.response_counton INSERT/DELETEtrg_survey_completion_rateupdatessurvey_surveys.completion_rateon status change to 'completed'
RLS Policies:
- Authenticated users can create responses
- Users can read their own responses; admins can read all in their org
- Users can update their own in-progress/paused responses
Indexes:
idx_survey_responses_survey_idonsurvey_ididx_survey_responses_user_idonuser_ididx_survey_responses_statusonstatusidx_survey_responses_resume_tokenonresume_tokenidx_survey_responses_organization_idonorganization_id
survey_answers¶
Per-question answers stored as flexible JSONB values.
idUUID PRIMARY KEYresponse_idUUID NOT NULL REFERENCESsurvey_responses(id)ON DELETE CASCADEquestion_idUUID NOT NULL REFERENCESsurvey_questions(id)ON DELETE CASCADEvalueJSONBfile_urlsTEXT[]created_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()- UNIQUE(
response_id,question_id)
RLS Policies:
- Users can insert answers to their own in-progress responses
- Users and org admins can read answers
- Users can update their own in-progress answers
Indexes:
idx_survey_answers_response_idonresponse_ididx_survey_answers_question_idonquestion_ididx_survey_answers_question_responseon(question_id, response_id)
survey_templates¶
Reusable survey templates with pre-configured questions.
idUUID PRIMARY KEYorganization_idUUID REFERENCESorganizations(id)ON DELETE CASCADEnameTEXT NOT NULLdescriptionTEXTcategoryTEXTquestionsJSONB NOT NULL DEFAULT '[]'default_settingsJSONB DEFAULT '{}'suggested_triggersJSONB DEFAULT '[]'is_systemBOOLEAN NOT NULL DEFAULT falsecreated_byUUID REFERENCESauth.users(id)— Note: Legacy reference, should beusers(id)created_atTIMESTAMPTZ NOT NULL DEFAULT now()
RLS Policies:
- Public for system templates, org-scoped for custom
- Org admins have full access for org templates
Indexes:
idx_survey_templates_categoryoncategory
survey_translations¶
Survey translations for multiple locales.
idUUID PRIMARY KEYsurvey_idUUID NOT NULL REFERENCESsurvey_surveys(id)ON DELETE CASCADElocaleTEXT NOT NULLtitleTEXTdescriptionTEXTthank_you_messageTEXTquestionsJSONB DEFAULT '{}'statusTEXT NOT NULL DEFAULT 'draft'created_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()- UNIQUE(
survey_id,locale)
Indexes:
idx_survey_translations_survey_idonsurvey_id
survey_themes¶
Survey theming and branding.
idUUID PRIMARY KEYorganization_idUUID REFERENCESorganizations(id)ON DELETE CASCADEnameTEXT NOT NULLcolorsJSONB DEFAULT '{}'typographyJSONB DEFAULT '{}'spacingJSONB DEFAULT '{}'border_radiusTEXTlogo_urlTEXTlogo_positionTEXTprogress_barJSONB DEFAULT '{}'is_systemBOOLEAN NOT NULL DEFAULT falsecreated_byUUID REFERENCESauth.users(id)— Note: Legacy reference, should beusers(id)created_atTIMESTAMPTZ NOT NULL DEFAULT now()
RLS Policies:
- System themes readable by all; org themes by org members
survey_triggers¶
Event and time-based survey activation rules.
idUUID PRIMARY KEYsurvey_idUUID NOT NULL REFERENCESsurvey_surveys(id)ON DELETE CASCADEtypeTEXT NOT NULL DEFAULT 'manual'configJSONB DEFAULT '{}'enabledBOOLEAN NOT NULL DEFAULT truecreated_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
RLS Policies:
- Org admins have full access (via survey's organization)
Indexes:
idx_survey_triggers_survey_idonsurvey_ididx_survey_triggers_enabledontypeWHEREenabled = true
survey_targeting_rules¶
Audience segmentation rules for survey distribution.
idUUID PRIMARY KEYsurvey_idUUID NOT NULL REFERENCESsurvey_surveys(id)ON DELETE CASCADErulesJSONB NOT NULL DEFAULT '{}'created_atTIMESTAMPTZ NOT NULL DEFAULT now()updated_atTIMESTAMPTZ NOT NULL DEFAULT now()
RLS Policies:
- Org admins have full access (via survey's organization)
Indexes:
idx_survey_targeting_rules_survey_idonsurvey_id
survey_fatigue_config¶
Rate limiting configuration to prevent survey overload.
idUUID PRIMARY KEYsurvey_idUUID REFERENCESsurvey_surveys(id)ON DELETE CASCADEis_globalBOOLEAN NOT NULL DEFAULT falsefrequency_limitINTEGER DEFAULT 1cooldown_daysINTEGER DEFAULT 7suppress_if_completedBOOLEAN DEFAULT truesuppress_if_dismissedBOOLEAN DEFAULT truedismiss_cooldown_daysINTEGER DEFAULT 30priorityINTEGER DEFAULT 0created_atTIMESTAMPTZ NOT NULL DEFAULT now()- CONSTRAINT
fatigue_survey_or_globalCHECK ((survey_id IS NOT NULL AND is_global = false) OR (survey_id IS NULL AND is_global = true))
RLS Policies:
- Org admins have full access; global configs readable by authenticated users
survey_fatigue_state¶
Per-user fatigue tracking state.
idUUID PRIMARY KEYuser_idUUID NOT NULL REFERENCESauth.users(id)— Note: Legacy reference, should beusers(id)last_survey_shown_atTIMESTAMPTZsurveys_shown_todayINTEGER NOT NULL DEFAULT 0surveys_shown_today_dateDATEcompletion_rate_30dNUMERIC(5,2) DEFAULT 0survey_historyJSONB DEFAULT '[]'updated_atTIMESTAMPTZ NOT NULL DEFAULT now()- UNIQUE(
user_id)
RLS Policies:
- Users can manage their own fatigue state
Indexes:
idx_survey_fatigue_state_user_idonuser_id
Admin Tables¶
audit_log¶
Append-only audit trail for security-relevant actions (SOC 2 / ISO 27001).
idUUID PRIMARY KEYactor_idUUID REFERENCESusers(id)actor_typeTEXT NOT NULL DEFAULT 'user'actionTEXT NOT NULLresource_typeTEXT NOT NULLresource_idUUIDmetadataJSONBip_addressINETuser_agentTEXTorganization_idUUID NOT NULL REFERENCESorganizations(id)ON DELETE CASCADEcreated_atTIMESTAMPTZ DEFAULT NOW()
RLS Policies:
- Append-only insert (anyone can log)
- Org admins can read within their org
Indexes:
idx_audit_log_actoronactor_ididx_audit_log_actiononactionidx_audit_log_resourceon(resource_type, resource_id)idx_audit_log_createdoncreated_at DESCidx_audit_log_organization_idonorganization_id
beta_signups¶
(This table is not explicitly created in the baseline migration, but may exist via a separate migration or external process.)
Views¶
public_user_profiles¶
Public view of user profiles with redacted information.
CREATE OR REPLACE VIEW public_user_profiles AS
SELECT
id,
COALESCE(display_name, 'User') AS display_name
FROM users;
Grants: SELECT on authenticated, anon
Storage¶
organization-logos¶
Storage bucket for organization logos (public-read, 2MB limit).
- Bucket ID:
organization-logos - Public: Yes
- File size limit: 2MB
- Allowed MIME types:
image/png,image/jpeg,image/webp
RLS Policies:
- Authenticated users can upload to org logos bucket
- Public read access to logos
- Authenticated users can delete org logos
Key RLS Patterns¶
Org-Scoped Read¶
CREATE POLICY "submissions_org_read" ON submissions
FOR SELECT USING (organization_id = current_organization_id());
Org Member Insert¶
CREATE POLICY "submissions_org_insert" ON submissions
FOR INSERT WITH CHECK (
organization_id = current_organization_id()
AND current_user_id() = author_id
AND is_org_member()
);
Own Update¶
CREATE POLICY "submissions_org_own_update" ON submissions
FOR UPDATE USING (
organization_id = current_organization_id()
AND current_user_id() = author_id
);
Admin Full Access¶
CREATE POLICY "submissions_org_admin_all" ON submissions
FOR ALL USING (
organization_id = current_organization_id()
AND is_org_admin()
);
Known Legacy References¶
Several survey tables still reference auth.users(id) in foreign keys:
survey_surveys.created_bysurvey_responses.user_idsurvey_templates.created_bysurvey_themes.created_bysurvey_fatigue_state.user_id
RLS policies on these tables use auth.uid() and should be updated to use current_user_id(). These are legacy references from before the custom auth system was implemented.
What's Next¶
- System Overview — How the app uses these tables
- Authentication — RLS policies in depth
- Realtime — Realtime subscriptions to table changes