mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 19:09:41 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
8.4 KiB
8.4 KiB
Skalierbare Minimallösung für Zentrale Stories
Foundation für späteres Community-System
Kernprinzip
Start mit minimalem Setup, das bereits die richtige Struktur für spätere Features hat.
Phase 1: Minimale Basis (JETZT - 2 Tage)
1.1 Stories Tabelle erweitern
-- Diese Felder bleiben für immer und werden später genutzt
ALTER TABLE stories
ADD COLUMN is_published BOOLEAN DEFAULT FALSE,
ADD COLUMN published_at TIMESTAMPTZ,
ADD COLUMN published_by TEXT, -- 'system', 'user', 'curator'
ADD COLUMN visibility VARCHAR(20) DEFAULT 'private'
CHECK (visibility IN ('private', 'public', 'central', 'featured')),
ADD COLUMN featured_score DECIMAL DEFAULT 0, -- Für späteres Ranking
ADD COLUMN metadata JSONB DEFAULT '{}'::jsonb; -- Flexibel für Zukunft
-- Index für Performance (wichtig für später!)
CREATE INDEX idx_story_visibility ON stories(visibility, published_at DESC);
CREATE INDEX idx_story_metadata ON stories USING gin (metadata);
1.2 Story Collections (Basis-Struktur)
-- Diese Tabelle JETZT anlegen, auch wenn minimal genutzt
CREATE TABLE story_collections (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
slug VARCHAR(50) UNIQUE NOT NULL, -- 'central-stories', 'weekly-picks'
name VARCHAR(255) NOT NULL,
description TEXT,
type VARCHAR(50) DEFAULT 'manual', -- 'manual', 'automatic', 'contest'
is_active BOOLEAN DEFAULT TRUE,
sort_order INTEGER DEFAULT 0,
config JSONB DEFAULT '{}'::jsonb, -- Für spätere Features
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- Junction Table
CREATE TABLE collection_stories (
collection_id UUID REFERENCES story_collections(id) ON DELETE CASCADE,
story_id UUID REFERENCES stories(id) ON DELETE CASCADE,
position INTEGER DEFAULT 0,
added_at TIMESTAMPTZ DEFAULT NOW(),
added_by TEXT,
PRIMARY KEY (collection_id, story_id)
);
-- Erste Collection erstellen
INSERT INTO story_collections (slug, name, description, type)
VALUES ('central-stories', 'Märchenzauber Geschichten', 'Offizielle Geschichten von Märchenzauber', 'manual');
1.3 RLS Policies (zukunftssicher)
-- Stories Policy
CREATE POLICY "story_visibility_policy" ON stories
FOR SELECT USING (
user_id = auth.uid()::text -- Eigene Stories
OR visibility IN ('public', 'central', 'featured') -- Öffentliche
);
-- Collections sind öffentlich lesbar
ALTER TABLE story_collections ENABLE ROW LEVEL SECURITY;
CREATE POLICY "collections_public_read" ON story_collections
FOR SELECT USING (is_active = true);
ALTER TABLE collection_stories ENABLE ROW LEVEL SECURITY;
CREATE POLICY "collection_stories_public_read" ON collection_stories
FOR SELECT USING (true);
1.4 Backend Service (erweiterbar)
// story.service.ts - Struktur für Wachstum
export class StoryService {
// Basis-Methode die später erweitert wird
async getStories(
userId: string,
options?: {
includePublic?: boolean;
collectionSlug?: string;
}
) {
let query = this.supabase.from('stories').select('*');
if (options?.collectionSlug) {
// Via Collection
const { data: collection } = await this.supabase
.from('story_collections')
.select('id')
.eq('slug', options.collectionSlug)
.single();
query = this.supabase
.from('collection_stories')
.select('stories(*)')
.eq('collection_id', collection.id)
.order('position');
} else {
// Direkt
query = query.or(`user_id.eq.${userId},visibility.in.(public,central,featured)`);
}
return query;
}
// Vorbereitet für später
async voteStory(storyId: string, userId: string) {
// TODO: Implement in Phase 2
throw new Error('Coming soon');
}
}
1.5 Mobile App (mit Zukunfts-Hooks)
// types/story.ts
export interface Story {
// Bestehende Felder...
visibility?: 'private' | 'public' | 'central' | 'featured';
metadata?: {
votes?: number; // Vorbereitet
author?: string; // Vorbereitet
collection?: string; // Vorbereitet
};
}
// components/StoryList.tsx
export const StoryList = () => {
const [filter, setFilter] = useState<'all' | 'mine' | 'central'>('all');
// Struktur die später erweitert wird
const sections = [
{
title: 'Märchenzauber Geschichten',
data: stories.filter(s => s.visibility === 'central'),
badge: '✨'
},
{
title: 'Meine Geschichten',
data: stories.filter(s => s.user_id === currentUser.id),
}
];
return (
<SectionList
sections={sections}
renderItem={({ item }) => (
<StoryCard
story={item}
onVote={() => {/* TODO Phase 2 */}}
showVoteButton={false} // Wird später true
/>
)}
/>
);
};
Phase 2: Voting hinzufügen (+1 Woche)
-- Neue Tabelle, bestehende Struktur bleibt
CREATE TABLE story_votes (
story_id UUID REFERENCES stories(id),
user_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (story_id, user_id)
);
-- Stories Tabelle Update
ALTER TABLE stories ADD COLUMN vote_count INTEGER DEFAULT 0;
-- Automatisches Update via Trigger
CREATE TRIGGER update_vote_count
AFTER INSERT OR DELETE ON story_votes
FOR EACH ROW EXECUTE FUNCTION update_story_vote_count();
Backend:
// Nur neue Methode hinzufügen
async voteStory(storyId: string, userId: string) {
return this.supabase.from('story_votes').insert({
story_id: storyId,
user_id: userId
});
}
Phase 3: Character System (+1 Woche)
-- Characters Tabelle analog zu Stories
ALTER TABLE characters
ADD COLUMN is_published BOOLEAN DEFAULT FALSE,
ADD COLUMN visibility VARCHAR(20) DEFAULT 'private',
ADD COLUMN metadata JSONB DEFAULT '{}'::jsonb;
-- Character Collections (analog zu story_collections)
CREATE TABLE character_collections (...);
Phase 4: User Publishing (+1 Woche)
-- Keine Schema-Änderung nötig!
-- Nur Logic: User können visibility auf 'public' setzen
-- Neue Collection
INSERT INTO story_collections (slug, name, type)
VALUES ('community', 'Community Geschichten', 'automatic');
Phase 5: Erweiterte Features
- Weekly Collections → Nutzt bestehende
story_collections - Achievements → In
metadataJSONB speichern - Comments → Neue Tabelle, aber Stories bleiben unverändert
- Analytics → Seperate Tabelle mit Foreign Keys
Migrations-Strategie
-- migrations/001_initial_central_stories.sql
-- Phase 1 Changes
-- migrations/002_add_voting.sql
-- Phase 2 Changes
-- migrations/003_character_sharing.sql
-- Phase 3 Changes
Vorteile dieser Lösung
✅ Keine Breaking Changes - Jede Phase baut auf der vorherigen auf
✅ Collections von Anfang an - Zentrale Abstraktion für alle Gruppierungen
✅ JSONB metadata - Flexibilität ohne Schema-Änderungen
✅ Richtiges Naming - visibility statt is_central, skaliert besser
✅ Indices von Anfang an - Performance bleibt gut bei Wachstum
Start-Kommandos (Phase 1)
# 1. Migration ausführen
supabase migration new central_stories_foundation
# Code von oben einfügen
supabase db push
# 2. Erste zentrale Stories erstellen
npm run seed:central-stories
# 3. App Update
npm run deploy
Seed Script für erste Stories
// scripts/seed-central-stories.js
const createCentralStory = async (title, text, pages) => {
const { data: story } = await supabase
.from('stories')
.insert({
user_id: 'SYSTEM',
title,
story_text: text,
visibility: 'central',
published_at: new Date(),
published_by: 'system',
pages_data: pages,
metadata: {
version: 1,
language: 'de',
age_group: '4-8',
},
})
.select()
.single();
// In Collection einfügen
const { data: collection } = await supabase
.from('story_collections')
.select('id')
.eq('slug', 'central-stories')
.single();
await supabase.from('collection_stories').insert({
collection_id: collection.id,
story_id: story.id,
position: 1,
});
};
Zeitplan
Woche 1: Phase 1 implementieren, 5-10 zentrale Stories Woche 2: App Release, Feedback sammeln Woche 3: Phase 2 (Voting) entwickeln Woche 4: Phase 3 (Characters) entwickeln Monat 2: Phase 4-5 je nach Priorität
Diese Struktur wächst organisch ohne Breaking Changes!