diff --git a/backend/showcase/config.py b/backend/showcase/config.py index 0111f8b..8186f5a 100644 --- a/backend/showcase/config.py +++ b/backend/showcase/config.py @@ -33,9 +33,9 @@ messages={"condition": "Must start with 'SG.'"}, ), # Validator( - # "sendgrid_from_email", - # must_exist=False, - # default="", + # "sendgrid_from_email", + # must_exist=False, + # default="", # ), Validator( "jwt_secret", @@ -48,7 +48,6 @@ Validator( "jwt_expire_minutes", # 2 days. People can always log in again - default=2880, ), ], diff --git a/backend/showcase/db/event.py b/backend/showcase/db/event.py index 7a597f4..038da41 100644 --- a/backend/showcase/db/event.py +++ b/backend/showcase/db/event.py @@ -5,10 +5,8 @@ # https://docs.pydantic.dev/1.10/usage/schema/#field-customization class EventCreationPayload(BaseModel): - name: Annotated[str, StringConstraints( - min_length=1 - )] - description: Optional[Annotated[str, StringConstraints(max_length=500)]] + name: Annotated[str, StringConstraints(min_length=1)] + description: Optional[Annotated[str, StringConstraints(max_length=500)]] # Owner is inferred from the current user (token) # https://github.com/fastapi/fastapi/discussions/7585#discussioncomment-7573510 @@ -17,9 +15,11 @@ class EventCreationPayload(BaseModel): owner: SkipJsonSchema[str | List[str]] = None join_code: SkipJsonSchema[str] = None + class Event(EventCreationPayload): id: str + # Maybe rename to FullEvent? In the frontend it's OwnedEvent since that's the only time a normal user should see all event information (if they own it) class ComplexEvent(Event): # https://stackoverflow.com/questions/63793662/how-to-give-a-pydantic-list-field-a-default-value/63808835#63808835 @@ -27,8 +27,10 @@ class ComplexEvent(Event): attendees: Annotated[List[str], Field(default_factory=list)] join_code: str + class UserEvents(BaseModel): """Return information regarding what the events the user owns and what events they are attending. If they are only attending an event, don't return sensitive information like participants.""" + owned_events: List[ComplexEvent] # This was just the creation payload earlier and I was wondering why the ID wasn't being returned... - attending_events: List[Event] + attending_events: List[Event] diff --git a/backend/showcase/db/project.py b/backend/showcase/db/project.py index a00a46b..f007eef 100644 --- a/backend/showcase/db/project.py +++ b/backend/showcase/db/project.py @@ -3,27 +3,30 @@ from typing import Annotated, List, Optional from annotated_types import Len + # https://docs.pydantic.dev/1.10/usage/schema/#field-customization class ProjectCreationPayload(BaseModel): - name: Annotated[str, StringConstraints( - min_length=1 - )] + name: Annotated[str, StringConstraints(min_length=1)] readme: HttpUrl repo: HttpUrl image_url: HttpUrl description: Optional[str] = None - + owner: Annotated[SkipJsonSchema[List[str]], Field()] = None # https://docs.pydantic.dev/latest/api/types/#pydantic.types.constr--__tabbed_1_2 - event: Annotated[List[Annotated[str, StringConstraints(pattern=r"^rec\w*$")]], Len(min_length=1, max_length=1)] = None + event: Annotated[ + List[Annotated[str, StringConstraints(pattern=r"^rec\w*$")]], + Len(min_length=1, max_length=1), + ] = None def model_dump(self, *args, **kwargs): data = super().model_dump(*args, **kwargs) - data['readme'] = str(self.readme) - data['repo'] = str(self.repo) - data['image_url'] = str(self.image_url) + data["readme"] = str(self.readme) + data["repo"] = str(self.repo) + data["image_url"] = str(self.image_url) return data - + + class Project(ProjectCreationPayload): id: str - points: int \ No newline at end of file + points: int diff --git a/backend/showcase/db/user.py b/backend/showcase/db/user.py index c6c176e..3475aab 100644 --- a/backend/showcase/db/user.py +++ b/backend/showcase/db/user.py @@ -4,12 +4,13 @@ from showcase.db import tables from pyairtable.formulas import match + class UserSignupPayload(BaseModel): first_name: str last_name: str email: EmailStr # Might eventually add validation for mailing address, although it's not necessary for the MVP - mailing_address: str + mailing_address: str # It may help to create a lookup field later, although this works fine for now diff --git a/backend/showcase/routers/auth.py b/backend/showcase/routers/auth.py index 5c866fd..2afefad 100644 --- a/backend/showcase/routers/auth.py +++ b/backend/showcase/routers/auth.py @@ -1,4 +1,5 @@ from datetime import datetime, timedelta, timezone + # import smtplib # from email.mime.text import MIMEText from typing import Annotated @@ -42,7 +43,9 @@ def create_access_token( if expires_delta: expire = datetime.now(timezone.utc) + expires_delta else: - expire = datetime.now(timezone.utc) + timedelta(minutes=MAGIC_LINK_EXPIRE_MINUTES) + expire = datetime.now(timezone.utc) + timedelta( + minutes=MAGIC_LINK_EXPIRE_MINUTES + ) to_encode.update({"exp": expire}) encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) return encoded_jwt @@ -64,12 +67,14 @@ async def send_magic_link(email: str): ) try: - sg = SendGridAPIClient(settings.sendgrid_api_key) + sg = SendGridAPIClient(settings.sendgrid_api_key) _ = sg.send(message) except Exception as e: - raise HTTPException(status_code=500, detail="Failed to send auth email") + raise HTTPException(status_code=500, detail="Failed to send auth email") - print(f"Token for {email}: {token} | magic_link: {settings.production_url}/login?token={token}") + print( + f"Token for {email}: {token} | magic_link: {settings.production_url}/login?token={token}" + ) @router.post("/request-login") @@ -78,7 +83,7 @@ async def request_login(user: User): # Check if the user exists if db.user.get_user_record_id_by_email(user.email) is None: raise HTTPException(status_code=404, detail="User not found") - return # not needed + return # not needed await send_magic_link(user.email) @@ -86,6 +91,8 @@ class MagicLinkVerificationResponse(BaseModel): access_token: str token_type: str email: str + + @router.get("/verify") async def verify_token(token: Annotated[str, Query()]) -> MagicLinkVerificationResponse: """Verify a magic link and return an access token""" @@ -105,7 +112,9 @@ async def verify_token(token: Annotated[str, Query()]) -> MagicLinkVerificationR expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), token_type="access", ) - return MagicLinkVerificationResponse(access_token=access_token, token_type="access", email=email) + return MagicLinkVerificationResponse( + access_token=access_token, token_type="access", email=email + ) security = HTTPBearer() @@ -131,11 +140,14 @@ async def get_current_user( return {"email": email} - class CheckAuthResponse(BaseModel): email: str + + @router.get("/protected-route") -async def protected_route(current_user: Annotated[dict, Depends(get_current_user)]) -> CheckAuthResponse: +async def protected_route( + current_user: Annotated[dict, Depends(get_current_user)], +) -> CheckAuthResponse: return CheckAuthResponse(email=current_user["email"]) @@ -152,4 +164,6 @@ async def protected_route(current_user: Annotated[dict, Depends(get_current_user token_type="magic_link", ) # print the access token on one line and on the next line the magic link - print(f"Access token for {DEBUG_EMAIL}: {debug_access}\nMagic link: http://localhost:5173/login?token={debug_verify}") + print( + f"Access token for {DEBUG_EMAIL}: {debug_access}\nMagic link: http://localhost:5173/login?token={debug_verify}" + ) diff --git a/backend/showcase/routers/events.py b/backend/showcase/routers/events.py index 06b0055..0d14d4d 100644 --- a/backend/showcase/routers/events.py +++ b/backend/showcase/routers/events.py @@ -30,7 +30,7 @@ def get_event( user_id = db.user.get_user_record_id_by_email(current_user["email"]) if user_id is None: raise HTTPException(status_code=500, detail="User not found") - + user = db.users.get(user_id) event = db.events.get(event_id) @@ -42,7 +42,10 @@ def get_event( elif user["id"] in event["fields"].get("attendees", []): return Event.model_validate({"id": event["id"], **event["fields"]}) else: - raise HTTPException(status_code=403, detail="User does not have access to event") + raise HTTPException( + status_code=403, detail="User does not have access to event" + ) + # Used to be /attending @router.get("/") @@ -57,7 +60,6 @@ def get_attending_events( raise HTTPException(status_code=500, detail="User not found") user = db.users.get(user_id) - # Eventually it might be better to return a user object. Otherwise, the client that the event owner is using would need to fetch the user. Since user emails probably shouldn't be public with just a record ID as a parameter, we would need to check if the person calling GET /users?user=ID has an event wherein that user ID is present. To avoid all this, the user object could be returned. owned_events = [ ComplexEvent.model_validate({"id": event["id"], **event["fields"]}) @@ -74,13 +76,13 @@ def get_attending_events( ] ] - return UserEvents(owned_events=owned_events, attending_events=attending_events) @router.post("/") def create_event( - event: EventCreationPayload, current_user: Annotated[dict, Depends(get_current_user)] + event: EventCreationPayload, + current_user: Annotated[dict, Depends(get_current_user)], ): """ Create a new event. The current user is automatically added as an owner of the event. @@ -100,7 +102,6 @@ def create_event( db.events.create(event.model_dump()) - # The issue with the approach below was that ComplexEvent requires an ID, which isn't available until the event is created. It might be better to just do it and reoplace model_validate with model_construct to prevent validation errors # return db.events.create( # ComplexEvent.model_validate( @@ -113,13 +114,13 @@ def create_event( # } # ).model_dump( # exclude_unset=True - # ) + # ) # ) @router.post("/attend") def attend_event( - join_code: Annotated[str, Query(description="A unique code used to join an event")], + join_code: Annotated[str, Query(description="A unique code used to join an event")], current_user: Annotated[dict, Depends(get_current_user)], ): """ @@ -267,16 +268,22 @@ def get_leaderboard(event_id: Annotated[str, Path(title="Event ID")]) -> List[Pr # Sort the projects by the number of votes they have received projects.sort(key=lambda project: project["fields"].get("points", 0), reverse=True) - - projects = [Project.model_validate({"id": project["id"], **project["fields"]}) for project in projects] + + projects = [ + Project.model_validate({"id": project["id"], **project["fields"]}) + for project in projects + ] return projects + @router.get("/{event_id}/projects") -def get_event_projects(event_id: Annotated[str, Path(title="Event ID")]) -> List[Project]: +def get_event_projects( + event_id: Annotated[str, Path(title="Event ID")], +) -> List[Project]: """ Get the projects for a specific event. """ - try: + try: event = db.events.get(event_id) except HTTPError as e: raise ( @@ -285,5 +292,11 @@ def get_event_projects(event_id: Annotated[str, Path(title="Event ID")]) -> List else e ) - projects = [Project.model_validate({"id": project["id"], **project["fields"]}) for project in [db.projects.get(project_id) for project_id in event["fields"].get("projects", [])]] - return projects \ No newline at end of file + projects = [ + Project.model_validate({"id": project["id"], **project["fields"]}) + for project in [ + db.projects.get(project_id) + for project_id in event["fields"].get("projects", []) + ] + ] + return projects diff --git a/backend/showcase/routers/projects.py b/backend/showcase/routers/projects.py index 6f48f4c..e6fa919 100644 --- a/backend/showcase/routers/projects.py +++ b/backend/showcase/routers/projects.py @@ -11,7 +11,10 @@ # It's up to the client to provide the event record ID @router.post("/") -def create_project(project: db.ProjectCreationPayload, current_user: Annotated[dict, Depends(get_current_user)]): +def create_project( + project: db.ProjectCreationPayload, + current_user: Annotated[dict, Depends(get_current_user)], +): """ Create a new project. The current user is automatically added as an owner of the project. """ @@ -37,9 +40,9 @@ def create_project(project: db.ProjectCreationPayload, current_user: Annotated[d if not any(owner in event_attendees for owner in project.owner): raise HTTPException(status_code=403, detail="Owner not part of event") - return db.projects.create(project.model_dump())["fields"] + @router.get("/{project_id}") # The regex here is to ensure that the path parameter starts with "rec" and is followed by any number of alphanumeric characters def get_project(project_id: Annotated[str, Path(pattern=r"^rec\w*$")]): @@ -50,5 +53,5 @@ def get_project(project_id: Annotated[str, Path(pattern=r"^rec\w*$")]): HTTPException(status_code=404, detail="Project not found") if e.response.status_code == 404 else e - ) - return Project.model_validate({id: project["id"], **project["fields"]}) \ No newline at end of file + ) + return Project.model_validate({id: project["id"], **project["fields"]}) diff --git a/backend/showcase/routers/users.py b/backend/showcase/routers/users.py index b7cd7bd..347af4f 100644 --- a/backend/showcase/routers/users.py +++ b/backend/showcase/routers/users.py @@ -15,13 +15,15 @@ # Eventually, this should probably be rate-limited @router.post("/") -def create_user(user: UserSignupPayload): +def create_user(user: UserSignupPayload): db.users.create(user.model_dump()) + class UserExistsResponse(BaseModel): exists: bool + @router.get("/exists") def user_exists(email: Annotated[EmailStr, Query(...)]) -> UserExistsResponse: exists = True if db.user.get_user_record_id_by_email(email) else False - return UserExistsResponse(exists=exists) \ No newline at end of file + return UserExistsResponse(exists=exists) diff --git a/frontend/.prettierignore b/frontend/.prettierignore new file mode 100644 index 0000000..2046e43 --- /dev/null +++ b/frontend/.prettierignore @@ -0,0 +1 @@ +src/lib/client/ \ No newline at end of file diff --git a/frontend/.prettierrc b/frontend/.prettierrc index 7027e44..6b78363 100644 --- a/frontend/.prettierrc +++ b/frontend/.prettierrc @@ -1,4 +1,4 @@ { - "plugins": ["prettier-plugin-svelte"], - "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] -} \ No newline at end of file + "plugins": ["prettier-plugin-svelte"], + "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }] +} diff --git a/frontend/src/lib/apiErrorCheck.ts b/frontend/src/lib/apiErrorCheck.ts index 106c965..e1cfb9d 100644 --- a/frontend/src/lib/apiErrorCheck.ts +++ b/frontend/src/lib/apiErrorCheck.ts @@ -5,13 +5,17 @@ type ErrorWithDetail = { detail: string; }; -export function handleError(error: HTTPValidationError | ErrorWithDetail | Error | unknown) { +export function handleError( + error: HTTPValidationError | ErrorWithDetail | Error | unknown, +) { // If it's a FastAPI HTTPException, it will have a detail field. Same with validation errors. console.error("Error", error); if (error && typeof error === "object" && "detail" in error) { if (Array.isArray(error?.detail)) { // const invalidFields = error.detail.map((e) => e.msg); - const invalidFields = error.detail.map((e) => `${e.loc.join(".")}: ${e.msg}`); + const invalidFields = error.detail.map( + (e) => `${e.loc.join(".")}: ${e.msg}`, + ); toast(invalidFields.join(" | ")); } else if (typeof error?.detail === "string") { toast(error.detail); diff --git a/frontend/src/lib/client/index.ts b/frontend/src/lib/client/index.ts index eae885d..81abc82 100644 --- a/frontend/src/lib/client/index.ts +++ b/frontend/src/lib/client/index.ts @@ -1,3 +1,3 @@ // This file is auto-generated by @hey-api/openapi-ts -export * from "./sdk.gen"; -export * from "./types.gen"; +export * from './sdk.gen'; +export * from './types.gen'; \ No newline at end of file diff --git a/frontend/src/lib/client/sdk.gen.ts b/frontend/src/lib/client/sdk.gen.ts index b0c2807..aa94699 100644 --- a/frontend/src/lib/client/sdk.gen.ts +++ b/frontend/src/lib/client/sdk.gen.ts @@ -1,315 +1,168 @@ // This file is auto-generated by @hey-api/openapi-ts -import { - createClient, - createConfig, - type OptionsLegacyParser, -} from "@hey-api/client-fetch"; -import type { - RequestLoginRequestLoginPostData, - RequestLoginRequestLoginPostError, - RequestLoginRequestLoginPostResponse, - VerifyTokenVerifyGetData, - VerifyTokenVerifyGetError, - VerifyTokenVerifyGetResponse, - ProtectedRouteProtectedRouteGetError, - ProtectedRouteProtectedRouteGetResponse, - GetEventEventsEventIdGetData, - GetEventEventsEventIdGetError, - GetEventEventsEventIdGetResponse, - GetAttendingEventsEventsGetError, - GetAttendingEventsEventsGetResponse, - CreateEventEventsPostData, - CreateEventEventsPostError, - CreateEventEventsPostResponse, - AttendEventEventsAttendPostData, - AttendEventEventsAttendPostError, - AttendEventEventsAttendPostResponse, - VoteEventsVotePostData, - VoteEventsVotePostError, - VoteEventsVotePostResponse, - GetLeaderboardEventsEventIdLeaderboardGetData, - GetLeaderboardEventsEventIdLeaderboardGetError, - GetLeaderboardEventsEventIdLeaderboardGetResponse, - GetEventProjectsEventsEventIdProjectsGetData, - GetEventProjectsEventsEventIdProjectsGetError, - GetEventProjectsEventsEventIdProjectsGetResponse, - CreateProjectProjectsPostData, - CreateProjectProjectsPostError, - CreateProjectProjectsPostResponse, - GetProjectProjectsProjectIdGetData, - GetProjectProjectsProjectIdGetError, - GetProjectProjectsProjectIdGetResponse, - CreateUserUsersPostData, - CreateUserUsersPostError, - CreateUserUsersPostResponse, - UserExistsUsersExistsGetData, - UserExistsUsersExistsGetError, - UserExistsUsersExistsGetResponse, -} from "./types.gen"; +import { createClient, createConfig, type OptionsLegacyParser } from '@hey-api/client-fetch'; +import type { RequestLoginRequestLoginPostData, RequestLoginRequestLoginPostError, RequestLoginRequestLoginPostResponse, VerifyTokenVerifyGetData, VerifyTokenVerifyGetError, VerifyTokenVerifyGetResponse, ProtectedRouteProtectedRouteGetError, ProtectedRouteProtectedRouteGetResponse, GetEventEventsEventIdGetData, GetEventEventsEventIdGetError, GetEventEventsEventIdGetResponse, GetAttendingEventsEventsGetError, GetAttendingEventsEventsGetResponse, CreateEventEventsPostData, CreateEventEventsPostError, CreateEventEventsPostResponse, AttendEventEventsAttendPostData, AttendEventEventsAttendPostError, AttendEventEventsAttendPostResponse, VoteEventsVotePostData, VoteEventsVotePostError, VoteEventsVotePostResponse, GetLeaderboardEventsEventIdLeaderboardGetData, GetLeaderboardEventsEventIdLeaderboardGetError, GetLeaderboardEventsEventIdLeaderboardGetResponse, GetEventProjectsEventsEventIdProjectsGetData, GetEventProjectsEventsEventIdProjectsGetError, GetEventProjectsEventsEventIdProjectsGetResponse, CreateProjectProjectsPostData, CreateProjectProjectsPostError, CreateProjectProjectsPostResponse, GetProjectProjectsProjectIdGetData, GetProjectProjectsProjectIdGetError, GetProjectProjectsProjectIdGetResponse, CreateUserUsersPostData, CreateUserUsersPostError, CreateUserUsersPostResponse, UserExistsUsersExistsGetData, UserExistsUsersExistsGetError, UserExistsUsersExistsGetResponse } from './types.gen'; export const client = createClient(createConfig()); export class AuthService { - /** - * Request Login - * Send a magic link to the user's email. If the user has not yet signed up, an error will be raised - */ - public static requestLoginRequestLoginPost< - ThrowOnError extends boolean = false, - >( - options: OptionsLegacyParser< - RequestLoginRequestLoginPostData, - ThrowOnError - >, - ) { - return (options?.client ?? client).post< - RequestLoginRequestLoginPostResponse, - RequestLoginRequestLoginPostError, - ThrowOnError - >({ - ...options, - url: "/request-login", - }); - } - - /** - * Verify Token - * Verify a magic link and return an access token - */ - public static verifyTokenVerifyGet( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).get< - VerifyTokenVerifyGetResponse, - VerifyTokenVerifyGetError, - ThrowOnError - >({ - ...options, - url: "/verify", - }); - } - - /** - * Protected Route - */ - public static protectedRouteProtectedRouteGet< - ThrowOnError extends boolean = false, - >(options?: OptionsLegacyParser) { - return (options?.client ?? client).get< - ProtectedRouteProtectedRouteGetResponse, - ProtectedRouteProtectedRouteGetError, - ThrowOnError - >({ - ...options, - url: "/protected-route", - }); - } + /** + * Request Login + * Send a magic link to the user's email. If the user has not yet signed up, an error will be raised + */ + public static requestLoginRequestLoginPost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/request-login' + }); + } + + /** + * Verify Token + * Verify a magic link and return an access token + */ + public static verifyTokenVerifyGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/verify' + }); + } + + /** + * Protected Route + */ + public static protectedRouteProtectedRouteGet(options?: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/protected-route' + }); + } + } export class EventsService { - /** - * Get Event - * Get an event by its ID. If the user owns it, return a complex event. Otherwise, return a regular event. - */ - public static getEventEventsEventIdGet( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).get< - GetEventEventsEventIdGetResponse, - GetEventEventsEventIdGetError, - ThrowOnError - >({ - ...options, - url: "/events/{event_id}", - }); - } - - /** - * Get Attending Events - * Get a list of all events that the current user is attending. - */ - public static getAttendingEventsEventsGet< - ThrowOnError extends boolean = false, - >(options?: OptionsLegacyParser) { - return (options?.client ?? client).get< - GetAttendingEventsEventsGetResponse, - GetAttendingEventsEventsGetError, - ThrowOnError - >({ - ...options, - url: "/events/", - }); - } - - /** - * Create Event - * Create a new event. The current user is automatically added as an owner of the event. - */ - public static createEventEventsPost( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).post< - CreateEventEventsPostResponse, - CreateEventEventsPostError, - ThrowOnError - >({ - ...options, - url: "/events/", - }); - } - - /** - * Attend Event - * Attend an event. The client must supply a join code that matches the event's join code. - */ - public static attendEventEventsAttendPost< - ThrowOnError extends boolean = false, - >( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).post< - AttendEventEventsAttendPostResponse, - AttendEventEventsAttendPostError, - ThrowOnError - >({ - ...options, - url: "/events/attend", - }); - } - - /** - * Vote - * Vote for the top 3 projects in an event. The client must provide the event ID and a list of the top 3 projects. If there are less than 20 projects in the event, only the top 2 projects are required. - */ - public static voteEventsVotePost( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).post< - VoteEventsVotePostResponse, - VoteEventsVotePostError, - ThrowOnError - >({ - ...options, - url: "/events/vote", - }); - } - - /** - * Get Leaderboard - * Get the leaderboard for an event. The leaderboard is a list of projects in the event, sorted by the number of votes they have received. - */ - public static getLeaderboardEventsEventIdLeaderboardGet< - ThrowOnError extends boolean = false, - >( - options: OptionsLegacyParser< - GetLeaderboardEventsEventIdLeaderboardGetData, - ThrowOnError - >, - ) { - return (options?.client ?? client).get< - GetLeaderboardEventsEventIdLeaderboardGetResponse, - GetLeaderboardEventsEventIdLeaderboardGetError, - ThrowOnError - >({ - ...options, - url: "/events/{event_id}/leaderboard", - }); - } - - /** - * Get Event Projects - * Get the projects for a specific event. - */ - public static getEventProjectsEventsEventIdProjectsGet< - ThrowOnError extends boolean = false, - >( - options: OptionsLegacyParser< - GetEventProjectsEventsEventIdProjectsGetData, - ThrowOnError - >, - ) { - return (options?.client ?? client).get< - GetEventProjectsEventsEventIdProjectsGetResponse, - GetEventProjectsEventsEventIdProjectsGetError, - ThrowOnError - >({ - ...options, - url: "/events/{event_id}/projects", - }); - } + /** + * Get Event + * Get an event by its ID. If the user owns it, return a complex event. Otherwise, return a regular event. + */ + public static getEventEventsEventIdGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/events/{event_id}' + }); + } + + /** + * Get Attending Events + * Get a list of all events that the current user is attending. + */ + public static getAttendingEventsEventsGet(options?: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/events/' + }); + } + + /** + * Create Event + * Create a new event. The current user is automatically added as an owner of the event. + */ + public static createEventEventsPost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/events/' + }); + } + + /** + * Attend Event + * Attend an event. The client must supply a join code that matches the event's join code. + */ + public static attendEventEventsAttendPost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/events/attend' + }); + } + + /** + * Vote + * Vote for the top 3 projects in an event. The client must provide the event ID and a list of the top 3 projects. If there are less than 20 projects in the event, only the top 2 projects are required. + */ + public static voteEventsVotePost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/events/vote' + }); + } + + /** + * Get Leaderboard + * Get the leaderboard for an event. The leaderboard is a list of projects in the event, sorted by the number of votes they have received. + */ + public static getLeaderboardEventsEventIdLeaderboardGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/events/{event_id}/leaderboard' + }); + } + + /** + * Get Event Projects + * Get the projects for a specific event. + */ + public static getEventProjectsEventsEventIdProjectsGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/events/{event_id}/projects' + }); + } + } export class ProjectsService { - /** - * Create Project - * Create a new project. The current user is automatically added as an owner of the project. - */ - public static createProjectProjectsPost( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).post< - CreateProjectProjectsPostResponse, - CreateProjectProjectsPostError, - ThrowOnError - >({ - ...options, - url: "/projects/", - }); - } - - /** - * Get Project - */ - public static getProjectProjectsProjectIdGet< - ThrowOnError extends boolean = false, - >( - options: OptionsLegacyParser< - GetProjectProjectsProjectIdGetData, - ThrowOnError - >, - ) { - return (options?.client ?? client).get< - GetProjectProjectsProjectIdGetResponse, - GetProjectProjectsProjectIdGetError, - ThrowOnError - >({ - ...options, - url: "/projects/{project_id}", - }); - } + /** + * Create Project + * Create a new project. The current user is automatically added as an owner of the project. + */ + public static createProjectProjectsPost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/projects/' + }); + } + + /** + * Get Project + */ + public static getProjectProjectsProjectIdGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/projects/{project_id}' + }); + } + } export class UsersService { - /** - * Create User - */ - public static createUserUsersPost( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).post< - CreateUserUsersPostResponse, - CreateUserUsersPostError, - ThrowOnError - >({ - ...options, - url: "/users/", - }); - } - - /** - * User Exists - */ - public static userExistsUsersExistsGet( - options: OptionsLegacyParser, - ) { - return (options?.client ?? client).get< - UserExistsUsersExistsGetResponse, - UserExistsUsersExistsGetError, - ThrowOnError - >({ - ...options, - url: "/users/exists", - }); - } -} + /** + * Create User + */ + public static createUserUsersPost(options: OptionsLegacyParser) { + return (options?.client ?? client).post({ + ...options, + url: '/users/' + }); + } + + /** + * User Exists + */ + public static userExistsUsersExistsGet(options: OptionsLegacyParser) { + return (options?.client ?? client).get({ + ...options, + url: '/users/exists' + }); + } + +} \ No newline at end of file diff --git a/frontend/src/lib/client/types.gen.ts b/frontend/src/lib/client/types.gen.ts index 04c99c9..59057e4 100644 --- a/frontend/src/lib/client/types.gen.ts +++ b/frontend/src/lib/client/types.gen.ts @@ -1,56 +1,60 @@ // This file is auto-generated by @hey-api/openapi-ts export type CheckAuthResponse = { - email: string; + email: string; }; export type ComplexEvent = { - name: string; - description: string | null; - join_code: string; - id: string; - attendees?: Array; + name: string; + description: (string | null); + join_code: string; + id: string; + attendees?: Array<(string)>; }; export type Event = { - name: string; - description: string | null; - id: string; + name: string; + description: (string | null); + id: string; }; export type EventCreationPayload = { - name: string; - description: string | null; + name: string; + description: (string | null); }; export type HTTPValidationError = { - detail?: Array; + detail?: Array; }; export type MagicLinkVerificationResponse = { - access_token: string; - token_type: string; - email: string; + access_token: string; + token_type: string; + email: string; }; export type Project = { - name: string; - readme: string; - repo: string; - image_url: string; - description?: string | null; - event: Array; - id: string; - points: number; + name: string; + readme: string; + repo: string; + image_url: string; + description?: (string | null); + event?: [ + string + ]; + id: string; + points: number; }; export type ProjectCreationPayload = { - name: string; - readme: string; - repo: string; - image_url: string; - description?: string | null; - event: Array; + name: string; + readme: string; + repo: string; + image_url: string; + description?: (string | null); + event?: [ + string + ]; }; /** @@ -66,171 +70,170 @@ export type ProjectCreationPayload = { * } */ export type RecordDict = { - id: string; - createdTime: string; - fields: { - [key: string]: unknown; - }; + id: string; + createdTime: string; + fields: { + [key: string]: unknown; + }; }; export type User = { - email: string; + email: string; }; /** * Return information regarding what the events the user owns and what events they are attending. If they are only attending an event, don't return sensitive information like participants. */ export type UserEvents = { - owned_events: Array; - attending_events: Array; + owned_events: Array; + attending_events: Array; }; export type UserExistsResponse = { - exists: boolean; + exists: boolean; }; export type UserSignupPayload = { - first_name: string; - last_name: string; - email: string; - mailing_address: string; + first_name: string; + last_name: string; + email: string; + mailing_address: string; }; export type ValidationError = { - loc: Array; - msg: string; - type: string; + loc: Array<(string | number)>; + msg: string; + type: string; }; export type Vote = { - /** - * The ID of the event to vote in. - */ - event_id: string; - /** - * In no particular order, the top 3 (or 2 if there are less than 20 projects) projects that the user is voting for. - */ - projects: Array; + /** + * The ID of the event to vote in. + */ + event_id: string; + /** + * In no particular order, the top 3 (or 2 if there are less than 20 projects) projects that the user is voting for. + */ + projects: Array<(string)>; }; export type RequestLoginRequestLoginPostData = { - body: User; + body: User; }; -export type RequestLoginRequestLoginPostResponse = unknown; +export type RequestLoginRequestLoginPostResponse = (unknown); -export type RequestLoginRequestLoginPostError = HTTPValidationError; +export type RequestLoginRequestLoginPostError = (HTTPValidationError); export type VerifyTokenVerifyGetData = { - query: { - token: string; - }; + query: { + token: string; + }; }; -export type VerifyTokenVerifyGetResponse = MagicLinkVerificationResponse; +export type VerifyTokenVerifyGetResponse = (MagicLinkVerificationResponse); -export type VerifyTokenVerifyGetError = HTTPValidationError; +export type VerifyTokenVerifyGetError = (HTTPValidationError); -export type ProtectedRouteProtectedRouteGetResponse = CheckAuthResponse; +export type ProtectedRouteProtectedRouteGetResponse = (CheckAuthResponse); export type ProtectedRouteProtectedRouteGetError = unknown; export type GetEventEventsEventIdGetData = { - path: { - event_id: string; - }; + path: { + event_id: string; + }; }; -export type GetEventEventsEventIdGetResponse = ComplexEvent | Event; +export type GetEventEventsEventIdGetResponse = ((ComplexEvent | Event)); -export type GetEventEventsEventIdGetError = HTTPValidationError; +export type GetEventEventsEventIdGetError = (HTTPValidationError); -export type GetAttendingEventsEventsGetResponse = UserEvents; +export type GetAttendingEventsEventsGetResponse = (UserEvents); export type GetAttendingEventsEventsGetError = unknown; export type CreateEventEventsPostData = { - body: EventCreationPayload; + body: EventCreationPayload; }; -export type CreateEventEventsPostResponse = unknown; +export type CreateEventEventsPostResponse = (unknown); -export type CreateEventEventsPostError = HTTPValidationError; +export type CreateEventEventsPostError = (HTTPValidationError); export type AttendEventEventsAttendPostData = { - query: { - /** - * A unique code used to join an event - */ - join_code: string; - }; + query: { + /** + * A unique code used to join an event + */ + join_code: string; + }; }; -export type AttendEventEventsAttendPostResponse = unknown; +export type AttendEventEventsAttendPostResponse = (unknown); -export type AttendEventEventsAttendPostError = HTTPValidationError; +export type AttendEventEventsAttendPostError = (HTTPValidationError); export type VoteEventsVotePostData = { - body: Vote; + body: Vote; }; -export type VoteEventsVotePostResponse = unknown; +export type VoteEventsVotePostResponse = (unknown); -export type VoteEventsVotePostError = HTTPValidationError; +export type VoteEventsVotePostError = (HTTPValidationError); export type GetLeaderboardEventsEventIdLeaderboardGetData = { - path: { - event_id: string; - }; + path: { + event_id: string; + }; }; -export type GetLeaderboardEventsEventIdLeaderboardGetResponse = Array; +export type GetLeaderboardEventsEventIdLeaderboardGetResponse = (Array); -export type GetLeaderboardEventsEventIdLeaderboardGetError = - HTTPValidationError; +export type GetLeaderboardEventsEventIdLeaderboardGetError = (HTTPValidationError); export type GetEventProjectsEventsEventIdProjectsGetData = { - path: { - event_id: string; - }; + path: { + event_id: string; + }; }; -export type GetEventProjectsEventsEventIdProjectsGetResponse = Array; +export type GetEventProjectsEventsEventIdProjectsGetResponse = (Array); -export type GetEventProjectsEventsEventIdProjectsGetError = HTTPValidationError; +export type GetEventProjectsEventsEventIdProjectsGetError = (HTTPValidationError); export type CreateProjectProjectsPostData = { - body: ProjectCreationPayload; + body: ProjectCreationPayload; }; -export type CreateProjectProjectsPostResponse = unknown; +export type CreateProjectProjectsPostResponse = (unknown); -export type CreateProjectProjectsPostError = HTTPValidationError; +export type CreateProjectProjectsPostError = (HTTPValidationError); export type GetProjectProjectsProjectIdGetData = { - path: { - project_id: string; - }; + path: { + project_id: string; + }; }; -export type GetProjectProjectsProjectIdGetResponse = unknown; +export type GetProjectProjectsProjectIdGetResponse = (unknown); -export type GetProjectProjectsProjectIdGetError = HTTPValidationError; +export type GetProjectProjectsProjectIdGetError = (HTTPValidationError); export type CreateUserUsersPostData = { - body: UserSignupPayload; + body: UserSignupPayload; }; -export type CreateUserUsersPostResponse = unknown; +export type CreateUserUsersPostResponse = (unknown); -export type CreateUserUsersPostError = HTTPValidationError; +export type CreateUserUsersPostError = (HTTPValidationError); export type UserExistsUsersExistsGetData = { - query: { - email: string; - }; + query: { + email: string; + }; }; -export type UserExistsUsersExistsGetResponse = UserExistsResponse; +export type UserExistsUsersExistsGetResponse = (UserExistsResponse); -export type UserExistsUsersExistsGetError = HTTPValidationError; +export type UserExistsUsersExistsGetError = (HTTPValidationError); \ No newline at end of file diff --git a/frontend/src/lib/components/AttendEvent.svelte b/frontend/src/lib/components/AttendEvent.svelte index e58f869..fe9e46c 100644 --- a/frontend/src/lib/components/AttendEvent.svelte +++ b/frontend/src/lib/components/AttendEvent.svelte @@ -1,43 +1,43 @@
- - - + - -
\ No newline at end of file + + diff --git a/frontend/src/lib/components/CreateEvent.svelte b/frontend/src/lib/components/CreateEvent.svelte index 0393c93..a8eec3b 100644 --- a/frontend/src/lib/components/CreateEvent.svelte +++ b/frontend/src/lib/components/CreateEvent.svelte @@ -1,42 +1,45 @@
- - - - - - -
\ No newline at end of file + + + + + + + diff --git a/frontend/src/lib/components/CreateProject.svelte b/frontend/src/lib/components/CreateProject.svelte index 6053f22..4f75180 100644 --- a/frontend/src/lib/components/CreateProject.svelte +++ b/frontend/src/lib/components/CreateProject.svelte @@ -1,97 +1,102 @@
- - - - - - - - - -
\ No newline at end of file + + + + + + + + + + diff --git a/frontend/src/lib/components/ProjectCard.svelte b/frontend/src/lib/components/ProjectCard.svelte index c83f475..78df70b 100644 --- a/frontend/src/lib/components/ProjectCard.svelte +++ b/frontend/src/lib/components/ProjectCard.svelte @@ -1,34 +1,37 @@ diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 934ed41..4355889 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -1,36 +1,36 @@ - {#if page.data.title} - {page.data.title} | Showcase - {:else} - Showcase - {/if} - {#if page.data.meta} - {#each page.data.meta as {name, content}} - + {#if page.data.title} + {page.data.title} | Showcase + {:else} + Showcase + {/if} + {#if page.data.meta} + {#each page.data.meta as { name, content }} + {/each} - {:else} - - {/if} + {:else} + + {/if} {@render children()} - \ No newline at end of file + diff --git a/frontend/src/routes/+page.svelte b/frontend/src/routes/+page.svelte index fa05a22..9163480 100644 --- a/frontend/src/routes/+page.svelte +++ b/frontend/src/routes/+page.svelte @@ -1,45 +1,51 @@ -
+
+

Login

+ {#if user.isAuthenticated} +
+

Hey!

+

+ You're signed in as {user.email}. +

+ +
+ {:else} + + {/if} +
+ + {#if user.isAuthenticated}
-

Login

- {#if user.isAuthenticated} -
-

Hey!

-

- You're signed in as {user.email}. -

- -
- {:else} - - {/if} +

Events

+ + Events Dashboard +
- {#if user.isAuthenticated} -
-

Events

- - Events Dashboard - -
- -
-

Create Project

- -
+
+

Create Project

+ +
-
-

Attend Event

- -
- {/if} -
\ No newline at end of file +
+

Attend Event

+ +
+ {/if} + diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index e771b8f..ec999ce 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -1,236 +1,234 @@
- {#if user.isAuthenticated} -
-

- You are logged in as {user.email} -

- -
- {:else} -
-

Welcome

-

- Sign in with your email to continue -

-
+ {#if user.isAuthenticated} +
+

+ You are logged in as {user.email} +

+ +
+ {:else} +
+

Welcome

+

Sign in with your email to continue

+
-
- -
-
- - -

- We'll send you a magic link to sign in -

-
- {#if showSignupFields} -
- - -
-
- - -
-
- - -
- {/if} -
- -
-
-
-
- ← Back to Home +
+ +
+
+ + +

+ We'll send you a magic link to sign in +

+
+ {#if showSignupFields} +
+ + +
+
+ + +
+
+ + +
+ {/if} +
+
- {/if} +
+
+ + {/if}