diff --git a/cmd/server/main.go b/cmd/server/main.go index 995acc3..4eb6c2d 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -48,12 +48,14 @@ func main() { subjectRepo := repository.NewSubjectRepository(db) syllabusRepo := repository.NewSyllabusRepository(db) facultyRepo := repository.NewFacultyRepository(db) + roomRepo := repository.NewRoomRepository(db) // Services subjectSvc := service.NewSubjectService(subjectRepo, syllabusRepo) facultySvc := service.NewFacultyService(facultyRepo) + roomSvc := service.NewRoomService(roomRepo) // Handler + Router - h := handler.NewHandler(subjectSvc, facultySvc) + h := handler.NewHandler(subjectSvc, facultySvc, roomSvc) strictHandler := api.NewStrictHandler(h, nil) api.RegisterHandlers(router, strictHandler) diff --git a/internal/database/migrate.go b/internal/database/migrate.go index e2c3819..f19289c 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -10,5 +10,6 @@ func AutoMigrate(db *gorm.DB) error { &SubjectFaculty{}, &SubjectEligibleAttribute{}, &SubjectRequirement{}, + &Room{}, ) } diff --git a/internal/database/room.go b/internal/database/room.go new file mode 100644 index 0000000..f8237dd --- /dev/null +++ b/internal/database/room.go @@ -0,0 +1,32 @@ +package database + +import ( + "time" + + "github.com/fun-dotto/academic-api/internal/domain" +) + +type Room struct { + ID string `gorm:"type:uuid;primaryKey"` + Name string `gorm:"not null"` + Floor string `gorm:"not null"` + + CreatedAt time.Time + UpdatedAt time.Time +} + +func RoomToDomain(m Room) domain.Room { + return domain.Room{ + ID: m.ID, + Name: m.Name, + Floor: domain.Floor(m.Floor), + } +} + +func RoomFromDomain(d domain.Room) Room { + return Room{ + ID: d.ID, + Name: d.Name, + Floor: string(d.Floor), + } +} diff --git a/internal/domain/room.go b/internal/domain/room.go new file mode 100644 index 0000000..2bf23ec --- /dev/null +++ b/internal/domain/room.go @@ -0,0 +1,24 @@ +package domain + +type Floor string + +const ( + FloorFloor1 Floor = "Floor1" + FloorFloor2 Floor = "Floor2" + FloorFloor3 Floor = "Floor3" + FloorFloor4 Floor = "Floor4" + FloorFloor5 Floor = "Floor5" + FloorFloor6 Floor = "Floor6" + FloorFloor7 Floor = "Floor7" +) + +type Room struct { + ID string + Name string + Floor Floor +} + +type RoomListFilter struct { + IDs []string + Floors []Floor +} diff --git a/internal/handler/converter.go b/internal/handler/converter.go index 1988c2d..c904cf2 100644 --- a/internal/handler/converter.go +++ b/internal/handler/converter.go @@ -204,3 +204,44 @@ func toDomainFacultyFromRequest(id string, req api.FacultyRequest) domain.Facult Email: req.Email, } } + +// Room + +func buildRoomListFilter(params api.RoomsV1ListParams) domain.RoomListFilter { + filter := domain.RoomListFilter{} + if params.Ids != nil { + filter.IDs = *params.Ids + } + if params.Floor != nil { + floors := make([]domain.Floor, len(*params.Floor)) + for i, f := range *params.Floor { + floors[i] = domain.Floor(f) + } + filter.Floors = floors + } + return filter +} + +func roomToAPI(room domain.Room) api.Room { + return api.Room{ + Id: room.ID, + Name: room.Name, + Floor: api.DottoFoundationV1Floor(room.Floor), + } +} + +func roomsToAPI(rooms []domain.Room) []api.Room { + result := make([]api.Room, len(rooms)) + for i, room := range rooms { + result[i] = roomToAPI(room) + } + return result +} + +func toDomainRoomFromRequest(id string, req api.RoomRequest) domain.Room { + return domain.Room{ + ID: id, + Name: req.Name, + Floor: domain.Floor(req.Floor), + } +} diff --git a/internal/handler/handler.go b/internal/handler/handler.go index aab7394..0f77da5 100644 --- a/internal/handler/handler.go +++ b/internal/handler/handler.go @@ -25,17 +25,28 @@ type facultyService interface { Delete(ctx context.Context, id string) error } +type roomService interface { + List(ctx context.Context, filter domain.RoomListFilter) ([]domain.Room, error) + GetByID(ctx context.Context, id string) (domain.Room, error) + Create(ctx context.Context, room domain.Room) (domain.Room, error) + Update(ctx context.Context, room domain.Room) (domain.Room, error) + Delete(ctx context.Context, id string) error +} + type Handler struct { subjectSvc subjectService facultySvc facultyService + roomSvc roomService } func NewHandler( subjectSvc subjectService, facultySvc facultyService, + roomSvc roomService, ) *Handler { return &Handler{ subjectSvc: subjectSvc, facultySvc: facultySvc, + roomSvc: roomSvc, } } diff --git a/internal/handler/room_create.go b/internal/handler/room_create.go index e226deb..80e02bf 100644 --- a/internal/handler/room_create.go +++ b/internal/handler/room_create.go @@ -2,11 +2,17 @@ package handler import ( "context" - "fmt" api "github.com/fun-dotto/academic-api/generated" + "github.com/google/uuid" ) func (h *Handler) RoomsV1Create(ctx context.Context, request api.RoomsV1CreateRequestObject) (api.RoomsV1CreateResponseObject, error) { - return nil, fmt.Errorf("not implemented") + id := uuid.New().String() + domainRoom := toDomainRoomFromRequest(id, *request.Body) + created, err := h.roomSvc.Create(ctx, domainRoom) + if err != nil { + return nil, err + } + return api.RoomsV1Create201JSONResponse{Room: roomToAPI(created)}, nil } diff --git a/internal/handler/room_delete.go b/internal/handler/room_delete.go index 08382fc..db8aead 100644 --- a/internal/handler/room_delete.go +++ b/internal/handler/room_delete.go @@ -2,11 +2,18 @@ package handler import ( "context" - "fmt" + "errors" api "github.com/fun-dotto/academic-api/generated" + "gorm.io/gorm" ) func (h *Handler) RoomsV1Delete(ctx context.Context, request api.RoomsV1DeleteRequestObject) (api.RoomsV1DeleteResponseObject, error) { - return nil, fmt.Errorf("not implemented") + if err := h.roomSvc.Delete(ctx, request.Id); err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return api.RoomsV1Delete404Response{}, nil + } + return nil, err + } + return api.RoomsV1Delete204Response{}, nil } diff --git a/internal/handler/room_detail.go b/internal/handler/room_detail.go index 38fdc2f..24c9867 100644 --- a/internal/handler/room_detail.go +++ b/internal/handler/room_detail.go @@ -2,11 +2,19 @@ package handler import ( "context" - "fmt" + "errors" api "github.com/fun-dotto/academic-api/generated" + "gorm.io/gorm" ) func (h *Handler) RoomsV1Detail(ctx context.Context, request api.RoomsV1DetailRequestObject) (api.RoomsV1DetailResponseObject, error) { - return nil, fmt.Errorf("not implemented") + room, err := h.roomSvc.GetByID(ctx, request.Id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return api.RoomsV1Detail404Response{}, nil + } + return nil, err + } + return api.RoomsV1Detail200JSONResponse{Room: roomToAPI(room)}, nil } diff --git a/internal/handler/room_list.go b/internal/handler/room_list.go index a0fbf88..8797ca1 100644 --- a/internal/handler/room_list.go +++ b/internal/handler/room_list.go @@ -2,11 +2,16 @@ package handler import ( "context" - "fmt" api "github.com/fun-dotto/academic-api/generated" ) func (h *Handler) RoomsV1List(ctx context.Context, request api.RoomsV1ListRequestObject) (api.RoomsV1ListResponseObject, error) { - return nil, fmt.Errorf("not implemented") + filter := buildRoomListFilter(request.Params) + + rooms, err := h.roomSvc.List(ctx, filter) + if err != nil { + return nil, err + } + return api.RoomsV1List200JSONResponse{Rooms: roomsToAPI(rooms)}, nil } diff --git a/internal/handler/room_update.go b/internal/handler/room_update.go index 25c3701..ba19fd0 100644 --- a/internal/handler/room_update.go +++ b/internal/handler/room_update.go @@ -2,11 +2,20 @@ package handler import ( "context" - "fmt" + "errors" api "github.com/fun-dotto/academic-api/generated" + "gorm.io/gorm" ) func (h *Handler) RoomsV1Update(ctx context.Context, request api.RoomsV1UpdateRequestObject) (api.RoomsV1UpdateResponseObject, error) { - return nil, fmt.Errorf("not implemented") + domainRoom := toDomainRoomFromRequest(request.Id, *request.Body) + updated, err := h.roomSvc.Update(ctx, domainRoom) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return api.RoomsV1Update404Response{}, nil + } + return nil, err + } + return api.RoomsV1Update200JSONResponse{Room: roomToAPI(updated)}, nil } diff --git a/internal/repository/room.go b/internal/repository/room.go new file mode 100644 index 0000000..97c204f --- /dev/null +++ b/internal/repository/room.go @@ -0,0 +1,91 @@ +package repository + +import ( + "context" + "errors" + + "github.com/fun-dotto/academic-api/internal/database" + "github.com/fun-dotto/academic-api/internal/domain" + "gorm.io/gorm" +) + +type RoomRepository struct { + db *gorm.DB +} + +func NewRoomRepository(db *gorm.DB) *RoomRepository { + return &RoomRepository{db: db} +} + +func (r *RoomRepository) List(ctx context.Context, filter domain.RoomListFilter) ([]domain.Room, error) { + var dbRooms []database.Room + query := r.db.WithContext(ctx) + if len(filter.IDs) > 0 { + query = query.Where("id IN ?", filter.IDs) + } + if len(filter.Floors) > 0 { + floors := make([]string, len(filter.Floors)) + for i, f := range filter.Floors { + floors[i] = string(f) + } + query = query.Where("floor IN ?", floors) + } + if err := query.Find(&dbRooms).Error; err != nil { + return nil, err + } + + domainRooms := make([]domain.Room, len(dbRooms)) + for i, dbRoom := range dbRooms { + domainRooms[i] = database.RoomToDomain(dbRoom) + } + + return domainRooms, nil +} + +func (r *RoomRepository) GetByID(ctx context.Context, id string) (domain.Room, error) { + var dbRoom database.Room + if err := r.db.WithContext(ctx).First(&dbRoom, "id = ?", id).Error; err != nil { + return domain.Room{}, err + } + return database.RoomToDomain(dbRoom), nil +} + +func (r *RoomRepository) Create(ctx context.Context, room domain.Room) (domain.Room, error) { + dbRoom := database.RoomFromDomain(room) + if err := r.db.WithContext(ctx).Create(&dbRoom).Error; err != nil { + return domain.Room{}, err + } + return database.RoomToDomain(dbRoom), nil +} + +func (r *RoomRepository) Update(ctx context.Context, room domain.Room) (domain.Room, error) { + dbRoom := database.RoomFromDomain(room) + if err := r.db.WithContext(ctx).Model(&database.Room{}).Where("id = ?", room.ID).Updates(map[string]interface{}{ + "name": dbRoom.Name, + "floor": dbRoom.Floor, + }).Error; err != nil { + return domain.Room{}, err + } + return r.GetByID(ctx, room.ID) +} + +func (r *RoomRepository) Delete(ctx context.Context, id string) error { + return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + var room database.Room + if err := tx.Where("id = ?", id).First(&room).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + return err + } + + result := tx.Where("id = ?", id).Delete(&database.Room{}) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil + }) +} diff --git a/internal/service/room.go b/internal/service/room.go new file mode 100644 index 0000000..d87625f --- /dev/null +++ b/internal/service/room.go @@ -0,0 +1,43 @@ +package service + +import ( + "context" + + "github.com/fun-dotto/academic-api/internal/domain" +) + +type roomRepository interface { + List(ctx context.Context, filter domain.RoomListFilter) ([]domain.Room, error) + GetByID(ctx context.Context, id string) (domain.Room, error) + Create(ctx context.Context, room domain.Room) (domain.Room, error) + Update(ctx context.Context, room domain.Room) (domain.Room, error) + Delete(ctx context.Context, id string) error +} + +type RoomService struct { + repo roomRepository +} + +func NewRoomService(repo roomRepository) *RoomService { + return &RoomService{repo: repo} +} + +func (s *RoomService) List(ctx context.Context, filter domain.RoomListFilter) ([]domain.Room, error) { + return s.repo.List(ctx, filter) +} + +func (s *RoomService) GetByID(ctx context.Context, id string) (domain.Room, error) { + return s.repo.GetByID(ctx, id) +} + +func (s *RoomService) Create(ctx context.Context, room domain.Room) (domain.Room, error) { + return s.repo.Create(ctx, room) +} + +func (s *RoomService) Update(ctx context.Context, room domain.Room) (domain.Room, error) { + return s.repo.Update(ctx, room) +} + +func (s *RoomService) Delete(ctx context.Context, id string) error { + return s.repo.Delete(ctx, id) +}