mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-09 15:17:59 -05:00
wip: adding waitlist
This commit is contained in:
@@ -1957,3 +1957,104 @@ async def get_agent_as_admin(
|
||||
)
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
async def get_waitlist() -> list[store_model.StoreWaitlistEntry]:
|
||||
"""Get all waitlists."""
|
||||
try:
|
||||
waitlists = await prisma.models.WaitlistEntry.prisma().find_many(
|
||||
where={"isDeleted": False},
|
||||
order=[{"createdAt": "desc"}],
|
||||
)
|
||||
|
||||
# order them by votes before returning without vote counts to the frontend
|
||||
sorted_list = sorted(waitlists, key=lambda x: x.votes, reverse=True)
|
||||
|
||||
lists = [
|
||||
store_model.StoreWaitlistEntry(
|
||||
name=waitlist.name,
|
||||
description=waitlist.description,
|
||||
waitlist_id=waitlist.waitlistId,
|
||||
slug=waitlist.slug,
|
||||
subHeading=waitlist.subHeading,
|
||||
imageUrls=waitlist.imageUrls or [],
|
||||
categories=waitlist.categories,
|
||||
)
|
||||
for waitlist in sorted_list
|
||||
if waitlist.status != prisma.enums.WaitlistExternalStatus.CANCELED
|
||||
and waitlist.status != prisma.enums.WaitlistExternalStatus.DONE
|
||||
]
|
||||
|
||||
return lists
|
||||
except Exception as e:
|
||||
logger.error(f"Error fetching waitlists: {e}")
|
||||
raise DatabaseError("Failed to fetch waitlists") from e
|
||||
|
||||
|
||||
async def add_user_to_waitlist(
|
||||
waitlist_id: str, user_id: str | None, email: str | None
|
||||
):
|
||||
"""Add a user to a waitlist."""
|
||||
logger.debug(f"Adding user {user_id} to waitlist {waitlist_id}")
|
||||
|
||||
try:
|
||||
waitlist = await prisma.models.WaitlistEntry.prisma().find_unique(
|
||||
where={"id": waitlist_id}
|
||||
)
|
||||
|
||||
if not waitlist:
|
||||
raise ValueError(f"Waitlist {waitlist_id} not found")
|
||||
|
||||
# Check if user is already in the waitlist
|
||||
existing_entry = None
|
||||
if user_id:
|
||||
existing_entry = await prisma.models.WaitlistEntry.prisma().find_first(
|
||||
where={
|
||||
"waitlistId": waitlist_id,
|
||||
"userId": user_id,
|
||||
"isDeleted": False,
|
||||
}
|
||||
)
|
||||
elif email:
|
||||
existing_entry = await prisma.models.WaitlistEntry.prisma().find_first(
|
||||
where={
|
||||
"waitlistId": waitlist_id,
|
||||
"email": email,
|
||||
"isDeleted": False,
|
||||
}
|
||||
)
|
||||
|
||||
if existing_entry:
|
||||
# convert emails to user if appropriate
|
||||
raise NotImplementedError(
|
||||
"Implment the capibility to convert email based waitlist entries to user based ones and remove the email based waitlist entry."
|
||||
)
|
||||
|
||||
# Create new waitlist entry
|
||||
new_entry = await prisma.models.WaitlistEntry.prisma().create(
|
||||
data=prisma.types.WaitlistEntryCreateInput(
|
||||
waitlistId=waitlist_id,
|
||||
userId=user_id,
|
||||
email=email,
|
||||
name="",
|
||||
description="",
|
||||
slug="",
|
||||
subHeading="",
|
||||
imageUrls=[],
|
||||
categories=[],
|
||||
)
|
||||
)
|
||||
|
||||
return store_model.StoreWaitlistEntry(
|
||||
name=new_entry.name,
|
||||
description=new_entry.description,
|
||||
waitlist_id=new_entry.waitlistId,
|
||||
slug=new_entry.slug,
|
||||
subHeading=new_entry.subHeading,
|
||||
imageUrls=new_entry.imageUrls or [],
|
||||
categories=new_entry.categories,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error adding user to waitlist: {e}")
|
||||
raise DatabaseError("Failed to add user to waitlist") from e
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import datetime
|
||||
from typing import List
|
||||
|
||||
from backend.data.model import User
|
||||
import prisma.enums
|
||||
import pydantic
|
||||
|
||||
@@ -216,3 +217,23 @@ class ReviewSubmissionRequest(pydantic.BaseModel):
|
||||
is_approved: bool
|
||||
comments: str # External comments visible to creator
|
||||
internal_comments: str | None = None # Private admin notes
|
||||
|
||||
|
||||
class StoreWaitlistEntry(pydantic.BaseModel):
|
||||
waitlist_id: str
|
||||
storeListing: StoreListingWithVersions | None = None
|
||||
owner: User | None = None
|
||||
slug: str
|
||||
|
||||
# Content fields
|
||||
name: str
|
||||
subHeading: str
|
||||
videoUrl: str | None = None
|
||||
agentOutputDemoUrl: str | None = None
|
||||
imageUrls: list[str]
|
||||
description: str
|
||||
categories: list[str]
|
||||
|
||||
|
||||
class StoreWaitlistsAllResponse(pydantic.BaseModel):
|
||||
listings: list[StoreWaitlistEntry]
|
||||
|
||||
@@ -5,6 +5,7 @@ import urllib.parse
|
||||
from typing import Literal
|
||||
|
||||
import autogpt_libs.auth
|
||||
from autogpt_libs.auth.dependencies import get_optional_user_id
|
||||
import fastapi
|
||||
import fastapi.responses
|
||||
|
||||
@@ -78,6 +79,45 @@ async def update_or_create_profile(
|
||||
return updated_profile
|
||||
|
||||
|
||||
##############################################
|
||||
############## Waitlist Endpoints ############
|
||||
##############################################
|
||||
@router.get(
|
||||
"/waitlist",
|
||||
summary="Get the agent waitlist",
|
||||
tags=["store", "public"],
|
||||
response_model=store_model.StoreWaitlistsAllResponse,
|
||||
)
|
||||
async def get_waitlist():
|
||||
"""
|
||||
Get the agent waitlist details.
|
||||
"""
|
||||
waitlist = await store_db.get_waitlist()
|
||||
return waitlist
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/waitlist/{waitlist_id}/join",
|
||||
summary="Add self to the agent waitlist",
|
||||
tags=["store", "public"],
|
||||
response_model=store_model.StoreWaitlistEntry,
|
||||
)
|
||||
async def add_self_to_waitlist(
|
||||
user_id: str | None = fastapi.Security(get_optional_user_id),
|
||||
waitlist_id: str = fastapi.Path(..., description="The ID of the waitlist to join"),
|
||||
email: str | None = fastapi.Body(
|
||||
default=None, description="Email address for unauthenticated users"
|
||||
),
|
||||
):
|
||||
"""
|
||||
Add the current user to the agent waitlist.
|
||||
"""
|
||||
waitlist_entry = await store_db.add_user_to_waitlist(
|
||||
waitlist_id=waitlist_id, user_id=user_id, email=email
|
||||
)
|
||||
return waitlist_entry
|
||||
|
||||
|
||||
##############################################
|
||||
############### Agent Endpoints ##############
|
||||
##############################################
|
||||
|
||||
@@ -67,6 +67,10 @@ model User {
|
||||
OAuthAuthorizationCodes OAuthAuthorizationCode[]
|
||||
OAuthAccessTokens OAuthAccessToken[]
|
||||
OAuthRefreshTokens OAuthRefreshToken[]
|
||||
|
||||
// Waitlist relations
|
||||
waitlistEntries WaitlistEntry[]
|
||||
joinedWaitlists WaitlistEntry[] @relation("joinedWaitlists")
|
||||
}
|
||||
|
||||
enum OnboardingStep {
|
||||
@@ -834,7 +838,8 @@ model StoreListing {
|
||||
OwningUser User @relation(fields: [owningUserId], references: [id])
|
||||
|
||||
// Relations
|
||||
Versions StoreListingVersion[] @relation("ListingVersions")
|
||||
Versions StoreListingVersion[] @relation("ListingVersions")
|
||||
waitlistEntries WaitlistEntry[]
|
||||
|
||||
// Unique index on agentId to ensure only one listing per agent, regardless of number of versions the agent has.
|
||||
@@unique([agentGraphId])
|
||||
@@ -924,6 +929,45 @@ model StoreListingReview {
|
||||
@@index([reviewByUserId])
|
||||
}
|
||||
|
||||
enum WaitlistExternalStatus {
|
||||
DONE
|
||||
NOT_STARTED
|
||||
CANCELED
|
||||
WORK_IN_PROGRESS
|
||||
}
|
||||
|
||||
model WaitlistEntry {
|
||||
id String @id @default(uuid())
|
||||
|
||||
storeListingId String?
|
||||
StoreListing StoreListing? @relation(fields: [storeListingId], references: [id], onDelete: Cascade)
|
||||
|
||||
owningUserId String
|
||||
OwningUser User @relation(fields: [owningUserId], references: [id])
|
||||
|
||||
slug String
|
||||
search Unsupported("tsvector")? @default(dbgenerated("''::tsvector"))
|
||||
|
||||
// Content fields
|
||||
name String
|
||||
subHeading String
|
||||
videoUrl String?
|
||||
agentOutputDemoUrl String?
|
||||
imageUrls String[]
|
||||
description String
|
||||
categories String[]
|
||||
|
||||
//Waitlist specific fields
|
||||
status WaitlistExternalStatus @default(NOT_STARTED)
|
||||
votes Int @default(0) // Hide from frontend api
|
||||
joinedUsers User[] @relation("joinedWaitlists")
|
||||
// NOTE: DO NOT DOUBLE SEND TO THESE USERS, IF THEY HAVE SIGNED UP SINCE THEY MAY HAVE ALREADY RECEIVED AN EMAIL
|
||||
// DOUBLE CHECK WHEN SENDING THAT THEY ARE NOT IN THE JOINED USERS LIST ALSO
|
||||
unafilliatedEmailUsers String[] @default([])
|
||||
|
||||
isDeleted Boolean @default(false)
|
||||
}
|
||||
|
||||
enum SubmissionStatus {
|
||||
DRAFT // Being prepared, not yet submitted
|
||||
PENDING // Submitted, awaiting review
|
||||
|
||||
@@ -4965,6 +4965,135 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/store/profile": {
|
||||
"get": {
|
||||
"tags": ["v2", "store", "private"],
|
||||
"summary": "Get user profile",
|
||||
"description": "Get the profile details for the authenticated user.\nCached for 1 hour per user.",
|
||||
"operationId": "getV2Get user profile",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ProfileDetails" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
|
||||
}
|
||||
},
|
||||
"security": [{ "HTTPBearerJWT": [] }]
|
||||
},
|
||||
"post": {
|
||||
"tags": ["v2", "store", "private"],
|
||||
"summary": "Update user profile",
|
||||
"description": "Update the store profile for the authenticated user.\n\nArgs:\n profile (Profile): The updated profile details\n user_id (str): ID of the authenticated user\n\nReturns:\n CreatorDetails: The updated profile\n\nRaises:\n HTTPException: If there is an error updating the profile",
|
||||
"operationId": "postV2Update user profile",
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/Profile" }
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/CreatorDetails" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"$ref": "#/components/responses/HTTP401NotAuthenticatedError"
|
||||
}
|
||||
},
|
||||
"security": [{ "HTTPBearerJWT": [] }]
|
||||
}
|
||||
},
|
||||
"/api/store/waitlist": {
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "Get the agent waitlist",
|
||||
"description": "Get the agent waitlist details.",
|
||||
"operationId": "getV2Get the agent waitlist",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/StoreWaitlistsAllResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/store/waitlist/{waitlist_id}/join": {
|
||||
"post": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
"summary": "Add self to the agent waitlist",
|
||||
"description": "Add the current user to the agent waitlist.",
|
||||
"operationId": "postV2Add self to the agent waitlist",
|
||||
"security": [{ "HTTPBearer": [] }],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "waitlist_id",
|
||||
"in": "path",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string",
|
||||
"description": "The ID of the waitlist to join",
|
||||
"title": "Waitlist Id"
|
||||
},
|
||||
"description": "The ID of the waitlist to join"
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"description": "Email address for unauthenticated users",
|
||||
"title": "Email"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful Response",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/StoreWaitlistEntry" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"422": {
|
||||
"description": "Validation Error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/HTTPValidationError" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/store/agents": {
|
||||
"get": {
|
||||
"tags": ["v2", "store", "public"],
|
||||
@@ -9885,6 +10014,68 @@
|
||||
"required": ["submissions", "pagination"],
|
||||
"title": "StoreSubmissionsResponse"
|
||||
},
|
||||
"StoreWaitlistEntry": {
|
||||
"properties": {
|
||||
"waitlist_id": { "type": "string", "title": "Waitlist Id" },
|
||||
"storeListing": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/components/schemas/StoreListingWithVersions" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"owner": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/components/schemas/User" },
|
||||
{ "type": "null" }
|
||||
]
|
||||
},
|
||||
"slug": { "type": "string", "title": "Slug" },
|
||||
"name": { "type": "string", "title": "Name" },
|
||||
"subHeading": { "type": "string", "title": "Subheading" },
|
||||
"videoUrl": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Videourl"
|
||||
},
|
||||
"agentOutputDemoUrl": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Agentoutputdemourl"
|
||||
},
|
||||
"imageUrls": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Imageurls"
|
||||
},
|
||||
"description": { "type": "string", "title": "Description" },
|
||||
"categories": {
|
||||
"items": { "type": "string" },
|
||||
"type": "array",
|
||||
"title": "Categories"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": [
|
||||
"waitlist_id",
|
||||
"slug",
|
||||
"name",
|
||||
"subHeading",
|
||||
"imageUrls",
|
||||
"description",
|
||||
"categories"
|
||||
],
|
||||
"title": "StoreWaitlistEntry"
|
||||
},
|
||||
"StoreWaitlistsAllResponse": {
|
||||
"properties": {
|
||||
"listings": {
|
||||
"items": { "$ref": "#/components/schemas/StoreWaitlistEntry" },
|
||||
"type": "array",
|
||||
"title": "Listings"
|
||||
}
|
||||
},
|
||||
"type": "object",
|
||||
"required": ["listings"],
|
||||
"title": "StoreWaitlistsAllResponse"
|
||||
},
|
||||
"SubmissionStatus": {
|
||||
"type": "string",
|
||||
"enum": ["DRAFT", "PENDING", "APPROVED", "REJECTED"],
|
||||
@@ -11368,6 +11559,128 @@
|
||||
],
|
||||
"title": "UploadFileResponse"
|
||||
},
|
||||
"User": {
|
||||
"properties": {
|
||||
"id": { "type": "string", "title": "Id", "description": "User ID" },
|
||||
"email": {
|
||||
"type": "string",
|
||||
"title": "Email",
|
||||
"description": "User email address"
|
||||
},
|
||||
"email_verified": {
|
||||
"type": "boolean",
|
||||
"title": "Email Verified",
|
||||
"description": "Whether email is verified",
|
||||
"default": true
|
||||
},
|
||||
"name": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Name",
|
||||
"description": "User display name"
|
||||
},
|
||||
"created_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"title": "Created At",
|
||||
"description": "When user was created"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"title": "Updated At",
|
||||
"description": "When user was last updated"
|
||||
},
|
||||
"metadata": {
|
||||
"additionalProperties": true,
|
||||
"type": "object",
|
||||
"title": "Metadata",
|
||||
"description": "User metadata as dict"
|
||||
},
|
||||
"integrations": {
|
||||
"type": "string",
|
||||
"title": "Integrations",
|
||||
"description": "Encrypted integrations data",
|
||||
"default": ""
|
||||
},
|
||||
"stripe_customer_id": {
|
||||
"anyOf": [{ "type": "string" }, { "type": "null" }],
|
||||
"title": "Stripe Customer Id",
|
||||
"description": "Stripe customer ID"
|
||||
},
|
||||
"top_up_config": {
|
||||
"anyOf": [
|
||||
{ "$ref": "#/components/schemas/AutoTopUpConfig" },
|
||||
{ "type": "null" }
|
||||
],
|
||||
"description": "Top up configuration"
|
||||
},
|
||||
"max_emails_per_day": {
|
||||
"type": "integer",
|
||||
"title": "Max Emails Per Day",
|
||||
"description": "Maximum emails per day",
|
||||
"default": 3
|
||||
},
|
||||
"notify_on_agent_run": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Agent Run",
|
||||
"description": "Notify on agent run",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_zero_balance": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Zero Balance",
|
||||
"description": "Notify on zero balance",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_low_balance": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Low Balance",
|
||||
"description": "Notify on low balance",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_block_execution_failed": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Block Execution Failed",
|
||||
"description": "Notify on block execution failure",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_continuous_agent_error": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Continuous Agent Error",
|
||||
"description": "Notify on continuous agent error",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_daily_summary": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Daily Summary",
|
||||
"description": "Notify on daily summary",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_weekly_summary": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Weekly Summary",
|
||||
"description": "Notify on weekly summary",
|
||||
"default": true
|
||||
},
|
||||
"notify_on_monthly_summary": {
|
||||
"type": "boolean",
|
||||
"title": "Notify On Monthly Summary",
|
||||
"description": "Notify on monthly summary",
|
||||
"default": true
|
||||
},
|
||||
"timezone": {
|
||||
"type": "string",
|
||||
"title": "Timezone",
|
||||
"description": "User timezone (IANA timezone identifier or 'not-set')",
|
||||
"default": "not-set"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"required": ["id", "email", "created_at", "updated_at"],
|
||||
"title": "User",
|
||||
"description": "Application-layer User model with snake_case convention."
|
||||
},
|
||||
"UserHistoryResponse": {
|
||||
"properties": {
|
||||
"history": {
|
||||
@@ -11674,6 +11987,7 @@
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"HTTPBearer": { "type": "http", "scheme": "bearer" },
|
||||
"APIKeyAuthenticator-X-Postmark-Webhook-Token": {
|
||||
"type": "apiKey",
|
||||
"in": "header",
|
||||
|
||||
Reference in New Issue
Block a user