WIP: lokalen Stand vor dem Aufräumen sichern

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
denshooter
2026-10-15 15:32:22 +02:00
parent 7e910ce23d
commit e4f99255bd
68 changed files with 3510 additions and 5343 deletions
@@ -0,0 +1,132 @@
-- nightly — Fix migration: align DB with iOS app + add missing features
-- Run after 001_schema.sql
-- ── 1. Fix mood enum: replace old values with iOS app values ─────────────────
-- Drop dependent objects first
DROP VIEW IF EXISTS public.feed_posts;
ALTER TABLE public.posts ALTER COLUMN mood TYPE text USING mood::text;
DROP TYPE IF EXISTS public.post_mood;
CREATE TYPE public.post_mood AS ENUM ('still', 'unruhig', 'schwer', 'geladen', 'zart', 'dunkel');
ALTER TABLE public.posts ALTER COLUMN mood TYPE public.post_mood USING mood::public.post_mood;
-- ── 2. Increase content length from 280 to 420 ───────────────────────────────
ALTER TABLE public.posts DROP CONSTRAINT posts_content_check;
ALTER TABLE public.posts ADD CONSTRAINT posts_content_check
CHECK (LENGTH(TRIM(content)) BETWEEN 1 AND 420);
-- ── 3. Add content warning columns to posts ─────────────────────────────────
ALTER TABLE public.posts ADD COLUMN IF NOT EXISTS has_content_warning BOOLEAN DEFAULT FALSE NOT NULL;
ALTER TABLE public.posts ADD COLUMN IF NOT EXISTS content_warning_type TEXT;
-- ── 4. Recreate feed_posts view with content_warning fields ──────────────────
CREATE OR REPLACE VIEW public.feed_posts AS
SELECT
p.id,
p.content,
p.mood,
p.is_anonymous,
p.created_at,
p.has_content_warning,
p.content_warning_type,
CASE WHEN p.is_anonymous THEN NULL ELSE p.user_id END AS author_id,
CASE WHEN p.is_anonymous THEN NULL ELSE pr.username END AS author_username,
CASE WHEN p.is_anonymous THEN NULL ELSE pr.display_name END AS author_display_name,
CASE WHEN p.is_anonymous THEN NULL ELSE pr.avatar_url END AS author_avatar_url,
COALESCE((
SELECT COUNT(*) FROM public.resonances r WHERE r.post_id = p.id
), 0)::int AS resonance_count
FROM public.posts p
JOIN public.profiles pr ON pr.id = p.user_id
WHERE p.deleted_at IS NULL
AND p.review_status IN ('ok', 'approved')
AND p.created_at > NOW() - INTERVAL '14 hours';
-- ── 5. Add get_streak RPC ────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.get_streak(p_user_id UUID)
RETURNS TABLE(streak INT) LANGUAGE plpgsql STABLE AS $$
DECLARE
v_streak INT := 0;
v_check_date DATE := CURRENT_DATE;
v_has_post BOOLEAN;
BEGIN
LOOP
SELECT EXISTS(
SELECT 1 FROM public.posts
WHERE user_id = p_user_id
AND deleted_at IS NULL
AND (created_at AT TIME ZONE 'Europe/Berlin')::DATE = v_check_date
) INTO v_has_post;
IF v_has_post THEN
v_streak := v_streak + 1;
v_check_date := v_check_date - 1;
ELSE
EXIT;
END IF;
END LOOP;
-- Check if user posted today; if not, streak is 0
IF v_streak = 0 THEN
RETURN NEXT;
ELSE
RETURN QUERY SELECT v_streak;
END IF;
END;
$$;
-- ── 6. Add get_resonance_count RPC ───────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.get_resonance_count(p_user_id UUID)
RETURNS TABLE(count INT) LANGUAGE plpgsql STABLE AS $$
BEGIN
RETURN QUERY
SELECT COUNT(*)::int AS count
FROM public.resonances r
JOIN public.posts p ON p.id = r.post_id
WHERE p.user_id = p_user_id
AND p.deleted_at IS NULL;
END;
$$;
-- ── 7. Add public_profile view for other-user profile lookups ────────────────
CREATE OR REPLACE VIEW public.public_profile AS
SELECT
p.id,
p.username,
p.display_name,
p.bio,
p.avatar_url,
p.is_pro,
p.is_admin,
p.created_at,
(SELECT COUNT(*) FROM public.posts WHERE user_id = p.id AND deleted_at IS NULL)::int AS post_count,
(SELECT COUNT(*) FROM public.resonances r JOIN public.posts p2 ON p2.id = r.post_id WHERE p2.user_id = p.id AND p2.deleted_at IS NULL)::int AS resonance_count,
(SELECT COUNT(*) FROM public.follows WHERE following_id = p.id)::int AS follower_count,
(SELECT COUNT(*) FROM public.follows WHERE follower_id = p.id)::int AS following_count
FROM public.profiles p;
-- ── 8. Add profile view RLS (public_profile is a view, inherits from profiles) ─
-- Allow updates to profiles row
-- (already handled by existing policy, no change needed)
-- ── 9. Enable Realtime for posts table ────────────────────────────────────────
ALTER PUBLICATION supabase_realtime ADD TABLE public.posts;
-- ── 10. Fix RLS for posts: allow reading all non-deleted approved posts ───────
DROP POLICY IF EXISTS posts_select ON public.posts;
CREATE POLICY "posts_select" ON public.posts FOR SELECT
USING (
deleted_at IS NULL
AND review_status IN ('ok', 'approved')
);
+112
View File
@@ -0,0 +1,112 @@
-- nightly — Seed data: realistic German users and posts for testing
-- Run manually or via supabase db reset --seed
-- ── Cleanup (idempotent) ────────────────────────────────────────────────────
DELETE FROM resonances;
DELETE FROM whispers;
DELETE FROM follows;
DELETE FROM reports;
DELETE FROM posts;
DELETE FROM profiles WHERE id != auth.uid(); -- keep own account
-- Note: we can't delete from auth.users easily, so we'll create new ones
-- ── Create test users via Supabase Auth ──────────────────────────────────────
-- These must be created via auth.users first (or signup), then profiles get auto-created.
-- For seeding we insert directly into profiles with UUIDs.
INSERT INTO profiles (id, username, display_name, bio, is_pro) VALUES
('a0000000-0000-0000-0000-000000000001', 'nachtvogel_', 'Nachtvogel', '3 Uhr ist meine goldene Stunde', true),
('a0000000-0000-0000-0000-000000000002', 'insomniac_', 'can''t sleep', NULL, false),
('a0000000-0000-0000-0000-000000000003', 'felix.nacht', 'Felix', NULL, false),
('a0000000-0000-0000-0000-000000000004', '4am_thoughts', 'Lena', 'Nachtgedanken auf Deutsch', false),
('a0000000-0000-0000-0000-000000000005', 'mondkind', 'Mondkind', NULL, true),
('a0000000-0000-0000-0000-000000000006', 'keinschlaf_', 'k.einschlaf', 'Aufgewacht und nicht mehr müde', false),
('a0000000-0000-0000-0000-000000000007', 'sterne_sehn', 'Sternesehende', 'Liege auf dem Dach und schaue hoch', false),
('a0000000-0000-0000-0000-000000000008', 'dunkelheit_', 'Dunkelheit', NULL, false),
('a0000000-0000-0000-0000-000000000009', 'leise.stimme', 'Leise Stimme', 'Ich schreibe nachts am besten', false),
('a0000000-0000-0000-0000-000000000010', 'wolkenzug_', 'Wolkenzug', 'Gedanken die wie Wolken vorbeiziehen', false)
ON CONFLICT (id) DO NOTHING;
-- ── Posts ─────────────────────────────────────────────────────────────────────
INSERT INTO posts (id, user_id, content, mood, is_anonymous, has_content_warning, content_warning_type, created_at) VALUES
-- User 1: nachtvogel_
('b0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000001', 'manchmal ist die stille nachts lauter als alle geräusche tagsüber', 'still', false, false, NULL, NOW() - INTERVAL '5 minutes'),
('b0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000001', 'regentropfen am fenster sind das einzige was mich gerade noch companie macht', 'still', false, false, NULL, NOW() - INTERVAL '30 minutes'),
-- User 2: insomniac_
('b0000000-0000-0000-0000-000000000003', 'a0000000-0000-0000-0000-000000000002', 'das licht vom handy macht alles schlimmer aber ich leg es trotzdem nicht weg', 'unruhig', false, false, NULL, NOW() - INTERVAL '15 minutes'),
('b0000000-0000-0000-0000-000000000004', 'a0000000-0000-0000-0000-000000000002', '3 uhr und ich rechne vor allem leb ich noch wenn ich aufwache', 'unruhig', false, false, NULL, NOW() - INTERVAL '2 hours'),
-- User 3: felix.nacht
('b0000000-0000-0000-0000-000000000005', 'a0000000-0000-0000-0000-000000000003', 'hab gerade realisiert dass ich seit 4 stunden auf tiktok bin und morgen um 7 aufstehen muss', 'geladen', false, false, NULL, NOW() - INTERVAL '1 minute'),
('b0000000-0000-0000-0000-000000000006', 'a0000000-0000-0000-0000-000000000003', 'hab mein zimmer aufgeräumt statt zu schlafen. zumindest irgendetwas produktives heute nacht', 'geladen', false, true, 'other', NOW() - INTERVAL '45 minutes'),
-- User 4: 4am_thoughts
('b0000000-0000-0000-0000-000000000007', 'a0000000-0000-0000-0000-000000000004', 'traum von einer stadt in der niemand schläft und alle auf den dächern sitzen und in den himmel schauen', 'zart', false, false, NULL, NOW() - INTERVAL '2 hours'),
('b0000000-0000-0000-0000-000000000008', 'a0000000-0000-0000-0000-000000000004', 'die nacht hat eine andere sprache als der tag', 'still', false, false, NULL, NOW() - INTERVAL '4 hours'),
-- User 5: mondkind
('b0000000-0000-0000-0000-000000000009', 'a0000000-0000-0000-0000-000000000005', 'wenn der mond hell genug ist brauch ich kein licht auf dem handy', 'zart', false, false, NULL, NOW() - INTERVAL '20 minutes'),
-- Anonymous posts
('b0000000-0000-0000-0000-000000000010', 'a0000000-0000-0000-0000-000000000001', 'ich warte irgendwie immer noch auf eine nachricht von dir obwohl ich weiß dass sie nicht kommt', 'schwer', true, false, NULL, NOW() - INTERVAL '5 minutes'),
('b0000000-0000-0000-0000-000000000011', 'a0000000-0000-0000-0000-000000000002', 'ich hab meiner besten freundin gesagt dass ich sie vermisse und sie hat nur geantwortet mit einem smiley', 'zart', true, false, NULL, NOW() - INTERVAL '90 minutes'),
('b0000000-0000-0000-0000-000000000012', 'a0000000-0000-0000-0000-000000000003', 'ich kann nicht aufhören an ihn zu denken und das schlimmste ist ich weiß dass es falsch ist', 'dunkel', true, true, 'other', NOW() - INTERVAL '3 hours'),
('b0000000-0000-0000-0000-000000000013', 'a0000000-0000-0000-0000-000000000004', 'warum denk ich um 3 uhr morgens noch an das was ich 2019 gesagt hab', 'schwer', true, false, NULL, NOW() - INTERVAL '3 minutes'),
('b0000000-0000-0000-0000-000000000014', 'a0000000-0000-0000-0000-000000000005', 'manchmal wünsch ich mir jemand würde einfach nur nachts bei mir sein ohne dass wir reden müssen', 'zart', true, false, NULL, NOW() - INTERVAL '1 hour'),
-- More diverse posts
('b0000000-0000-0000-0000-000000000015', 'a0000000-0000-0000-0000-000000000006', 'kopfhörer auf. playlist die nur nachts funktioniert. welt draußen geht weiter aber hier drinnen ist sie gestoppt', 'still', false, false, NULL, NOW() - INTERVAL '10 minutes'),
('b0000000-0000-0000-0000-000000000016', 'a0000000-0000-0000-0000-000000000007', 'lieber allein wach als in gesellschaft die mich nicht versteht', 'dunkel', false, false, NULL, NOW() - INTERVAL '40 minutes'),
('b0000000-0000-0000-0000-000000000017', 'a0000000-0000-0000-0000-000000000008', 'ich hab so viel energie um 3 uhr aber null motivation um 15 uhr', 'geladen', true, false, NULL, NOW() - INTERVAL '55 minutes'),
('b0000000-0000-0000-0000-000000000018', 'a0000000-0000-0000-0000-000000000009', 'die nacht ist der einzige ort wo ich ehrlich sein kann', 'dunkel', false, false, NULL, NOW() - INTERVAL '1 hour 20 minutes'),
('b0000000-0000-0000-0000-000000000019', 'a0000000-0000-0000-0000-000000000010', 'wolken ziehen vorbei und ich frage mich ob sie auch nachts nicht schlafen können', 'still', false, false, NULL, NOW() - INTERVAL '2 hours 30 minutes'),
('b0000000-0000-0000-0000-000000000020', 'a0000000-0000-0000-0000-000000000001', 'jeder irgendwo auf der welt ist gerade auch wach und das tröstet mich mehr als es sollte', 'zart', false, false, NULL, NOW() - INTERVAL '8 minutes'),
-- Content warning posts
('b0000000-0000-0000-0000-000000000021', 'a0000000-0000-0000-0000-000000000004', 'heute nacht war das erste mal seit langem dass ich dachte vielleicht wird es doch noch besser', 'zart', false, true, 'other', NOW() - INTERVAL '25 minutes'),
('b0000000-0000-0000-0000-000000000022', 'a0000000-0000-0000-0000-000000000006', 'das problem ist nicht dass ich nicht schlafen kann. das problem ist dass ich nicht aufhören kann zu denken', 'unruhig', false, false, NULL, NOW() - INTERVAL '50 minutes')
ON CONFLICT DO NOTHING;
-- ── Resonances ────────────────────────────────────────────────────────────────
INSERT INTO resonances (post_id, user_id, created_at) VALUES
('b0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000002', NOW() - INTERVAL '4 minutes'),
('b0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000003', NOW() - INTERVAL '3 minutes'),
('b0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000004', NOW() - INTERVAL '2 minutes'),
('b0000000-0000-0000-0000-000000000003', 'a0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '10 minutes'),
('b0000000-0000-0000-0000-000000000005', 'a0000000-0000-0000-0000-000000000005', NOW() - INTERVAL '30 seconds'),
('b0000000-0000-0000-0000-000000000010', 'a0000000-0000-0000-0000-000000000007', NOW() - INTERVAL '4 minutes'),
('b0000000-0000-0000-0000-000000000010', 'a0000000-0000-0000-0000-000000000008', NOW() - INTERVAL '3 minutes'),
('b0000000-0000-0000-0000-000000000011', 'a0000000-0000-0000-0000-000000000009', NOW() - INTERVAL '45 minutes'),
('b0000000-0000-0000-0000-000000000011', 'a0000000-0000-0000-0000-000000000010', NOW() - INTERVAL '40 minutes'),
('b0000000-0000-0000-0000-000000000012', 'a0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '2 hours'),
('b0000000-0000-0000-0000-000000000013', 'a0000000-0000-0000-0000-000000000005', NOW() - INTERVAL '2 minutes'),
('b0000000-0000-0000-0000-000000000013', 'a0000000-0000-0000-0000-000000000006', NOW() - INTERVAL '1 minute'),
('b0000000-0000-0000-0000-000000000009', 'a0000000-0000-0000-0000-000000000004', NOW() - INTERVAL '15 minutes'),
('b0000000-0000-0000-0000-000000000020', 'a0000000-0000-0000-0000-000000000002', NOW() - INTERVAL '5 minutes'),
('b0000000-0000-0000-0000-000000000020', 'a0000000-0000-0000-0000-000000000009', NOW() - INTERVAL '3 minutes'),
('b0000000-0000-0000-0000-000000000007', 'a0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '1 hour')
ON CONFLICT DO NOTHING;
-- ── Follows ──────────────────────────────────────────────────────────────────
INSERT INTO follows (follower_id, following_id, created_at) VALUES
('a0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000002', NOW() - INTERVAL '3 days'),
('a0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000005', NOW() - INTERVAL '1 day'),
('a0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000003', NOW() - INTERVAL '5 days'),
('a0000000-0000-0000-0000-000000000004', 'a0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '2 days'),
('a0000000-0000-0000-0000-000000000005', 'a0000000-0000-0000-0000-000000000006', NOW() - INTERVAL '12 hours'),
('a0000000-0000-0000-0000-000000000006', 'a0000000-0000-0000-0000-000000000007', NOW() - INTERVAL '6 hours'),
('a0000000-0000-0000-0000-000000000009', 'a0000000-0000-0000-0000-000000000001', NOW() - INTERVAL '4 days'),
('a0000000-0000-0000-0000-000000000010', 'a0000000-0000-0000-0000-000000000005', NOW() - INTERVAL '1 day')
ON CONFLICT DO NOTHING;
-- ── Whispers ──────────────────────────────────────────────────────────────────
INSERT INTO whispers (id, from_user_id, to_user_id, content, read_at, created_at) VALUES
('c0000000-0000-0000-0000-000000000001', 'a0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000001', 'dein post hat mir gerade alles gesagt was ich fühle', NULL, NOW() - INTERVAL '20 minutes'),
('c0000000-0000-0000-0000-000000000002', 'a0000000-0000-0000-0000-000000000005', 'a0000000-0000-0000-0000-000000000001', 'wollte dir nur sagen dass du nicht allein bist diese nacht', NOW() - INTERVAL '10 minutes', NOW() - INTERVAL '2 hours')
ON CONFLICT DO NOTHING;