Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions cmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ func (app *application) mount() http.Handler {
r.Get("/reviews/completed", app.getCompletedReviews)

// Scans
r.Get("/scans/types", app.getScanTypesHandler)
r.Post("/scans", app.createScanHandler)
r.Get("/scans/user/{userID}", app.getUserScansHandler)
r.Get("/scans/stats", app.getScanStatsHandler)

// Hacker Pack

Expand All @@ -164,6 +168,12 @@ func (app *application) mount() http.Handler {
r.Post("/applications/assign", app.batchAssignReviews)
r.Get("/applications/emails", app.getApplicantEmailsByStatusHandler)
r.Patch("/applications/{applicationID}/status", app.setApplicationStatus)

// Scans Config
r.Put("/settings/scan-types", app.updateScanTypesHandler)

// Emails
r.Post("/emails/qr", app.sendQREmailsHandler)
})
})
})
Expand Down
231 changes: 116 additions & 115 deletions cmd/api/applications.go
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you intentionally remove the first line of the headers:
Examples:

// getOrCreateApplicationHandler returns the user's application, creating a draft if none exists

// updateApplicationHandler updates the user's draft application

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh yeah I feel like the swagger descriptions pretty self explanatory already. Also I don't like when comments restate the function. Lowkey claudes faults.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait claudes smarter than me... Apparently it's needed 🫩. I'll go add them again.

Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ type UpdateApplicationPayload struct {
DietaryRestrictions *[]string `json:"dietary_restrictions"`
Accommodations *string `json:"accommodations"`

// Social/Professional Links (all optional)
Github *string `json:"github" validate:"omitempty,url"`
LinkedIn *string `json:"linkedin" validate:"omitempty,url"`
Website *string `json:"website" validate:"omitempty,url"`
Expand All @@ -48,24 +47,24 @@ type UpdateApplicationPayload struct {
OptInMLHEmails *bool `json:"opt_in_mlh_emails"`
}

// ApplicationWithQuestions embeds questions in the response for the hacker
// SAQs embeds questions in the response for the hacker
type ApplicationWithQuestions struct {
*store.Application
ShortAnswerQuestions []store.ShortAnswerQuestion `json:"short_answer_questions"`
}

// getOrCreateApplicationHandler returns the user's application, creating a draft if none exists
// getOrCreateApplicationHandler returns or creates the user's hackathon application
//
// @Summary Get or create application
// @Description Returns the authenticated user's hackathon application. If no application exists, creates a new draft application.
// @Tags applications
// @Accept json
// @Produce json
// @Success 200 {object} store.Application
// @Failure 401 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /applications/me [get]
// @Summary Get or create application
// @Description Returns the authenticated user's hackathon application. If no application exists, creates a new draft application.
// @Tags applications
// @Accept json
// @Produce json
// @Success 200 {object} store.Application
// @Failure 401 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /applications/me [get]
func (app *application) getOrCreateApplicationHandler(w http.ResponseWriter, r *http.Request) {
user := getUserFromContext(r.Context())
if user == nil {
Expand Down Expand Up @@ -115,21 +114,21 @@ func (app *application) getOrCreateApplicationHandler(w http.ResponseWriter, r *
}
}

// updateApplicationHandler updates the user's draft application
// updateApplicationHandler partially updates the authenticated user's application
//
// @Summary Update application
// @Description Partially updates the authenticated user's application. Only fields included in the request body are updated. Application must be in draft status.
// @Tags applications
// @Accept json
// @Produce json
// @Param application body UpdateApplicationPayload true "Fields to update"
// @Success 200 {object} store.Application
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 409 {object} object{error=string} "Application not in draft status"
// @Security CookieAuth
// @Router /applications/me [patch]
// @Summary Update application
// @Description Partially updates the authenticated user's application. Only fields included in the request body are updated. Application must be in draft status.
// @Tags applications
// @Accept json
// @Produce json
// @Param application body UpdateApplicationPayload true "Fields to update"
// @Success 200 {object} store.Application
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 409 {object} object{error=string} "Application not in draft status"
// @Security CookieAuth
// @Router /applications/me [patch]
func (app *application) updateApplicationHandler(w http.ResponseWriter, r *http.Request) {
user := getUserFromContext(r.Context())
if user == nil {
Expand Down Expand Up @@ -250,19 +249,19 @@ func (app *application) updateApplicationHandler(w http.ResponseWriter, r *http.
}
}

// submitApplicationHandler submits the user's draft application
// submitApplicationHandler submits the authenticated user's application for review
//
// @Summary Submit application
// @Description Submits the authenticated user's application for review. All required fields must be filled and acknowledgments must be accepted. Application must be in draft status.
// @Tags applications
// @Produce json
// @Success 200 {object} store.Application
// @Failure 400 {object} object{error=string} "Missing required fields"
// @Failure 401 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 409 {object} object{error=string} "Application not in draft status"
// @Security CookieAuth
// @Router /applications/me/submit [post]
// @Summary Submit application
// @Description Submits the authenticated user's application for review. All required fields must be filled and acknowledgments must be accepted. Application must be in draft status.
// @Tags applications
// @Produce json
// @Success 200 {object} store.Application
// @Failure 400 {object} object{error=string} "Missing required fields"
// @Failure 401 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 409 {object} object{error=string} "Application not in draft status"
// @Security CookieAuth
// @Router /applications/me/submit [post]
func (app *application) submitApplicationHandler(w http.ResponseWriter, r *http.Request) {
user := getUserFromContext(r.Context())
if user == nil {
Expand Down Expand Up @@ -387,18 +386,18 @@ func (app *application) submitApplicationHandler(w http.ResponseWriter, r *http.
}
}

// getApplicationStatsHandler returns aggregated application statistics
// getApplicationStatsHandler returns aggregated statistics for all applications
//
// @Summary Get application stats (Admin)
// @Description Returns aggregated statistics for all applications
// @Tags admin
// @Produce json
// @Success 200 {object} store.ApplicationStats
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications/stats [get]
// @Summary Get application stats (Admin)
// @Description Returns aggregated statistics for all applications
// @Tags admin
// @Produce json
// @Success 200 {object} store.ApplicationStats
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications/stats [get]
func (app *application) getApplicationStatsHandler(w http.ResponseWriter, r *http.Request) {
stats, err := app.store.Application.GetStats(r.Context())
if err != nil {
Expand All @@ -411,23 +410,23 @@ func (app *application) getApplicationStatsHandler(w http.ResponseWriter, r *htt
}
}

// listApplicationsHandler lists applications with cursor pagination for admins
// listApplicationsHandler lists all applications with cursor-based pagination
//
// @Summary List applications (Admin)
// @Description Lists all applications with cursor-based pagination and optional status filter
// @Tags admin
// @Produce json
// @Param cursor query string false "Pagination cursor"
// @Param status query string false "Filter by status (draft, submitted, accepted, rejected, waitlisted)"
// @Param limit query int false "Page size (default 50, max 100)"
// @Param direction query string false "Pagination direction: forward (default) or backward"
// @Success 200 {object} store.ApplicationListResult
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications [get]
// @Summary List applications (Admin)
// @Description Lists all applications with cursor-based pagination and optional status filter
// @Tags admin
// @Produce json
// @Param cursor query string false "Pagination cursor"
// @Param status query string false "Filter by status (draft, submitted, accepted, rejected, waitlisted)"
// @Param limit query int false "Page size (default 50, max 100)"
// @Param direction query string false "Pagination direction: forward (default) or backward"
// @Success 200 {object} store.ApplicationListResult
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications [get]
func (app *application) listApplicationsHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()

Expand Down Expand Up @@ -503,23 +502,23 @@ type EmailListResponse struct {
Count int `json:"count"`
}

// setApplicationStatus sets the final status on an application (superadmin only)
// setApplicationStatus sets the final status on an application
//
// @Summary Set application status (Super Admin)
// @Description Sets the final status (accepted, rejected, or waitlisted) on an application
// @Tags superadmin
// @Accept json
// @Produce json
// @Param applicationID path string true "Application ID"
// @Param status body SetStatusPayload true "New status"
// @Success 200 {object} ApplicationResponse
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /superadmin/applications/{applicationID}/status [patch]
// @Summary Set application status (Super Admin)
// @Description Sets the final status (accepted, rejected, or waitlisted) on an application
// @Tags superadmin
// @Accept json
// @Produce json
// @Param applicationID path string true "Application ID"
// @Param status body SetStatusPayload true "New status"
// @Success 200 {object} ApplicationResponse
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /superadmin/applications/{applicationID}/status [patch]
func (app *application) setApplicationStatus(w http.ResponseWriter, r *http.Request) {
applicationID := chi.URLParam(r, "applicationID")
if applicationID == "" {
Expand Down Expand Up @@ -553,20 +552,20 @@ func (app *application) setApplicationStatus(w http.ResponseWriter, r *http.Requ
}
}

// getApplication returns a single application by ID for admin review
// getApplication returns a single application by ID with embedded questions
//
// @Summary Get application by ID (Admin)
// @Description Returns a single application by its ID with embedded short answer questions
// @Tags admin
// @Produce json
// @Param applicationID path string true "Application ID"
// @Success 200 {object} ApplicationWithQuestions
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications/{applicationID} [get]
// @Summary Get application by ID (Admin)
// @Description Returns a single application by its ID with embedded short answer questions
// @Tags admin
// @Produce json
// @Param applicationID path string true "Application ID"
// @Success 200 {object} ApplicationWithQuestions
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 404 {object} object{error=string}
// @Security CookieAuth
// @Router /admin/applications/{applicationID} [get]
func (app *application) getApplication(w http.ResponseWriter, r *http.Request) {
applicationID := chi.URLParam(r, "applicationID")
if applicationID == "" {
Expand Down Expand Up @@ -602,44 +601,46 @@ func (app *application) getApplication(w http.ResponseWriter, r *http.Request) {
}
}

// getApplicantEmailsByStatusHandler returns emails of applicants filtered by status
// getApplicantEmailsByStatusHandler returns applicant emails filtered by status
//
// @Summary Get applicant emails by status (Super Admin)
// @Description Returns a list of applicant emails filtered by application status (accepted, rejected, or waitlisted)
// @Tags superadmin
// @Produce json
// @Param status query string true "Application status (accepted, rejected, or waitlisted)"
// @Success 200 {object} EmailListResponse
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /superadmin/applications/emails [get]
// @Summary Get applicant emails by status (Super Admin)
// @Description Returns a list of applicant emails filtered by application status (accepted, rejected, or waitlisted)
// @Tags superadmin
// @Produce json
// @Param status query string true "Application status (accepted, rejected, or waitlisted)"
// @Success 200 {object} EmailListResponse
// @Failure 400 {object} object{error=string}
// @Failure 401 {object} object{error=string}
// @Failure 403 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /superadmin/applications/emails [get]
func (app *application) getApplicantEmailsByStatusHandler(w http.ResponseWriter, r *http.Request) {
statusStr := r.URL.Query().Get("status")
if statusStr == "" {
app.badRequestResponse(w, r, errors.New("status is required"))
return
}

var emails []string
var err error

switch status := store.ApplicationStatus(statusStr); status {
case store.StatusAccepted,
store.StatusRejected, store.StatusWaitlisted:
emails, err = app.store.Application.GetEmailsByStatus(r.Context(), status)
status := store.ApplicationStatus(statusStr)
switch status {
case store.StatusAccepted, store.StatusRejected, store.StatusWaitlisted:
default:
app.badRequestResponse(w, r, errors.New("status must be one of accepted, rejected, or waitlisted"))
return
}

users, err := app.store.Application.GetEmailsByStatus(r.Context(), status)
if err != nil {
app.internalServerError(w, r, err)
return
}

emails := make([]string, len(users))
for i, u := range users {
emails[i] = u.Email
}

response := EmailListResponse{
Emails: emails,
Count: len(emails),
Expand Down
21 changes: 11 additions & 10 deletions cmd/api/auth.go
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same questions about the first line

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"github.com/hackutd/portal/internal/store"
)

// JSON response for user data
type UserResponse struct {
ID string `json:"id"`
Email string `json:"email"`
Expand All @@ -19,16 +18,16 @@ type UserResponse struct {
UpdatedAt time.Time `json:"updatedAt"`
}

// getCurrentUserHandler returns the currently authenticated user
// getCurrentUserHandler returns the authenticated user's profile
//
// @Summary Get current user
// @Description Returns the authenticated user's profile
// @Tags auth
// @Accept json
// @Produce json
// @Success 200 {object} UserResponse
// @Failure 401 {object} object{error=string}
// @Router /auth/me [get]
// @Summary Get current user
// @Description Returns the authenticated user's profile
// @Tags auth
// @Accept json
// @Produce json
// @Success 200 {object} UserResponse
// @Failure 401 {object} object{error=string}
// @Router /auth/me [get]
func (app *application) getCurrentUserHandler(w http.ResponseWriter, r *http.Request) {
user := getUserFromContext(r.Context())
if user == nil {
Expand Down Expand Up @@ -56,6 +55,8 @@ type CheckEmailResponse struct {
AuthMethod *store.AuthMethod `json:"auth_method,omitempty"`
}

// checkEmailAuthMethodHandler checks if an email is registered and returns the auth method
//
// @Summary Check email auth method
// @Description Checks if an email is registered and returns the auth method used
// @Tags auth
Expand Down
Loading