Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions apps/web/app/api/space/feed/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';

import type { BrowseSidebarData } from '~/core/browse/fetch-browse-sidebar-data';
import { resolveMemberSpaceFromWalletSafe } from '~/core/browse/resolve-member-space-from-wallet';
import { WALLET_ADDRESS } from '~/core/cookie';
import { type ExploreSort, type ExploreTime, fetchExploreFeed } from '~/core/explore/fetch-explore-feed';

import { getGovernanceHomeSpaceContext } from '~/app/home/governance-home-space-ids';

const SORTS: ExploreSort[] = ['new', 'top'];
const TIMES: ExploreTime[] = ['today', 'week', 'month', 'year', 'all'];

function parseSort(raw: string | null): ExploreSort {
if (raw && (SORTS as string[]).includes(raw)) return raw as ExploreSort;
return 'new';
}

function parseTime(raw: string | null): ExploreTime {
if (raw && (TIMES as string[]).includes(raw)) return raw as ExploreTime;
return 'week';
}

/**
* Generic type-scoped feed for a single space. Reuses the explore fetcher pinned to one
* space (via a synthesized single-space browse, so it works for spaces the viewer is not a
* member of) and restricted to the requested entity types. Powers the space-home content
* sections (News stories, and later debates / claims) — each section passes its own `types`.
*/
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const sort = parseSort(searchParams.get('sort'));
const time = parseTime(searchParams.get('time'));
const spaceId = searchParams.get('spaceId');
const cursor = searchParams.get('cursor');

const parsedTypes = (searchParams.get('types') ?? '')
.split(',')
.map(t => t.trim())
.filter(Boolean);
const typeIds = parsedTypes.length > 0 ? parsedTypes : undefined;

if (!spaceId) {
return NextResponse.json({ items: [], nextCursor: null });
}

const cookieWallet = (await cookies()).get(WALLET_ADDRESS)?.value;

let personalMemberSpaceId: string | null = null;
let memberOrEditorSpaceIds: string[] = [];

if (cookieWallet) {
personalMemberSpaceId = await resolveMemberSpaceFromWalletSafe(cookieWallet);
if (personalMemberSpaceId) {
try {
const ctx = await getGovernanceHomeSpaceContext(personalMemberSpaceId);
memberOrEditorSpaceIds = [...new Set([...ctx.editorIds, ...ctx.myProposalSpaceIds, personalMemberSpaceId])];
} catch {
memberOrEditorSpaceIds = [personalMemberSpaceId];
}
}
}

// Pinned to a single space passed in from the URL, so we don't need the real browse
// sidebar data to build the allowed-space set — synthesize a minimal one. This also lets
// the feed work for spaces the viewer is not a member of (a space home must render for
// anyone), unlike the explore route which matches against the viewer's own browse spaces.
const browse: BrowseSidebarData = {
featured: [{ id: spaceId, name: '', image: null }],
editorOf: [],
memberOf: [],
documentationImage: null,
personalSpaceId: null,
};

try {
const result = await fetchExploreFeed({
browse,
sort,
time,
spaceFilterId: spaceId,
cursor,
walletAddress: cookieWallet ?? null,
memberOrEditorSpaceIds,
typeIds,
requireName: true,
});
return NextResponse.json(result);
} catch (e) {
console.error('space feed', e);
/** Degraded response so the space home still mounts when GraphQL is down; client shows empty feed. */
return NextResponse.json({ items: [], nextCursor: null });
}
}
3 changes: 0 additions & 3 deletions apps/web/app/space/[id]/(space)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import { PersonalProfileBioStarterMerge } from '~/partials/entity-page/personal-
import { PersonalProfileSuggestedCard } from '~/partials/entity-page/personal-profile-suggested-card';
import { PersonalProfileSuggestedTaskSync } from '~/partials/entity-page/personal-profile-suggested-task-sync';
import { TypeSchemaInline } from '~/partials/entity-page/type-schema-inline';
import { AddDataPanel } from '~/partials/space-page/add-data-panel';
import { SpaceEditors } from '~/partials/space-page/space-editors';
import { SpaceMembers } from '~/partials/space-page/space-members';
import { SpacePageMetadataHeader } from '~/partials/space-page/space-metadata-header';
Expand Down Expand Up @@ -82,8 +81,6 @@ export default async function Layout(props0: LayoutProps) {
</div>

<div className="mt-6 flex flex-col gap-6">
<AddDataPanel spaceId={spaceId} />

{typeIds.includes(SystemIds.PERSON_TYPE) ? (
<>
<PersonalProfileBioStarterMerge entityId={props.id} spaceId={spaceId} />
Expand Down
5 changes: 5 additions & 0 deletions apps/web/app/space/[id]/(space)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Spacer } from '~/design-system/spacer';
import { Editor } from '~/partials/editor/editor';
import { BacklinksServerContainer } from '~/partials/entity-page/backlinks-server-container';
import { ToggleEntityPage } from '~/partials/entity-page/toggle-entity-page';
import { NewsStoriesSection } from '~/partials/space-page/news-stories/news-stories-section';
import { SubtopicGallery } from '~/partials/space-page/subtopic-gallery';

import { cachedFetchEntitiesBatch, cachedFetchEntityPage } from '../../(entity)/[id]/[entityId]/cached-fetch-entity';
Expand Down Expand Up @@ -77,6 +78,8 @@ export default async function SpacePage(props0: Props) {

return (
<>
<NewsStoriesSection spaceId={spaceId} />
<Spacer height={40} />
<React.Suspense fallback={<SubtopicGallerySkeleton />}>
<SubtopicGalleryContainer spaceId={params.id} />
</React.Suspense>
Expand Down Expand Up @@ -113,6 +116,8 @@ async function TopicEntityBody({ spaceId, topicEntityId }: { spaceId: string; to
initialTabs={topic.tabs}
initialCollectionItems={topic.initialCollectionItems}
>
<NewsStoriesSection spaceId={spaceId} />
<Spacer height={40} />
<React.Suspense fallback={<SubtopicGallerySkeleton />}>
<SubtopicGalleryContainer spaceId={spaceId} />
</React.Suspense>
Expand Down
Loading
Loading