11import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler" ;
2+ import { getOrCreateFeaturebaseUserFromAuth , requireFeaturebaseApiKey } from "@/lib/featurebase" ;
23import { adaptSchema , yupArray , yupBoolean , yupNumber , yupObject , yupString } from "@stackframe/stack-shared/dist/schema-fields" ;
3- import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env" ;
44import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors" ;
5- import { getOrCreateFeaturebaseUser } from "@stackframe/stack-shared/dist/utils/featurebase" ;
65
7- const STACK_FEATUREBASE_API_KEY = getEnvVariable ( "STACK_FEATUREBASE_API_KEY" , "" ) ;
6+ // Typed subset of the Featurebase v2 API responses; fields we don't use are omitted.
7+ // The response schema validated by yup on output acts as the runtime safety net.
8+ type FeaturebasePost = {
9+ id : string ,
10+ title : string ,
11+ content : string | null ,
12+ upvotes : number ,
13+ date : string ,
14+ mergedToSubmissionId : string | null ,
15+ postStatus : { name : string , color : string , type : string } | null ,
16+ } ;
17+
18+ type FeaturebaseUpvoter = {
19+ userId : string ,
20+ } ;
21+
22+ type FeaturebaseListResponse < T > = {
23+ results ?: T [ ] ,
24+ error ?: string ,
25+ } ;
826
927// GET /api/latest/internal/feature-requests
1028export const GET = createSmartRouteHandler ( {
@@ -43,27 +61,18 @@ export const GET = createSmartRouteHandler({
4361 } ) . defined ( ) ,
4462 } ) ,
4563 handler : async ( { auth } ) => {
46- if ( ! STACK_FEATUREBASE_API_KEY ) {
47- throw new StackAssertionError ( "STACK_FEATUREBASE_API_KEY environment variable is not set" ) ;
48- }
49-
50- // Get or create Featurebase user for consistent email handling
51- const featurebaseUser = await getOrCreateFeaturebaseUser ( {
52- id : auth . user . id ,
53- primaryEmail : auth . user . primary_email ,
54- displayName : auth . user . display_name ,
55- profileImageUrl : auth . user . profile_image_url ,
56- } ) ;
64+ const featurebaseApiKey = requireFeaturebaseApiKey ( ) ;
65+ const featurebaseUser = await getOrCreateFeaturebaseUserFromAuth ( auth . user ) ;
5766
5867 // Fetch all posts with sorting
5968 const response = await fetch ( 'https://do.featurebase.app/v2/posts?limit=50&sortBy=upvotes:desc' , {
6069 method : 'GET' ,
6170 headers : {
62- 'X-API-Key' : STACK_FEATUREBASE_API_KEY ,
71+ 'X-API-Key' : featurebaseApiKey ,
6372 } ,
6473 } ) ;
6574
66- const data = await response . json ( ) ;
75+ const data : FeaturebaseListResponse < FeaturebasePost > = await response . json ( ) ;
6776
6877 if ( ! response . ok ) {
6978 throw new StackAssertionError ( `Featurebase API error: ${ data . error || 'Failed to fetch feature requests' } ` , {
@@ -74,30 +83,28 @@ export const GET = createSmartRouteHandler({
7483 } ) ;
7584 }
7685
77- const posts = data . results || [ ] ;
86+ const posts = data . results ?? [ ] ;
7887
79- // Filter out posts that have been merged into other posts or are completed
80- const activePosts = posts . filter ( ( post : any ) =>
88+ const activePosts = posts . filter ( ( post ) =>
8189 ! post . mergedToSubmissionId &&
8290 post . postStatus ?. type !== 'completed'
8391 ) ;
8492
85- // Check upvote status for each post for the current user using Featurebase email
8693 const postsWithUpvoteStatus = await Promise . all (
87- activePosts . map ( async ( post : any ) => {
94+ activePosts . map ( async ( post ) => {
8895 let userHasUpvoted = false ;
8996
9097 const upvoteResponse = await fetch ( `https://do.featurebase.app/v2/posts/upvoters?submissionId=${ post . id } ` , {
9198 method : 'GET' ,
9299 headers : {
93- 'X-API-Key' : STACK_FEATUREBASE_API_KEY ,
100+ 'X-API-Key' : featurebaseApiKey ,
94101 } ,
95102 } ) ;
96103
97104 if ( upvoteResponse . ok ) {
98- const upvoteData = await upvoteResponse . json ( ) ;
99- const upvoters = upvoteData . results || [ ] ;
100- userHasUpvoted = upvoters . some ( ( upvoter : any ) =>
105+ const upvoteData : FeaturebaseListResponse < FeaturebaseUpvoter > = await upvoteResponse . json ( ) ;
106+ const upvoters = upvoteData . results ?? [ ] ;
107+ userHasUpvoted = upvoters . some ( ( upvoter ) =>
101108 upvoter . userId === featurebaseUser . userId
102109 ) ;
103110 }
@@ -156,17 +163,8 @@ export const POST = createSmartRouteHandler({
156163 } ) . defined ( ) ,
157164 } ) ,
158165 handler : async ( { auth, body } ) => {
159- if ( ! STACK_FEATUREBASE_API_KEY ) {
160- throw new StackAssertionError ( "STACK_FEATUREBASE_API_KEY environment variable is not set" ) ;
161- }
162-
163- // Get or create Featurebase user for consistent email handling
164- const featurebaseUser = await getOrCreateFeaturebaseUser ( {
165- id : auth . user . id ,
166- primaryEmail : auth . user . primary_email ,
167- displayName : auth . user . display_name ,
168- profileImageUrl : auth . user . profile_image_url ,
169- } ) ;
166+ const featurebaseApiKey = requireFeaturebaseApiKey ( ) ;
167+ const featurebaseUser = await getOrCreateFeaturebaseUserFromAuth ( auth . user ) ;
170168
171169 const featurebaseRequestBody = {
172170 title : body . title ,
@@ -189,7 +187,7 @@ export const POST = createSmartRouteHandler({
189187 method : 'POST' ,
190188 headers : {
191189 'Content-Type' : 'application/json' ,
192- 'X-API-Key' : STACK_FEATUREBASE_API_KEY ,
190+ 'X-API-Key' : featurebaseApiKey ,
193191 } ,
194192 body : JSON . stringify ( featurebaseRequestBody ) ,
195193 } ) ;
0 commit comments