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
195 changes: 195 additions & 0 deletions internal/api/handlers/board.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Package handlers handles http requests and responses
// Business logic belongs in services, not here
package handlers

import (
"net/http"
"strconv"

"github.com/acmcsufoss/api.acmcsuf.com/internal/api/services"
"github.com/acmcsufoss/api.acmcsuf.com/internal/db/models"
"github.com/gin-gonic/gin"
)

type BoardHandler struct {
boardService services.BoardServicer
}

func NewBoardHandler(boardService services.BoardServicer) *BoardHandler {
return &BoardHandler{boardService: boardService}
}

func (h *BoardHandler) GetOfficer(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")

officer, err := h.boardService.GetOfficer(ctx, id)

if err != nil {
if err.Error() == "sql: no rows in result set" {
c.JSON(http.StatusNotFound, gin.H{
"error": "Officer not found",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to retrieve officer",
})
}

c.JSON(http.StatusOK, officer)
}

func (h *BoardHandler) GetOfficers(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) UpdateOfficer(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) DeleteOfficer(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) CreateOfficer(c *gin.Context) {
ctx := c.Request.Context()
var params models.CreateOfficerParams

if err := c.ShouldBindJSON(&params); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body. " + err.Error(),
})
return
}

// TODO: error out if required fields aren't provided
if err := h.boardService.CreateOfficer(ctx, params); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create officer",
})
}
c.JSON(http.StatusOK, gin.H{
"message": "Officer created successfully",
"uuid": params.Uuid,
})
}

func (h *BoardHandler) GetTiers(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) GetTier(c *gin.Context) {
ctx := c.Request.Context()
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to convert string to int",
})
return
}

tier, err := h.boardService.GetTier(ctx, int64(id))
if err != nil {
if err.Error() == "sql: no rows in result set" {
c.JSON(http.StatusNotFound, gin.H{
"error": "Tier not found",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to retrieve tier",
})
}

c.JSON(http.StatusOK, tier)
}

func (h *BoardHandler) CreateTier(c *gin.Context) {
ctx := c.Request.Context()
var params models.CreateTierParams

if err := c.ShouldBindJSON(&params); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body. " + err.Error(),
})
return
}

// TODO: error out if required fields aren't provided
if err := h.boardService.CreateTier(ctx, params); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create tier",
})
}
c.JSON(http.StatusOK, gin.H{
"message": "Tier created successfully",
"tier": params.Tier,
})
}

func (h *BoardHandler) UpdateTier(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) DeleteTier(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) GetPositions(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) GetPosition(c *gin.Context) {
ctx := c.Request.Context()
id := c.Param("id")

position, err := h.boardService.GetPosition(ctx, id)

if err != nil {
if err.Error() == "sql: no rows in result set" {
c.JSON(http.StatusNotFound, gin.H{
"error": "Position not found",
})
return
}
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to retrieve position",
})
}

c.JSON(http.StatusOK, position)
}

func (h *BoardHandler) CreatePosition(c *gin.Context) {
ctx := c.Request.Context()
var params models.CreatePositionParams

if err := c.ShouldBindJSON(&params); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": "Invalid request body. " + err.Error(),
})
return
}

// TODO: error out if required fields aren't provided
if err := h.boardService.CreatePosition(ctx, params); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": "Failed to create position",
})
}
c.JSON(http.StatusOK, gin.H{
"message": "Position created successfully",
"oid": params.Oid,
"semester": params.Semester,
"tier": params.Tier,
})
}

func (h *BoardHandler) UpdatePosition(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}

func (h *BoardHandler) DeletePosition(c *gin.Context) {
c.Status(http.StatusNotImplemented)
}
20 changes: 19 additions & 1 deletion internal/api/routes/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
)

func SetupV1Routes(router *gin.Engine, eventService services.EventsServicer,
announcementService services.AnnouncementServicer) {
announcementService services.AnnouncementServicer, boardService services.BoardServicer) {
router.GET("/swagger/*any", handlers.NewSwaggerHandler())

// Version 1 routes
Expand All @@ -29,5 +29,23 @@ func SetupV1Routes(router *gin.Engine, eventService services.EventsServicer,
v1.PUT("/announcements/:id", announcementHandler.UpdateAnnouncement)
v1.DELETE("/announcements/:id", announcementHandler.DeleteAnnouncement)

boardHandler := handlers.NewBoardHandler(boardService)
v1.GET("/officers", boardHandler.GetOfficers)
v1.GET("/officer/:id", boardHandler.GetOfficer)
v1.POST("/officer", boardHandler.CreateOfficer)
v1.PUT("/officer/:id", boardHandler.UpdateOfficer)
v1.DELETE("/officer/:id", boardHandler.DeleteOfficer)

v1.GET("/tier", boardHandler.GetTiers)
v1.GET("/tier/:id", boardHandler.GetTier)
v1.POST("/tier", boardHandler.CreateTier)
v1.PUT("/tier/:id", boardHandler.UpdateTier)
v1.DELETE("/tier/:id", boardHandler.DeleteTier)

v1.GET("/position", boardHandler.GetPositions)
v1.GET("/position/:id", boardHandler.GetPosition)
v1.POST("/position", boardHandler.CreatePosition)
v1.PUT("/position/:id", boardHandler.UpdatePosition)
v1.DELETE("/position/:id", boardHandler.DeletePosition)
}
}
3 changes: 2 additions & 1 deletion internal/api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,13 @@ func Run(ctx context.Context) {
queries := models.New(db)
eventsService := services.NewEventsService(queries)
announcementService := services.NewAnnouncementService(queries)
boardService := services.NewBoardService(queries)
router := gin.Default()

router.SetTrustedProxies([]string{
"127.0.0.1/32",
})
routes.SetupV1Routes(router, eventsService, announcementService)
routes.SetupV1Routes(router, eventsService, announcementService, boardService)

port := os.Getenv("PORT")
if port == "" {
Expand Down
101 changes: 100 additions & 1 deletion internal/api/services/board.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,102 @@
package services

import ()
import (
"context"
"errors"

"github.com/acmcsufoss/api.acmcsuf.com/internal/db/models"
)

type BoardServicer interface {
GetOfficer(ctx context.Context, id string) (models.Officer, error)
ListOfficers(ctx context.Context, filters ...any) ([]models.Officer, error)
CreateOfficer(ctx context.Context, params models.CreateOfficerParams) error
UpdateOfficer(ctx context.Context, id string, params models.UpdateOfficerParams) error
DeleteOfficer(ctx context.Context, id string) error

GetTier(ctx context.Context, tierName int64) (models.Tier, error)
ListTiers(ctx context.Context, filters ...any) ([]models.Tier, error)
CreateTier(ctx context.Context, params models.CreateTierParams) error
DeleteTier(ctx context.Context, tierName int64) error

GetPosition(ctx context.Context, fullName string) (models.Position, error)
CreatePosition(ctx context.Context, params models.CreatePositionParams) error
}

type BoardService struct {
q *models.Queries
}

var _ BoardServicer = (*BoardService)(nil)

func NewBoardService(q *models.Queries) *BoardService {
return &BoardService{q: q}
}

func (s *BoardService) GetOfficer(ctx context.Context, uuid string) (models.Officer, error) {
officer, err := s.q.GetOfficer(ctx, uuid)
if err != nil {
return models.Officer{}, err
}
return officer, nil
}

func (s *BoardService) ListOfficers(ctx context.Context, filters ...any) ([]models.Officer, error) {
return nil, errors.New("not implemented")
}

func (s *BoardService) CreateOfficer(ctx context.Context, params models.CreateOfficerParams) error {
err := s.q.CreateOfficer(ctx, params)
if err != nil {
return err
}
return nil
}

func (s *BoardService) UpdateOfficer(ctx context.Context, uuid string, params models.UpdateOfficerParams) error {
return nil
}

func (s *BoardService) DeleteOfficer(ctx context.Context, uuid string) error {
return nil
}

func (s *BoardService) GetTier(ctx context.Context, tierName int64) (models.Tier, error) {
tier, err := s.q.GetTier(ctx, tierName)
if err != nil {
return models.Tier{}, err
}
return tier, nil
}

func (s *BoardService) CreateTier(ctx context.Context, params models.CreateTierParams) error {
err := s.q.CreateTier(ctx, params)
if err != nil {
return err
}
return nil
}

func (s *BoardService) ListTiers(ctx context.Context, filters ...any) ([]models.Tier, error) {
return nil, errors.New("not implemented")
}

func (s *BoardService) DeleteTier(ctx context.Context, tierName int64) error {
return errors.New("not implemented")
}

func (s *BoardService) GetPosition(ctx context.Context, fullName string) (models.Position, error) {
position, err := s.q.GetPosition(ctx, fullName)
if err != nil {
return models.Position{}, err
}
return position, nil
}

func (s *BoardService) CreatePosition(ctx context.Context, params models.CreatePositionParams) error {
err := s.q.CreatePosition(ctx, params)
if err != nil {
return err
}
return nil
}
Loading