Konzepte, Nutzen und typische Clone-Szenarien — weniger Code.
Konzepte, Nutzen und typische Clone-Szenarien — weniger Code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
REST für den Marktplatz mit doppelter Natur. Die Browse-Liste ist für Besucher offen; alles andere erfordert eine Sitzung. Die Browse-Liste (GET /api/opportunities) ist eine rollenbewusste Cursor-Paginierung plus Anreicherung pro Betrachter. Die Suche ist eine eigene Route. Like / Save / Hide / Contact sind Server Actions, keine HTTP-Ressourcen.
Nutze die Tabs Founder / Developer, um diese Seite zu filtern. Produktkreislauf und Karten-UX: Opportunities.
Ältere API-Texte nannten feste "7 Typen". Laufzeittypen sind die Union OpportunityType in features/opportunities/types/index.ts, begrenzt durch ring-config.json → opportunities.enabledTypes. Dokumentiere keine geschlossene Siebenermenge.
| Methode | Route | Authentifizierung | Hinweise |
|---|---|---|---|
GET | /api/opportunities | Optional | limit, startAfter. Die Filterparameter types/type, q/query, categories/category, location, priority schalten auf den Suchdienst um. Übergibt viewerUserId, wenn eine Sitzung besteht. Liefert { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sitzung | Body createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sitzung | Ein Eintrag |
PUT / PATCH | /api/opportunities/{id} | Sitzung | Aktualisieren |
DELETE | /api/opportunities/{id} | Sitzung | Löschen |
GET | /api/opportunities/search | Sitzung | Erfordert q. Optional types, categories, location, limit |
POST | /api/opportunities/search | Sitzung | JSON-Body SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sitzung | Beratende Petitionsunterschrift, eine pro Nutzer. Liefert signatureCount, signatureTarget |
GET | /api/opportunities/my | Sitzung | Liste des Eigentümers; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sitzung | Anhänge |
GET / POST | /api/mcp/v1/opportunities | MCP-Guard | Die Liste übergibt heute kein viewerUserId |
Seit 2026-10-17 liefert ein nicht authentifiziertes GET /api/opportunities öffentliche, nicht vertrauliche Zeilen für die Rolle visitor. Der Besucherfilter greift nach dem Abruf der Seite, daher kann eine Gastseite weniger als limit Zeilen haben. Detail, Suche und Änderungen liefern ohne Sitzung weiterhin 401.
/opportunities lädt GET /api/opportunities (eine Seite mit Einträgen). Filter im rechten Panel werden als Abfrageparameter gesendet und auf dem Server angewendet. Besucher können ohne Konto browsen.creator-Snapshot an, sodass die UI nie eine rohe UUID zeigt.createOpportunity (app/_actions/opportunities.ts). REST-POST ist für API- und MCP-Clients; beide rufen denselben Service auf.public | subscriber | member | confidential | entity. entity-Zeilen sind nur für Admins, den Autor und Mitglieder der verknüpften Entity lesbar. Listenabfragen wenden Sichtbarkeitsfilter nach Rolle an (buildOpportunityVisibilityFilters)./apply).Ja, nur öffentliche, nicht vertrauliche Zeilen. GET /api/opportunities/{id} und die Suche erfordern weiterhin eine Sitzung.
Ja, wenn searchOpportunities die Nutzer-ID der Sitzung (oder des MCP-Akteurs) hat. Die HTTP-Suchrouten GET/POST erfordern eine Sitzung.
createdBy weg?Nein. Es ist weiterhin die Nutzer-ID. creator ist ein optionaler öffentlicher Snapshot für die UI.
Die MCP-Liste lässt viewerUserId weg, daher greift attachOpportunityFeedFields auf diesem Pfad nicht.
Voraussetzung: Produktkreislauf, Aktionen der Feed-Karten und Grenzen der aktivierten Typen.
Hängt ab von: Cache-Tags, revalidatePath und Tunnel opportunity:* nach Änderungen.
Gleicher Ablauf: Umwandeln in ein Gesuch schreibt metadata.source, das die Listenkarten lesen.
Gleicher Ablauf: Die Verknüpfung mit der Organisation ist beim Erstellen Pflicht (`organizationId` / linkedEntity).
REST für den Marktplatz mit doppelter Natur. Die Browse-Liste ist für Besucher offen; alles andere erfordert eine Sitzung. Die Browse-Liste (GET /api/opportunities) ist eine rollenbewusste Cursor-Paginierung plus Anreicherung pro Betrachter. Die Suche ist eine eigene Route. Like / Save / Hide / Contact sind Server Actions, keine HTTP-Ressourcen.
Nutze die Tabs Founder / Developer, um diese Seite zu filtern. Produktkreislauf und Karten-UX: Opportunities.
Ältere API-Texte nannten feste "7 Typen". Laufzeittypen sind die Union OpportunityType in features/opportunities/types/index.ts, begrenzt durch ring-config.json → opportunities.enabledTypes. Dokumentiere keine geschlossene Siebenermenge.
| Methode | Route | Authentifizierung | Hinweise |
|---|---|---|---|
GET | /api/opportunities | Optional | limit, startAfter. Die Filterparameter types/type, q/query, categories/category, location, priority schalten auf den Suchdienst um. Übergibt viewerUserId, wenn eine Sitzung besteht. Liefert { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sitzung | Body createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sitzung | Ein Eintrag |
PUT / PATCH | /api/opportunities/{id} | Sitzung | Aktualisieren |
DELETE | /api/opportunities/{id} | Sitzung | Löschen |
GET | /api/opportunities/search | Sitzung | Erfordert q. Optional types, categories, location, limit |
POST | /api/opportunities/search | Sitzung | JSON-Body SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sitzung | Beratende Petitionsunterschrift, eine pro Nutzer. Liefert signatureCount, signatureTarget |
GET | /api/opportunities/my | Sitzung | Liste des Eigentümers; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sitzung | Anhänge |
GET / POST | /api/mcp/v1/opportunities | MCP-Guard | Die Liste übergibt heute kein viewerUserId |
Seit 2026-10-17 liefert ein nicht authentifiziertes GET /api/opportunities öffentliche, nicht vertrauliche Zeilen für die Rolle visitor. Der Besucherfilter greift nach dem Abruf der Seite, daher kann eine Gastseite weniger als limit Zeilen haben. Detail, Suche und Änderungen liefern ohne Sitzung weiterhin 401.
/opportunities lädt GET /api/opportunities (eine Seite mit Einträgen). Filter im rechten Panel werden als Abfrageparameter gesendet und auf dem Server angewendet. Besucher können ohne Konto browsen.creator-Snapshot an, sodass die UI nie eine rohe UUID zeigt.createOpportunity (app/_actions/opportunities.ts). REST-POST ist für API- und MCP-Clients; beide rufen denselben Service auf.public | subscriber | member | confidential | entity. entity-Zeilen sind nur für Admins, den Autor und Mitglieder der verknüpften Entity lesbar. Listenabfragen wenden Sichtbarkeitsfilter nach Rolle an (buildOpportunityVisibilityFilters)./apply).Ja, nur öffentliche, nicht vertrauliche Zeilen. GET /api/opportunities/{id} und die Suche erfordern weiterhin eine Sitzung.
Ja, wenn searchOpportunities die Nutzer-ID der Sitzung (oder des MCP-Akteurs) hat. Die HTTP-Suchrouten GET/POST erfordern eine Sitzung.
createdBy weg?Nein. Es ist weiterhin die Nutzer-ID. creator ist ein optionaler öffentlicher Snapshot für die UI.
Die MCP-Liste lässt viewerUserId weg, daher greift attachOpportunityFeedFields auf diesem Pfad nicht.
Voraussetzung: Produktkreislauf, Aktionen der Feed-Karten und Grenzen der aktivierten Typen.
Hängt ab von: Cache-Tags, revalidatePath und Tunnel opportunity:* nach Änderungen.
Gleicher Ablauf: Umwandeln in ein Gesuch schreibt metadata.source, das die Listenkarten lesen.
Gleicher Ablauf: Die Verknüpfung mit der Organisation ist beim Erstellen Pflicht (`organizationId` / linkedEntity).
REST für den Marktplatz mit doppelter Natur. Die Browse-Liste ist für Besucher offen; alles andere erfordert eine Sitzung. Die Browse-Liste (GET /api/opportunities) ist eine rollenbewusste Cursor-Paginierung plus Anreicherung pro Betrachter. Die Suche ist eine eigene Route. Like / Save / Hide / Contact sind Server Actions, keine HTTP-Ressourcen.
Nutze die Tabs Founder / Developer, um diese Seite zu filtern. Produktkreislauf und Karten-UX: Opportunities.
Ältere API-Texte nannten feste "7 Typen". Laufzeittypen sind die Union OpportunityType in features/opportunities/types/index.ts, begrenzt durch ring-config.json → opportunities.enabledTypes. Dokumentiere keine geschlossene Siebenermenge.
| Methode | Route | Authentifizierung | Hinweise |
|---|---|---|---|
GET | /api/opportunities | Optional | limit, startAfter. Die Filterparameter types/type, q/query, categories/category, location, priority schalten auf den Suchdienst um. Übergibt viewerUserId, wenn eine Sitzung besteht. Liefert { opportunities, items, lastVisible, cursor, hasMore } |
POST | /api/opportunities | Sitzung | Body createOpportunityBodySchema → { opportunity } 201 |
GET | /api/opportunities/{id} | Sitzung | Ein Eintrag |
PUT / PATCH | /api/opportunities/{id} | Sitzung | Aktualisieren |
DELETE | /api/opportunities/{id} | Sitzung | Löschen |
GET | /api/opportunities/search | Sitzung | Erfordert q. Optional types, categories, location, limit |
POST | /api/opportunities/search | Sitzung | JSON-Body SearchOpportunitiesParams |
POST | /api/opportunities/{id}/sign | Sitzung | Beratende Petitionsunterschrift, eine pro Nutzer. Liefert signatureCount, signatureTarget |
GET | /api/opportunities/my | Sitzung | Liste des Eigentümers; view= all | drafts | pending | active | archived |
POST | /api/opportunities/upload | Sitzung | Anhänge |
GET / POST | /api/mcp/v1/opportunities | MCP-Guard | Die Liste übergibt heute kein viewerUserId |
Seit 2026-10-17 liefert ein nicht authentifiziertes GET /api/opportunities öffentliche, nicht vertrauliche Zeilen für die Rolle visitor. Der Besucherfilter greift nach dem Abruf der Seite, daher kann eine Gastseite weniger als limit Zeilen haben. Detail, Suche und Änderungen liefern ohne Sitzung weiterhin 401.
/opportunities lädt GET /api/opportunities (eine Seite mit Einträgen). Filter im rechten Panel werden als Abfrageparameter gesendet und auf dem Server angewendet. Besucher können ohne Konto browsen.creator-Snapshot an, sodass die UI nie eine rohe UUID zeigt.createOpportunity (app/_actions/opportunities.ts). REST-POST ist für API- und MCP-Clients; beide rufen denselben Service auf.public | subscriber | member | confidential | entity. entity-Zeilen sind nur für Admins, den Autor und Mitglieder der verknüpften Entity lesbar. Listenabfragen wenden Sichtbarkeitsfilter nach Rolle an (buildOpportunityVisibilityFilters)./apply).Ja, nur öffentliche, nicht vertrauliche Zeilen. GET /api/opportunities/{id} und die Suche erfordern weiterhin eine Sitzung.
Ja, wenn searchOpportunities die Nutzer-ID der Sitzung (oder des MCP-Akteurs) hat. Die HTTP-Suchrouten GET/POST erfordern eine Sitzung.
createdBy weg?Nein. Es ist weiterhin die Nutzer-ID. creator ist ein optionaler öffentlicher Snapshot für die UI.
Die MCP-Liste lässt viewerUserId weg, daher greift attachOpportunityFeedFields auf diesem Pfad nicht.
Voraussetzung: Produktkreislauf, Aktionen der Feed-Karten und Grenzen der aktivierten Typen.
Hängt ab von: Cache-Tags, revalidatePath und Tunnel opportunity:* nach Änderungen.
Gleicher Ablauf: Umwandeln in ein Gesuch schreibt metadata.source, das die Listenkarten lesen.
Gleicher Ablauf: Die Verknüpfung mit der Organisation ist beim Erstellen Pflicht (`organizationId` / linkedEntity).
creator { id, name, avatar }| Aktion | Modul |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Kommentar | createOpportunityComment |
| Kontaktabsicht | recordOpportunityContactIntent (Matcher; die UI macht weiterhin router.push zu /messages?user=) |
Datei: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() in features/opportunities/lib/opportunity-db-mapper.ts. Nutze lib/converters/opportunity-converter.ts nicht auf Postgres.
Der Operator 'not-in' steht in IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). Der Firebase-Adapter filtert nachträglich.
Erstellen, Aktualisieren, Löschen und Statusänderungen rufen syncOpportunityDiscovery() auf, das:
revalidateTag: invalidiert die Opportunity-Listen von unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted auf dem Kanal opportunitiesEs gibt keinen separaten Befehl zum Neuindexieren der Suche. Siehe Discovery mutation sync. Das Browsen im Client nutzt weiterhin useCursorFeed (24 h); revalidatePath allein leert diesen Cache nicht.
Erstellen in der UI: useActionState + createOpportunity aus app/_actions/opportunities.ts, siehe API integration.
Nächster Schritt: curl mit einem lokalen Sitzungs-Cookie.
creator { id, name, avatar }| Aktion | Modul |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Kommentar | createOpportunityComment |
| Kontaktabsicht | recordOpportunityContactIntent (Matcher; die UI macht weiterhin router.push zu /messages?user=) |
Datei: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() in features/opportunities/lib/opportunity-db-mapper.ts. Nutze lib/converters/opportunity-converter.ts nicht auf Postgres.
Der Operator 'not-in' steht in IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). Der Firebase-Adapter filtert nachträglich.
Erstellen, Aktualisieren, Löschen und Statusänderungen rufen syncOpportunityDiscovery() auf, das:
revalidateTag: invalidiert die Opportunity-Listen von unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted auf dem Kanal opportunitiesEs gibt keinen separaten Befehl zum Neuindexieren der Suche. Siehe Discovery mutation sync. Das Browsen im Client nutzt weiterhin useCursorFeed (24 h); revalidatePath allein leert diesen Cache nicht.
Erstellen in der UI: useActionState + createOpportunity aus app/_actions/opportunities.ts, siehe API integration.
Nächster Schritt: curl mit einem lokalen Sitzungs-Cookie.
creator { id, name, avatar }| Aktion | Modul |
|---|---|
| Like | toggleOpportunityLike → like-service / likes |
| Save | toggleOpportunitySave → user_content_interactions |
| Hide | markOpportunityNotInterested → user_content_interactions |
| Kommentar | createOpportunityComment |
| Kontaktabsicht | recordOpportunityContactIntent (Matcher; die UI macht weiterhin router.push zu /messages?user=) |
Datei: app/_actions/opportunity-interactions.ts.
not-inmapDbQueryRowToSerializedOpportunity() in features/opportunities/lib/opportunity-db-mapper.ts. Nutze lib/converters/opportunity-converter.ts nicht auf Postgres.
Der Operator 'not-in' steht in IDatabaseService DatabaseFilterOperator. PostgreSQL: NOT (field = ANY($n)). Der Firebase-Adapter filtert nachträglich.
Erstellen, Aktualisieren, Löschen und Statusänderungen rufen syncOpportunityDiscovery() auf, das:
revalidateTag: invalidiert die Opportunity-Listen von unstable_cache (lib/cached-data.ts)revalidatePath: /[locale]/opportunities, /[locale]/opportunities/[id], /[locale]/opportunities/myopportunity:created|updated|deleted auf dem Kanal opportunitiesEs gibt keinen separaten Befehl zum Neuindexieren der Suche. Siehe Discovery mutation sync. Das Browsen im Client nutzt weiterhin useCursorFeed (24 h); revalidatePath allein leert diesen Cache nicht.
Erstellen in der UI: useActionState + createOpportunity aus app/_actions/opportunities.ts, siehe API integration.
Nächster Schritt: curl mit einem lokalen Sitzungs-Cookie.