Skip to content
Draft
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
2 changes: 1 addition & 1 deletion app/api/community/votes/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { NextRequest, NextResponse } from 'next/server';
import { devLog } from '@/lib/devLogger';
import { adminDb } from '@/lib/firebase-admin';
import { calculateKarmaForAction } from '@/lib/firestore/communityHelpers';

Check warning on line 4 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

'calculateKarmaForAction' is defined but never used
import { verifyUserRequest, resolveOwnedUserId } from '@/lib/userApiAuth';

export const dynamic = 'force-static'
export const dynamic = 'force-dynamic';

interface VoteData {
userId: string;
Expand Down Expand Up @@ -73,7 +73,7 @@

const existingVotes = await voteQuery.get();

let existingVote: any = null;

Check warning on line 76 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

Unexpected any. Specify a different type
let existingVoteId: string | null = null;

if (!existingVotes.empty) {
Expand All @@ -87,7 +87,7 @@
await db.collection('communityVotes').doc(existingVoteId!).delete();

// Update vote counts (decrement)
const updateField = isDiscussion ? 'discussions' : 'comments';

Check warning on line 90 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

'updateField' is assigned a value but never used
await updateVoteCounts(db, isDiscussion, targetId, voteType === 'up' ? -1 : 0, voteType === 'down' ? -1 : 0);

// Update karma for original author (undo previous karma change)
Expand Down Expand Up @@ -231,7 +231,7 @@
} else {
return NextResponse.json({ error: 'Client-side not supported for this endpoint' }, { status: 400 });
}
} catch (error: any) {

Check warning on line 234 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

Unexpected any. Specify a different type
devLog.error('Error recording vote:', error, 'route');
return NextResponse.json({ error: error.message || 'Failed to record vote' }, { status: 500 });
}
Expand All @@ -239,7 +239,7 @@

// Helper function to update vote counts on discussion or comment
async function updateVoteCounts(
db: any,

Check warning on line 242 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

Unexpected any. Specify a different type
isDiscussion: boolean,
targetId: string,
upvoteDelta: number,
Expand Down Expand Up @@ -279,7 +279,7 @@
}

// Helper function to update author karma
async function updateAuthorKarma(db: any, authorId: string, karmaDelta: number, additionalDelta: number = 0) {

Check warning on line 282 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

Unexpected any. Specify a different type
try {
const memberRef = db.collection('communityMembers').doc(authorId);
const memberDoc = await memberRef.get();
Expand Down Expand Up @@ -354,7 +354,7 @@
} else {
return NextResponse.json({ error: 'Client-side not supported for this endpoint' }, { status: 400 });
}
} catch (error: any) {

Check warning on line 357 in app/api/community/votes/route.ts

View workflow job for this annotation

GitHub Actions / Lint + Jest

Unexpected any. Specify a different type
devLog.error('Error checking vote:', error, 'route');
return NextResponse.json({ error: error.message || 'Failed to check vote' }, { status: 500 });
}
Expand Down
7 changes: 4 additions & 3 deletions app/community/attribution/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import { useToast } from '@/components/ui/use-toast';
import { getReturningPaymentCommitDestination } from '@/lib/authRouting';
import { analytics } from '@/lib/analytics';
import { fetchWithFirebaseAuthRequired } from '@/lib/clientFirebaseFetch';
interface UserContribution {
id: string;
type: 'feedback' | 'suggestion' | 'bug-report' | 'feature-request';
Expand Down Expand Up @@ -311,7 +312,7 @@
// Load user votes in background so we don't block first paint
const votePromises = discussionsData.map(async (d: Record<string, unknown>) => {
try {
const voteResponse = await fetch(`/api/community/votes?userId=${uid}&discussionId=${String(d.id ?? '')}`);
const voteResponse = await fetchWithFirebaseAuthRequired(`/api/community/votes?userId=${uid}&discussionId=${String(d.id ?? '')}`);
if (!voteResponse.ok) return null;
const voteData = await voteResponse.json();
if (voteData.success && voteData.hasVoted) {
Expand Down Expand Up @@ -643,7 +644,7 @@
});

// Send vote to API
const response = await fetch('/api/community/votes', {
const response = await fetchWithFirebaseAuthRequired('/api/community/votes', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand Down Expand Up @@ -700,7 +701,7 @@
}

try {
const response = await fetch('/api/community/discussions', {
const response = await fetchWithFirebaseAuthRequired('/api/community/discussions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
Expand Down Expand Up @@ -1270,7 +1271,7 @@
)}
</div>
) : (
<p className="text-sm text-slate-700">Pick any discussion and tap "Open thread" to read and reply in one flow.</p>

Check warning on line 1274 in app/community/attribution/page.tsx

View workflow job for this annotation

GitHub Actions / Lint + Jest

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`

Check warning on line 1274 in app/community/attribution/page.tsx

View workflow job for this annotation

GitHub Actions / Lint + Jest

`"` can be escaped with `&quot;`, `&ldquo;`, `&#34;`, `&rdquo;`
)}
</CardContent>
</Card>
Expand Down
35 changes: 35 additions & 0 deletions tests/unit/community-signed-in-writes-auth.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';

/**
* Signed-in community discussion create and votes require verifyUserRequest.
* The community page must send a Firebase Bearer token; guest discussion POST
* stays captcha-gated without auth.
*/
describe('community signed-in writes send Firebase auth', () => {
const pageSrc = readFileSync(
join(process.cwd(), 'app/community/attribution/page.tsx'),
'utf8',
);
const votesRouteSrc = readFileSync(
join(process.cwd(), 'app/api/community/votes/route.ts'),
'utf8',
);

it('sends a Bearer token for signed-in discussion create', () => {
expect(pageSrc).toContain("fetchWithFirebaseAuthRequired('/api/community/discussions'");
expect(pageSrc).toContain('guestPost: true');
const guestBlock = pageSrc.slice(pageSrc.indexOf('handleCreateGuestDiscussion'));
expect(guestBlock).toMatch(/await fetch\('\/api\/community\/discussions'/);
});

it('sends a Bearer token for vote reads and writes', () => {
expect(pageSrc).toContain('fetchWithFirebaseAuthRequired(`/api/community/votes?userId=');
expect(pageSrc).toContain("fetchWithFirebaseAuthRequired('/api/community/votes'");
});

it('renders the votes route dynamically so Authorization is available', () => {
expect(votesRouteSrc).toContain("export const dynamic = 'force-dynamic'");
expect(votesRouteSrc).not.toMatch(/export const dynamic = 'force-static'/);
});
});
Loading