Skip to content
Open
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
2 changes: 2 additions & 0 deletions cmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func (app *application) mount() http.Handler {
r.Get("/me", app.getOrCreateApplicationHandler)
r.Patch("/me", app.updateApplicationHandler)
r.Post("/me/submit", app.submitApplicationHandler)
r.Get("/enabled", app.getApplicationsEnabled)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move this to admin routes. gonna change implementation.

r.Post("/me/resume-upload-url", app.generateResumeUploadURLHandler)
r.Delete("/me/resume", app.deleteResumeHandler)
})
Expand Down Expand Up @@ -239,6 +240,7 @@ func (app *application) mount() http.Handler {
r.Get("/hackathon-date-range", app.getHackathonDateRange)
r.Post("/hackathon-date-range", app.setHackathonDateRange)
r.Put("/scan-types", app.updateScanTypesHandler)
r.Put("/applications-enabled", app.setApplicationsEnabled)
})

r.Route("/applications", func(r chi.Router) {
Expand Down
198 changes: 135 additions & 63 deletions cmd/api/applications.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,16 @@ type ApplicationWithQuestions struct {

// 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 hackers
// @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 hackers
// @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 @@ -117,19 +117,19 @@ func (app *application) getOrCreateApplicationHandler(w http.ResponseWriter, r *

// 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 hackers
// @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 hackers
// @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 @@ -255,17 +255,17 @@ func (app *application) updateApplicationHandler(w http.ResponseWriter, r *http.

// 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 hackers
// @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 hackers
// @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 @@ -392,16 +392,16 @@ func (app *application) submitApplicationHandler(w http.ResponseWriter, r *http.

// getApplicationStatsHandler returns aggregated statistics for all applications
//
// @Summary Get application stats (Admin)
// @Description Returns aggregated statistics for all applications
// @Tags admin/applications
// @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/applications
// @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 @@ -416,22 +416,22 @@ func (app *application) getApplicationStatsHandler(w http.ResponseWriter, r *htt

// 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/applications
// @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"
// @Param sort_by query string false "Sort column: created_at (default), accept_votes, reject_votes, waitlist_votes"
// @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/applications
// @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"
// @Param sort_by query string false "Sort column: created_at (default), accept_votes, reject_votes, waitlist_votes"
// @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 @@ -538,11 +538,19 @@ type EmailListResponse struct {
Count int `json:"count"`
}

type ApplicationsEnabledResponse struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Get and set response are the same struct just keep them as one ApplicationsEnabledResponse

Enabled bool `json:"enabled"`
}

type SetApplicationsEnabledResponse struct {
Enabled bool `json:"enabled"` //no validate required because bool enforces the only two possible values, which is what we want
}

// 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/applications
// @Tags superadmin
// @Accept json
// @Produce json
// @Param applicationID path string true "Application ID"
Expand Down Expand Up @@ -592,7 +600,7 @@ func (app *application) setApplicationStatus(w http.ResponseWriter, r *http.Requ
//
// @Summary Get application by ID (Admin)
// @Description Returns a single application by its ID with embedded short answer questions
// @Tags admin/applications
// @Tags admin
// @Produce json
// @Param applicationID path string true "Application ID"
// @Success 200 {object} ApplicationWithQuestions
Expand Down Expand Up @@ -641,7 +649,7 @@ func (app *application) getApplication(w http.ResponseWriter, r *http.Request) {
//
// @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/applications
// @Tags superadmin
// @Produce json
// @Param status query string true "Application status (accepted, rejected, or waitlisted)"
// @Success 200 {object} EmailListResponse
Expand Down Expand Up @@ -691,3 +699,67 @@ func (app *application) getApplicantEmailsByStatusHandler(w http.ResponseWriter,
}

}

// getApplicationsEnabled returns whether applications are currently open
//
// @Summary Get applications enabled status
// @Description Returns whether the application portal is currently open for submissions
// @Tags applications
// @Produce json
// @Success 200 {object} ApplicationsEnabledResponse
// @Failure 401 {object} object{error=string}
// @Failure 500 {object} object{error=string}
// @Security CookieAuth
// @Router /applications/enabled [get]
func (app *application) getApplicationsEnabled(w http.ResponseWriter, r *http.Request) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move these handlers to settings.go

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

response := ApplicationsEnabledResponse{
Enabled: enabled,
}

if err = app.jsonResponse(w, http.StatusOK, response); err != nil {
app.internalServerError(w, r, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

add a return

}
}

// setApplicationsEnabled updates whether applications are currently open
//
// @Summary Set applications enabled status
// @Description Sets whether the application portal is currently open for submissions. Requires SuperAdmin privileges.
// @Tags superadmin
// @Produce json
// @Param enabled query bool true "Enable or disable applications"
// @Success 200 {object} SetApplicationsEnabledResponse
// @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/settings/applications-enabled [put]
func (app *application) setApplicationsEnabled(w http.ResponseWriter, r *http.Request) {
enabled, err := strconv.ParseBool(r.URL.Query().Get("enabled"))

if err != nil {
app.badRequestResponse(w, r, errors.New("enabled must be a boolean value"))
return
}

enabled, err = app.store.Application.SetApplicationsEnabled(r.Context(), enabled)
if err != nil {
app.internalServerError(w, r, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

add a return

}

//NOTE: Following existing design pattern of Get response and Set response structs
response := SetApplicationsEnabledResponse{
Enabled: enabled,
}

if err = app.jsonResponse(w, http.StatusOK, response); err != nil {
app.internalServerError(w, r, err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Another return here

}
}
34 changes: 17 additions & 17 deletions cmd/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ type UserResponse struct {

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

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

ugh this is an issue were consistently having I'll standardize it to be tabs in the future. This is good tho

// @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 @@ -57,15 +57,15 @@ type CheckEmailResponse struct {

// 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
// @Accept json
// @Produce json
// @Param email query string true "Email address to check"
// @Success 200 {object} CheckEmailResponse
// @Failure 400 {object} object{error=string}
// @Router /auth/check-email [get]
// @Summary Check email auth method
// @Description Checks if an email is registered and returns the auth method used
// @Tags auth
// @Accept json
// @Produce json
// @Param email query string true "Email address to check"
// @Success 200 {object} CheckEmailResponse
// @Failure 400 {object} object{error=string}
// @Router /auth/check-email [get]
func (app *application) checkEmailAuthMethodHandler(w http.ResponseWriter, r *http.Request) {
email := r.URL.Query().Get("email")
if email == "" {
Expand Down
Loading