Skip to content

Commit f0617d2

Browse files
chitalianJustin Torreclaude[bot]tomharmon
authored
Justin/waitlist credits (#4648)
* put credits behind a waitlist * clean up pr and fixed some edge cases * credits empty state better * lint * fix build * lint * fix web build * Remove unnecessary index creation in waitlist migration Use direct unique constraint instead of index + constraint pattern. Co-authored-by: Justin Torre <chitalian@users.noreply.github.com> * docs: improve controllers README with clearer routing explanation - Add comprehensive documentation of public vs private controller structure - Clarify authentication rules based on route paths, not directory structure - Explain that /v1/public routes bypass auth while others require API keys - Include examples and documentation generation differences - Address confusion about routing logic mentioned in PR review Co-authored-by: Justin Torre <chitalian@users.noreply.github.com> * Update README.md Clarify README * Update web/pages/settings/credits.tsx Co-authored-by: Thomas Harmon <thomas.alan.harmon@gmail.com> --------- Co-authored-by: Justin Torre <justin@helicone.ai> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Co-authored-by: Justin Torre <chitalian@users.noreply.github.com> Co-authored-by: Thomas Harmon <thomas.alan.harmon@gmail.com>
1 parent b9562ce commit f0617d2

File tree

19 files changed

+895
-380
lines changed

19 files changed

+895
-380
lines changed

bifrost/lib/clients/jawnTypes/private.ts

Lines changed: 64 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@ interface JsonObject { [key: string]: JsonValue; }
99

1010

1111
export interface paths {
12+
"/v1/public/waitlist/feature": {
13+
post: operations["AddToWaitlist"];
14+
};
15+
"/v1/public/waitlist/feature/status": {
16+
get: operations["IsOnWaitlist"];
17+
};
1218
"/v1/user-feedback": {
1319
post: operations["PostUserFeedback"];
1420
};
@@ -502,6 +508,28 @@ export type webhooks = Record<string, never>;
502508

503509
export interface components {
504510
schemas: {
511+
"ResultSuccess__success-boolean--message-string__": {
512+
data: {
513+
message: string;
514+
success: boolean;
515+
};
516+
/** @enum {number|null} */
517+
error: null;
518+
};
519+
ResultError_string_: {
520+
/** @enum {number|null} */
521+
data: null;
522+
error: string;
523+
};
524+
"Result__success-boolean--message-string_.string_": components["schemas"]["ResultSuccess__success-boolean--message-string__"] | components["schemas"]["ResultError_string_"];
525+
"ResultSuccess__isOnWaitlist-boolean__": {
526+
data: {
527+
isOnWaitlist: boolean;
528+
};
529+
/** @enum {number|null} */
530+
error: null;
531+
};
532+
"Result__isOnWaitlist-boolean_.string_": components["schemas"]["ResultSuccess__isOnWaitlist-boolean__"] | components["schemas"]["ResultError_string_"];
505533
RateLimitRuleView: {
506534
id: string;
507535
name: string;
@@ -520,11 +548,6 @@ export interface components {
520548
/** @enum {number|null} */
521549
error: null;
522550
};
523-
ResultError_string_: {
524-
/** @enum {number|null} */
525-
data: null;
526-
error: string;
527-
};
528551
"Result_RateLimitRuleView-Array.string_": components["schemas"]["ResultSuccess_RateLimitRuleView-Array_"] | components["schemas"]["ResultError_string_"];
529552
ResultSuccess_RateLimitRuleView_: {
530553
data: components["schemas"]["RateLimitRuleView"];
@@ -15730,6 +15753,42 @@ export type external = Record<string, never>;
1573015753

1573115754
export interface operations {
1573215755

15756+
AddToWaitlist: {
15757+
requestBody: {
15758+
content: {
15759+
"application/json": {
15760+
organizationId?: string;
15761+
feature: string;
15762+
email: string;
15763+
};
15764+
};
15765+
};
15766+
responses: {
15767+
/** @description Ok */
15768+
200: {
15769+
content: {
15770+
"application/json": components["schemas"]["Result__success-boolean--message-string_.string_"];
15771+
};
15772+
};
15773+
};
15774+
};
15775+
IsOnWaitlist: {
15776+
parameters: {
15777+
query: {
15778+
email: string;
15779+
feature: string;
15780+
organizationId: string;
15781+
};
15782+
};
15783+
responses: {
15784+
/** @description Ok */
15785+
200: {
15786+
content: {
15787+
"application/json": components["schemas"]["Result__isOnWaitlist-boolean_.string_"];
15788+
};
15789+
};
15790+
};
15791+
};
1573315792
PostUserFeedback: {
1573415793
requestBody: {
1573515794
content: {

bifrost/lib/clients/jawnTypes/public.ts

Lines changed: 0 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ export interface paths {
1616
"/v1/webhooks/{webhookId}": {
1717
delete: operations["DeleteWebhook"];
1818
};
19-
"/v1/public/waitlist/experiments": {
20-
post: operations["AddToWaitlist"];
21-
};
2219
"/v1/vault/add": {
2320
post: operations["AddKey"];
2421
};
@@ -697,11 +694,6 @@ export interface components {
697694
error: null;
698695
};
699696
"Result_null.string_": components["schemas"]["ResultSuccess_null_"] | components["schemas"]["ResultError_string_"];
700-
ResultError_any_: {
701-
/** @enum {number|null} */
702-
data: null;
703-
error: unknown;
704-
};
705697
"ResultSuccess__id-string__": {
706698
data: {
707699
id: string;
@@ -4012,23 +4004,6 @@ export interface operations {
40124004
};
40134005
};
40144006
};
4015-
AddToWaitlist: {
4016-
requestBody: {
4017-
content: {
4018-
"application/json": {
4019-
email: string;
4020-
};
4021-
};
4022-
};
4023-
responses: {
4024-
/** @description Ok */
4025-
200: {
4026-
content: {
4027-
"application/json": components["schemas"]["ResultSuccess_unknown_"] | components["schemas"]["ResultError_any_"];
4028-
};
4029-
};
4030-
};
4031-
};
40324007
AddKey: {
40334008
requestBody: {
40344009
content: {

docs/swagger.json

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -183,24 +183,6 @@
183183
}
184184
]
185185
},
186-
"ResultError_any_": {
187-
"properties": {
188-
"data": {
189-
"type": "number",
190-
"enum": [
191-
null
192-
],
193-
"nullable": true
194-
},
195-
"error": {}
196-
},
197-
"required": [
198-
"data",
199-
"error"
200-
],
201-
"type": "object",
202-
"additionalProperties": false
203-
},
204186
"ResultSuccess__id-string__": {
205187
"properties": {
206188
"data": {
@@ -12176,53 +12158,6 @@
1217612158
]
1217712159
}
1217812160
},
12179-
"/v1/public/waitlist/experiments": {
12180-
"post": {
12181-
"operationId": "AddToWaitlist",
12182-
"responses": {
12183-
"200": {
12184-
"description": "Ok",
12185-
"content": {
12186-
"application/json": {
12187-
"schema": {
12188-
"anyOf": [
12189-
{
12190-
"$ref": "#/components/schemas/ResultSuccess_unknown_"
12191-
},
12192-
{
12193-
"$ref": "#/components/schemas/ResultError_any_"
12194-
}
12195-
]
12196-
}
12197-
}
12198-
}
12199-
}
12200-
},
12201-
"tags": [
12202-
"Waitlist"
12203-
],
12204-
"security": [],
12205-
"parameters": [],
12206-
"requestBody": {
12207-
"required": true,
12208-
"content": {
12209-
"application/json": {
12210-
"schema": {
12211-
"properties": {
12212-
"email": {
12213-
"type": "string"
12214-
}
12215-
},
12216-
"required": [
12217-
"email"
12218-
],
12219-
"type": "object"
12220-
}
12221-
}
12222-
}
12223-
}
12224-
}
12225-
},
1222612161
"/v1/vault/add": {
1222712162
"post": {
1222812163
"operationId": "AddKey",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- Create feature_waitlist table for tracking users waiting for specific features
2+
CREATE TABLE IF NOT EXISTS public.feature_waitlist (
3+
id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL,
4+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
5+
email VARCHAR(255) NOT NULL,
6+
feature VARCHAR(100) NOT NULL,
7+
organization_id UUID NULL -- Optional organization ID
8+
);
9+
-- Create primary key
10+
CREATE UNIQUE INDEX feature_waitlist_pkey ON public.feature_waitlist USING btree (id);
11+
ALTER TABLE public.feature_waitlist
12+
ADD CONSTRAINT feature_waitlist_pkey PRIMARY KEY USING INDEX feature_waitlist_pkey;
13+
-- Create unique constraint on email + feature + organization_id to prevent duplicates
14+
ALTER TABLE public.feature_waitlist
15+
ADD CONSTRAINT feature_waitlist_email_feature_key UNIQUE (email, feature, organization_id);
16+
REVOKE ALL ON TABLE public.feature_waitlist
17+
FROM PUBLIC;
18+
REVOKE ALL ON TABLE public.feature_waitlist
19+
FROM authenticated;
20+
REVOKE ALL ON TABLE public.feature_waitlist
21+
FROM service_role;

valhalla/jawn/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"llama-api-client": "^0.1.2",
5757
"lodash": "^4.17.21",
5858
"lodash.merge": "^4.6.2",
59+
"loops": "^6.0.0",
5960
"moment": "^2.30.1",
6061
"morgan": "^1.10.0",
6162
"node-sql-parser": "^5.3.10",
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Controllers Structure
2+
3+
## Directory Structure
4+
The private/public folders only represent was is generated for docs
5+
6+
- **`/private`** - Will not be in the public docs
7+
- **`/public`** - Will be in the docs
8+
9+
## Authentication & Routing
10+
11+
### Route Authentication Rules (in `middleware/auth.ts`):
12+
1. **Routes starting with `/v1/public`** - Bypass authentication entirely (no API key required)
13+
2. **All other routes** - Require API key authentication by default (see middleware.ts). The `@Security("api_key")` was never implemented properly. We willimplement this properly in the future
14+
15+
### Documentation Generation:
16+
- **`/private`** controllers - No auto-generated docs (internal/admin use)
17+
- **`/public`** controllers - Full auto-generated documentation
18+
19+
### Examples:
20+
21+
**Public Controllers:**
22+
- Can use routes like `@Route("v1/experiment")` - Requires authentication
23+
- Can use routes like `@Route("/v1/public/model-registry")` - No authentication required
24+
25+
**Private Controllers:**
26+
- Use routes like `@Route("v1/admin")` - Requires authentication + admin privileges
27+
- Generally for internal/admin functionality
28+
29+
The key distinction is the **route path**, not the directory. Routes containing `/v1/public` skip authentication, while all others require it.

0 commit comments

Comments
 (0)