-
Notifications
You must be signed in to change notification settings - Fork 498
Feature/stack companion #769
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
77 commits
Select commit
Hold shift + click to select a range
7202731
WIP: SSO and feature request board implementation
madster456 8f4e0e9
GitButler Workspace Commit
gitbutler-client dde4fc1
Implement user ID tracking for feature requests and fix linter warnings
madster456 969c193
Merge origin/dev into gitbutler/workspace
madster456 2b65e17
Update pnpm-lock.yaml after merge
madster456 1de8099
Remove mock data from changelog widget
madster456 210a90a
fix typescript errors with scrollbarWidth property
madster456 6295775
Merge dev into feature/stack-companion
N2D4 75d2763
remove old envvars from dashboard, setup new envvar in backend
madster456 4cff413
added relative back to header, removed commented out import
madster456 58b2430
feature-request-board updates for backend functionality
madster456 5474b51
imports keep reorganizing on save..... very annoying cannot find the …
madster456 27a271c
new routes on backend for feature-requests
madster456 d05e112
remove old route on dashboard, now on backend
madster456 d46cbbb
images from featurebase were blocked
madster456 b435187
Update apps/backend/.env
madster456 2161240
fix linter errors
madster456 c5435c6
remove dangerouslySetInnerHTML, and defined types for feature-request…
madster456 b6314d3
merge dev into feature/stack-companion
madster456 434d604
Merge branch 'dev' into feature/stack-companion
madster456 e3c0f2e
Merge branch 'dev' into pr/madster456/769
N2D4 a720ea2
Merge dev into feature/stack-companion
N2D4 1820b78
Merge dev into feature/stack-companion
N2D4 31bc972
Merge dev into feature/stack-companion
N2D4 5dfbf26
Merge dev into feature/stack-companion
N2D4 0fefd51
add STACK_ to featurebase env vars
madster456 ec4c4e8
add relative back to content body on sidebar-layout
madster456 962d888
add support tab to stack-companion, remove feedback button from top n…
madster456 c920dfa
shared version-check now
madster456 c620548
clean up props
madster456 eb748e7
fix viewport scrolling
madster456 45daff5
update icon in feature-request-board
madster456 e388f02
add color back in
madster456 449d196
active items now close stack-companion when clicked
madster456 ec2c4c5
fix tooltip not showing, and adjust timing
madster456 2334d77
since no interactive docs yet, access docs is now clickable to our ac…
madster456 9a604b9
implement featurebase-utils for managming users
madster456 7257ad4
throw error
madster456 24aa9d2
Not using primary emails as identifier in feature-requests route anymore
madster456 ae36e43
no longer using .then and .catch, rather using runAsynchronously
madster456 2ea4be1
using DOMParser for HTML on feature-request-board
madster456 9a1f05d
remove un-used comments, verified redirect to SSO works
madster456 3406e75
better error handling and user feedback on success/error
madster456 8549fb0
Merge dev into feature/stack-companion
N2D4 b5568b6
remove blocked userId from top-level field
madster456 b1b514f
no longer exporting StackAuthUser from featurebase utils, and updated…
madster456 85693b5
cleanup JSON body
madster456 2519267
naming conventions
madster456 7f7ac5d
add StackAssertionError in two places
madster456 7aafa7c
update authorName default to Stack Auth User
madster456 59ad738
update import to use stack-shared featurebase
madster456 d1d0d6c
remove duplicate code, use stack-shared featurebase
madster456 64d75fa
move featurebase-utils to stack-shared
madster456 f283d99
update user display name default to Stack Auth User
madster456 5b04efc
remove comments and format
madster456 a4d9fff
remove externalLink icon
madster456 0c6afd0
give more space for feature request titles, moving badge down
madster456 46cd29a
Added Stack Auth Logo at top of stack companion
madster456 50491c5
100ms load for tooltip
madster456 95d5f0d
no delay on tooltip hover. This is cleaner
madster456 b7b8b0e
sort by upvote count now
madster456 e16945d
new submit feature button to show/hide form for submission
madster456 f90a133
Merge dev into feature/stack-companion
N2D4 2f9ecb2
Merge branch 'dev' into feature/stack-companion
N2D4 8e931bd
Update user email to be sent with request instead of userID
madster456 7fc50ed
user with no email is now created with default email
madster456 e90950a
Merge branch 'dev' into feature/stack-companion
madster456 318e9b6
fix linter errors, add StackAssertionError
madster456 c5859c4
Merge dev into feature/stack-companion
N2D4 ef773d7
proper error handling
madster456 78834a5
clean up shared functions, proper error handling
madster456 674e0ed
remove unused jsonwebtoken in package.json
madster456 1cb8ea0
Merge remote-tracking branch 'origin/dev' into feature/stack-companion
madster456 7a3c42c
updated pnpm-lock
madster456 31e64f3
remove semicolon
madster456 0592243
better error handling
madster456 8baf5e1
update error handling in feedback form
madster456 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
.../backend/src/app/api/latest/internal/feature-requests/[featureRequestId]/upvote/route.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; | ||
| import { adaptSchema, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; | ||
| import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; | ||
| import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; | ||
| import { getOrCreateFeaturebaseUser } from "@stackframe/stack-shared/dist/utils/featurebase"; | ||
|
|
||
| const STACK_FEATUREBASE_API_KEY = getEnvVariable("STACK_FEATUREBASE_API_KEY"); | ||
|
|
||
| // POST /api/latest/internal/feature-requests/[featureRequestId]/upvote | ||
| export const POST = createSmartRouteHandler({ | ||
| metadata: { | ||
| summary: "Toggle upvote on feature request", | ||
| description: "Toggle upvote on a feature request for the current user", | ||
| tags: ["Internal"], | ||
| }, | ||
| request: yupObject({ | ||
| auth: yupObject({ | ||
| type: adaptSchema, | ||
| user: adaptSchema.defined(), | ||
| project: yupObject({ | ||
| id: yupString().oneOf(["internal"]).defined(), | ||
| }) | ||
| }).defined(), | ||
| params: yupObject({ | ||
| featureRequestId: yupString().defined(), | ||
| }).defined(), | ||
| body: yupObject({}), | ||
| method: yupString().oneOf(["POST"]).defined(), | ||
| }), | ||
| response: yupObject({ | ||
| statusCode: yupNumber().oneOf([200]).defined(), | ||
| bodyType: yupString().oneOf(["json"]).defined(), | ||
| body: yupObject({ | ||
| success: yupBoolean().defined(), | ||
| upvoted: yupBoolean().optional(), | ||
| }).defined(), | ||
| }), | ||
| handler: async ({ auth, params }) => { | ||
| // Get or create Featurebase user for consistent email handling | ||
| const featurebaseUser = await getOrCreateFeaturebaseUser({ | ||
| id: auth.user.id, | ||
| primaryEmail: auth.user.primary_email, | ||
| displayName: auth.user.display_name, | ||
| profileImageUrl: auth.user.profile_image_url, | ||
| }); | ||
|
|
||
| const response = await fetch('https://do.featurebase.app/v2/posts/upvoters', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-API-Key': STACK_FEATUREBASE_API_KEY, | ||
| }, | ||
| body: JSON.stringify({ | ||
| id: params.featureRequestId, | ||
| email: featurebaseUser.email, | ||
| }), | ||
| }); | ||
|
|
||
| let data; | ||
| try { | ||
| data = await response.json(); | ||
| } catch (error) { | ||
| if (error instanceof StackAssertionError) { | ||
| throw error; | ||
| } | ||
| throw new StackAssertionError("Failed to parse Featurebase upvote response", { cause: error }); | ||
| } | ||
|
|
||
| if (!response.ok) { | ||
| throw new StackAssertionError(`Featurebase upvote API error: ${data.error || 'Failed to toggle upvote'}`, { data }); | ||
| } | ||
|
|
||
| return { | ||
| statusCode: 200, | ||
| bodyType: "json" as const, | ||
| body: { success: true, upvoted: data.upvoted }, | ||
| }; | ||
| }, | ||
| }); |
190 changes: 190 additions & 0 deletions
190
apps/backend/src/app/api/latest/internal/feature-requests/route.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,190 @@ | ||
| import { createSmartRouteHandler } from "@/route-handlers/smart-route-handler"; | ||
| import { adaptSchema, yupArray, yupBoolean, yupNumber, yupObject, yupString } from "@stackframe/stack-shared/dist/schema-fields"; | ||
| import { getEnvVariable } from "@stackframe/stack-shared/dist/utils/env"; | ||
| import { StackAssertionError } from "@stackframe/stack-shared/dist/utils/errors"; | ||
| import { getOrCreateFeaturebaseUser } from "@stackframe/stack-shared/dist/utils/featurebase"; | ||
|
|
||
| const STACK_FEATUREBASE_API_KEY = getEnvVariable("STACK_FEATUREBASE_API_KEY"); | ||
|
|
||
| // GET /api/latest/internal/feature-requests | ||
| export const GET = createSmartRouteHandler({ | ||
| metadata: { | ||
| summary: "Get feature requests", | ||
| description: "Fetch all feature requests with upvote status for the current user", | ||
| tags: ["Internal"], | ||
| }, | ||
| request: yupObject({ | ||
| auth: yupObject({ | ||
| type: adaptSchema, | ||
| user: adaptSchema.defined(), | ||
| project: yupObject({ | ||
| id: yupString().oneOf(["internal"]).defined(), | ||
| }).defined(), | ||
| }).defined(), | ||
| query: yupObject({}), | ||
| method: yupString().oneOf(["GET"]).defined(), | ||
| }), | ||
| response: yupObject({ | ||
| statusCode: yupNumber().oneOf([200]).defined(), | ||
| bodyType: yupString().oneOf(["json"]).defined(), | ||
| body: yupObject({ | ||
| posts: yupArray(yupObject({ | ||
| id: yupString().defined(), | ||
| title: yupString().defined(), | ||
| content: yupString().nullable(), | ||
| upvotes: yupNumber().defined(), | ||
| date: yupString().defined(), | ||
| postStatus: yupObject({ | ||
| name: yupString().defined(), | ||
| color: yupString().defined(), | ||
| }).noUnknown(false).nullable(), | ||
| userHasUpvoted: yupBoolean().defined(), | ||
| }).noUnknown(false)).defined(), | ||
| }).defined(), | ||
| }), | ||
| handler: async ({ auth }) => { | ||
| // Get or create Featurebase user for consistent email handling | ||
| const featurebaseUser = await getOrCreateFeaturebaseUser({ | ||
| id: auth.user.id, | ||
| primaryEmail: auth.user.primary_email, | ||
| displayName: auth.user.display_name, | ||
| profileImageUrl: auth.user.profile_image_url, | ||
| }); | ||
|
|
||
| // Fetch all posts with sorting | ||
| const response = await fetch('https://do.featurebase.app/v2/posts?limit=50&sortBy=upvotes:desc', { | ||
| method: 'GET', | ||
| headers: { | ||
| 'X-API-Key': STACK_FEATUREBASE_API_KEY, | ||
| }, | ||
| }); | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Featurebase API error: ${data.error || 'Failed to fetch feature requests'}`); | ||
| } | ||
|
|
||
| const posts = data.results || []; | ||
|
|
||
| // Check upvote status for each post for the current user using Featurebase email | ||
| const postsWithUpvoteStatus = await Promise.all( | ||
madster456 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| posts.map(async (post: any) => { | ||
| let userHasUpvoted = false; | ||
madster456 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const upvoteResponse = await fetch(`https://do.featurebase.app/v2/posts/upvoters?submissionId=${post.id}`, { | ||
| method: 'GET', | ||
| headers: { | ||
| 'X-API-Key': STACK_FEATUREBASE_API_KEY, | ||
| }, | ||
| }); | ||
|
|
||
| if (upvoteResponse.ok) { | ||
| const upvoteData = await upvoteResponse.json(); | ||
| const upvoters = upvoteData.results || []; | ||
| userHasUpvoted = upvoters.some((upvoter: any) => | ||
| upvoter.userId === featurebaseUser.userId | ||
| ); | ||
| } | ||
|
|
||
| return { | ||
| id: post.id, | ||
| title: post.title, | ||
| content: post.content, | ||
| upvotes: post.upvotes || 0, | ||
| date: post.date, | ||
| postStatus: post.postStatus, | ||
| userHasUpvoted, | ||
| }; | ||
| }) | ||
| ); | ||
|
|
||
| return { | ||
| statusCode: 200, | ||
| bodyType: "json" as const, | ||
| body: { posts: postsWithUpvoteStatus }, | ||
| }; | ||
| }, | ||
| }); | ||
|
|
||
| // POST /api/latest/internal/feature-requests | ||
| export const POST = createSmartRouteHandler({ | ||
N2D4 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| metadata: { | ||
| summary: "Create feature request", | ||
| description: "Create a new feature request", | ||
| tags: ["Internal"], | ||
| }, | ||
| request: yupObject({ | ||
| auth: yupObject({ | ||
| type: adaptSchema, | ||
| user: adaptSchema.defined(), | ||
| project: yupObject({ | ||
| id: yupString().oneOf(["internal"]).defined(), | ||
| }).defined(), | ||
| }).defined(), | ||
| body: yupObject({ | ||
| title: yupString().defined(), | ||
| content: yupString().optional(), | ||
| category: yupString().optional(), | ||
| tags: yupArray(yupString()).optional(), | ||
| commentsAllowed: yupBoolean().optional(), | ||
| customInputValues: yupObject().noUnknown(false).optional().nullable(), | ||
| }).defined(), | ||
| method: yupString().oneOf(["POST"]).defined(), | ||
| }), | ||
| response: yupObject({ | ||
| statusCode: yupNumber().oneOf([200]).defined(), | ||
| bodyType: yupString().oneOf(["json"]).defined(), | ||
| body: yupObject({ | ||
| success: yupBoolean().defined(), | ||
| id: yupString().optional(), | ||
| }).defined(), | ||
| }), | ||
| handler: async ({ auth, body }) => { | ||
| // Get or create Featurebase user for consistent email handling | ||
| const featurebaseUser = await getOrCreateFeaturebaseUser({ | ||
| id: auth.user.id, | ||
| primaryEmail: auth.user.primary_email, | ||
| displayName: auth.user.display_name, | ||
| profileImageUrl: auth.user.profile_image_url, | ||
| }); | ||
|
|
||
| const featurebaseRequestBody = { | ||
| title: body.title, | ||
| content: body.content || '', | ||
| category: body.category || 'feature-requests', | ||
| tags: body.tags || ['feature_request', 'dashboard'], | ||
| commentsAllowed: body.commentsAllowed ?? true, | ||
| email: featurebaseUser.email, | ||
| authorName: auth.user.display_name || 'Stack Auth User', | ||
| customInputValues: { | ||
| // Using the actual field IDs from Featurebase | ||
| "6872f858cc9682d29cf2e4c0": 'dashboard_companion', // source field | ||
| "6872f88041fa77a4dd9dab29": featurebaseUser.userId, // userId field | ||
| "6872f890143fc108288d8f5a": 'stack-auth', // projectId field | ||
| ...body.customInputValues, | ||
madster456 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| }; | ||
|
|
||
| const response = await fetch('https://do.featurebase.app/v2/posts', { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| 'X-API-Key': STACK_FEATUREBASE_API_KEY, | ||
| }, | ||
| body: JSON.stringify(featurebaseRequestBody), | ||
| }); | ||
|
|
||
| const data = await response.json(); | ||
|
|
||
| if (!response.ok) { | ||
| throw new StackAssertionError(`Featurebase API error: ${data.error || 'Failed to create feature request'}`, { data }); | ||
| } | ||
|
|
||
| return { | ||
| statusCode: 200, | ||
| bodyType: "json" as const, | ||
| body: { success: true, id: data.id }, | ||
| }; | ||
| }, | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
madster456 marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.