Skip to content

Commit

Permalink
Adjusting course phase participation api (#82)
Browse files Browse the repository at this point in the history
* adding support to update a course phase participation with empty ID

* adding support to update multiple participation at the same time

* adding course participation in cpp response

* adding testing
  • Loading branch information
niclasheun authored Jan 20, 2025
1 parent 7731792 commit 8a1fb67
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 68 deletions.
20 changes: 4 additions & 16 deletions server/applicationAdministration/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -489,23 +489,11 @@ func UpdateApplicationAssessment(ctx context.Context, coursePhaseID uuid.UUID, c
qtx := ApplicationServiceSingleton.queries.WithTx(tx)

if assessment.PassStatus != nil || assessment.MetaData.Length() > 0 {
var passStatus db.NullPassStatus
if assessment.PassStatus != nil {
passStatus = db.NullPassStatus{
Valid: true,
PassStatus: *assessment.PassStatus,
}
} else {
passStatus = db.NullPassStatus{
Valid: false,
PassStatus: "",
}
}

err := coursePhaseParticipation.UpdateCoursePhaseParticipation(ctx, qtx, coursePhaseParticipationDTO.UpdateCoursePhaseParticipation{
ID: coursePhaseParticipationID,
PassStatus: passStatus,
MetaData: assessment.MetaData,
ID: coursePhaseParticipationID,
PassStatus: assessment.PassStatus,
MetaData: assessment.MetaData,
CoursePhaseID: coursePhaseID,
})
if err != nil {
log.Error(err)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
)

type CreateCoursePhaseParticipation struct {
CourseParticipationID uuid.UUID `json:"course_participation_id"`
CoursePhaseID uuid.UUID `json:"course_phase_id"`
PassStatus db.NullPassStatus `json:"pass_status"`
MetaData meta.MetaData `json:"meta_data"`
CourseParticipationID uuid.UUID `json:"course_participation_id"`
CoursePhaseID uuid.UUID `json:"course_phase_id"`
PassStatus *db.PassStatus `json:"pass_status"`
MetaData meta.MetaData `json:"meta_data"`
}

func (c CreateCoursePhaseParticipation) GetDBModel() (db.CreateCoursePhaseParticipationParams, error) {
Expand All @@ -24,7 +24,7 @@ func (c CreateCoursePhaseParticipation) GetDBModel() (db.CreateCoursePhasePartic
return db.CreateCoursePhaseParticipationParams{
CourseParticipationID: c.CourseParticipationID,
CoursePhaseID: c.CoursePhaseID,
PassStatus: c.PassStatus,
PassStatus: GetPassStatusDBModel(c.PassStatus),
MetaData: metaDataBytes,
}, nil

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@ import (
)

type GetAllCPPsForCoursePhase struct {
ID uuid.UUID `json:"id"`
PassStatus string `json:"pass_status"`
MetaData meta.MetaData `json:"meta_data"`
PrevMetaData meta.MetaData `json:"prev_meta_data"`
Student studentDTO.Student `json:"student"`
ID uuid.UUID `json:"id"`
PassStatus string `json:"pass_status"`
CourseParticipationID uuid.UUID `json:"course_participation_id"`
MetaData meta.MetaData `json:"meta_data"`
PrevMetaData meta.MetaData `json:"prev_meta_data"`
Student studentDTO.Student `json:"student"`
}

func GetAllCPPsForCoursePhaseDTOFromDBModel(model db.GetAllCoursePhaseParticipationsForCoursePhaseIncludingPreviousRow) (GetAllCPPsForCoursePhase, error) {
Expand All @@ -30,10 +31,11 @@ func GetAllCPPsForCoursePhaseDTOFromDBModel(model db.GetAllCoursePhaseParticipat
}

return GetAllCPPsForCoursePhase{
ID: model.CoursePhaseParticipationID,
PassStatus: GetPassStatusString(model.PassStatus),
MetaData: metaData,
PrevMetaData: prevMetaData,
ID: model.CoursePhaseParticipationID,
CourseParticipationID: model.CourseParticipationID,
PassStatus: GetPassStatusString(model.PassStatus),
MetaData: metaData,
PrevMetaData: prevMetaData,
Student: studentDTO.GetStudentDTOFromDBModel(db.Student{
ID: model.StudentID,
FirstName: model.FirstName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,15 @@ func GetPassStatusString(passStatus db.NullPassStatus) string {
}
return string(db.PassStatusNotAssessed)
}

func GetPassStatusDBModel(passStatus *db.PassStatus) db.NullPassStatus {
if passStatus == nil {
return db.NullPassStatus{
Valid: false,
}
}
return db.NullPassStatus{
Valid: true,
PassStatus: *passStatus,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,20 @@ import (
log "github.com/sirupsen/logrus"
)

type UpdateCoursePhaseParticipationRequest struct {
// for individual updates, the ID is in the url
// for batch updates, the ID is in the body
ID uuid.UUID `json:"id"`
CourseParticipationID uuid.UUID `json:"course_participation_id"`
PassStatus *db.PassStatus `json:"pass_status"`
MetaData meta.MetaData `json:"meta_data"`
}

type UpdateCoursePhaseParticipation struct {
ID uuid.UUID `json:"id"`
PassStatus db.NullPassStatus `json:"passed"`
MetaData meta.MetaData `json:"meta_data"`
ID uuid.UUID `json:"id"`
PassStatus *db.PassStatus `json:"passed"`
MetaData meta.MetaData `json:"meta_data"`
CoursePhaseID uuid.UUID `json:"course_phase_id"`
}

func (c UpdateCoursePhaseParticipation) GetDBModel() (db.UpdateCoursePhaseParticipationParams, error) {
Expand All @@ -21,9 +31,9 @@ func (c UpdateCoursePhaseParticipation) GetDBModel() (db.UpdateCoursePhasePartic
}

return db.UpdateCoursePhaseParticipationParams{
ID: c.ID,
PassStatus: c.PassStatus,
MetaData: metaDataBytes,
ID: c.ID,
PassStatus: GetPassStatusDBModel(c.PassStatus),
MetaData: metaDataBytes,
CoursePhaseID: c.CoursePhaseID,
}, nil

}
100 changes: 93 additions & 7 deletions server/coursePhase/coursePhaseParticipation/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import (
func setupCoursePhaseParticipationRouter(routerGroup *gin.RouterGroup, authMiddleware func() gin.HandlerFunc, permissionIDMiddleware func(allowedRoles ...string) gin.HandlerFunc) {
courseParticipation := routerGroup.Group("/course_phases/:uuid/participations", authMiddleware())
courseParticipation.GET("", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer, keycloak.CourseEditor), getParticipationsForCoursePhase)
courseParticipation.POST("", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer, keycloak.CourseEditor), createCoursePhaseParticipation)
courseParticipation.POST("", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer), createCoursePhaseParticipation)
courseParticipation.GET("/:participation_uuid", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer, keycloak.CourseEditor), getParticipation)
courseParticipation.PUT("/:participation_uuid", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer, keycloak.CourseEditor), updateCoursePhaseParticipation)
courseParticipation.PUT("/:participation_uuid", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer), updateCoursePhaseParticipation)
// allow to modify multiple at once
courseParticipation.PUT("", permissionIDMiddleware(keycloak.PromptAdmin, keycloak.CourseLecturer), updateBatchCoursePhaseParticipation)
}

func getParticipationsForCoursePhase(c *gin.Context) {
Expand Down Expand Up @@ -78,26 +80,110 @@ func createCoursePhaseParticipation(c *gin.Context) {
}

func updateCoursePhaseParticipation(c *gin.Context) {
// this might be uuid.Nil
id, err := uuid.Parse(c.Param("participation_uuid"))
if err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

var updatedCourseParticipation coursePhaseParticipationDTO.UpdateCoursePhaseParticipation
if err := c.BindJSON(&updatedCourseParticipation); err != nil {
coursePhaseId, err := uuid.Parse(c.Param("uuid"))
if err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

var updatedCourseParticipationRequest coursePhaseParticipationDTO.UpdateCoursePhaseParticipationRequest
if err := c.BindJSON(&updatedCourseParticipationRequest); err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

updatedCourseParticipation.ID = id
if id == uuid.Nil {
// Case 1: create a new course phase participation
createCourseParticipationDTO := coursePhaseParticipationDTO.CreateCoursePhaseParticipation{
CourseParticipationID: updatedCourseParticipationRequest.CourseParticipationID,
CoursePhaseID: coursePhaseId,
PassStatus: updatedCourseParticipationRequest.PassStatus,
MetaData: updatedCourseParticipationRequest.MetaData,
}

if err := Validate(createCourseParticipationDTO); err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

coursePhaseParticipation, err := CreateCoursePhaseParticipation(c, nil, createCourseParticipationDTO)
if err != nil {
handleError(c, http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusCreated, coursePhaseParticipation.ID)
} else {
// Case 2: update an existing course phase participation
err = UpdateCoursePhaseParticipation(c, nil, coursePhaseParticipationDTO.UpdateCoursePhaseParticipation{
ID: id,
PassStatus: updatedCourseParticipationRequest.PassStatus,
MetaData: updatedCourseParticipationRequest.MetaData,
CoursePhaseID: coursePhaseId, // we pass the coursePhaseId to check if the participation is in the correct course phase
})
if err != nil {
handleError(c, http.StatusInternalServerError, err)
return
}
c.IndentedJSON(http.StatusOK, id)
}
}

err = UpdateCoursePhaseParticipation(c, nil, updatedCourseParticipation)
func updateBatchCoursePhaseParticipation(c *gin.Context) {
coursePhaseId, err := uuid.Parse(c.Param("uuid"))
if err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

// we expect an array of updates
var updatedCourseParticipationRequest []coursePhaseParticipationDTO.UpdateCoursePhaseParticipationRequest
if err := c.BindJSON(&updatedCourseParticipationRequest); err != nil {
handleError(c, http.StatusBadRequest, err)
return
}

// we filter in the two different kinds
var createCourseParticipationDTOs []coursePhaseParticipationDTO.CreateCoursePhaseParticipation
var updateCourseParticipationDTOs []coursePhaseParticipationDTO.UpdateCoursePhaseParticipation
for _, update := range updatedCourseParticipationRequest {
if update.ID == uuid.Nil {
newParticipation := coursePhaseParticipationDTO.CreateCoursePhaseParticipation{
CourseParticipationID: update.CourseParticipationID,
CoursePhaseID: coursePhaseId,
PassStatus: update.PassStatus,
MetaData: update.MetaData,
}

// Validate for complete new participations
if err := Validate(newParticipation); err != nil {
handleError(c, http.StatusBadRequest, err)
return
}
createCourseParticipationDTOs = append(createCourseParticipationDTOs, newParticipation)
} else {
updateCourseParticipationDTOs = append(updateCourseParticipationDTOs, coursePhaseParticipationDTO.UpdateCoursePhaseParticipation{
ID: update.ID,
PassStatus: update.PassStatus,
MetaData: update.MetaData,
CoursePhaseID: coursePhaseId, // we pass the coursePhaseId to check if the participation is in the correct course phase
})
}
}

ids, err := UpdateBatchCoursePhaseParticipation(c, createCourseParticipationDTOs, updateCourseParticipationDTOs)
if err != nil {
handleError(c, http.StatusInternalServerError, err)
return
}
c.JSON(http.StatusOK, gin.H{"message": "updated course phase participation"})

c.IndentedJSON(http.StatusOK, ids)
}

func handleError(c *gin.Context, statusCode int, err error) {
Expand Down
53 changes: 50 additions & 3 deletions server/coursePhase/coursePhaseParticipation/router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"log"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/gin-gonic/gin"
Expand Down Expand Up @@ -77,10 +78,11 @@ func (suite *RouterTestSuite) TestCreateCoursePhaseParticipation() {
var metaData meta.MetaData
err := json.Unmarshal([]byte(jsonData), &metaData)
assert.NoError(suite.T(), err)
fail := db.PassStatusFailed

newParticipation := coursePhaseParticipationDTO.CreateCoursePhaseParticipation{
CourseParticipationID: uuid.MustParse("ca41772a-e06d-40eb-9c4b-ab44e06a890c"),
PassStatus: db.NullPassStatus{PassStatus: "failed", Valid: true},
PassStatus: &fail,
MetaData: metaData,
}
body, _ := json.Marshal(newParticipation)
Expand All @@ -103,11 +105,12 @@ func (suite *RouterTestSuite) TestUpdateCoursePhaseParticipation() {
var metaData meta.MetaData
err := json.Unmarshal([]byte(jsonData), &metaData)
assert.NoError(suite.T(), err)
pass := db.PassStatusPassed

updatedParticipation := coursePhaseParticipationDTO.UpdateCoursePhaseParticipation{
updatedParticipation := coursePhaseParticipationDTO.UpdateCoursePhaseParticipationRequest{
ID: uuid.MustParse("83d88b1f-1435-4c36-b8ca-6741094f35e4"),
MetaData: metaData,
PassStatus: db.NullPassStatus{PassStatus: "passed", Valid: true},
PassStatus: &pass,
}
body, _ := json.Marshal(updatedParticipation)

Expand Down Expand Up @@ -139,6 +142,50 @@ func (suite *RouterTestSuite) TestUpdateCoursePhaseParticipation() {
assert.Equal(suite.T(), updatedParticipation.MetaData["other-value"], updated.MetaData["other-value"], "New Meta data should match")
}

func (suite *RouterTestSuite) TestUpdateNewCoursePhaseParticipation() {
jsonData := `{"other-value": "some skills"}`
var metaData meta.MetaData
err := json.Unmarshal([]byte(jsonData), &metaData)
assert.NoError(suite.T(), err)
pass := db.PassStatusPassed

toBecreatedParticipation := coursePhaseParticipationDTO.UpdateCoursePhaseParticipationRequest{
CourseParticipationID: uuid.MustParse("f6744410-cfe2-456d-96fa-e857cf989569"),
MetaData: metaData,
PassStatus: &pass,
}
body, _ := json.Marshal(toBecreatedParticipation)

// Send the update request
req := httptest.NewRequest(http.MethodPut, "/api/course_phases/4e736d05-c125-48f0-8fa0-848b03ca6908/participations/00000000-0000-0000-0000-000000000000", bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()

suite.router.ServeHTTP(w, req)

// Assert the update request was successful
assert.Equal(suite.T(), http.StatusCreated, w.Code)

newId := strings.ReplaceAll(w.Body.String(), "\"", "")

// Perform a GET request to verify the changes
getReq := httptest.NewRequest(http.MethodGet, "/api/course_phases/4e736d05-c125-48f0-8fa0-848b03ca6908/participations/"+newId, nil)
getW := httptest.NewRecorder()

suite.router.ServeHTTP(getW, getReq)

// Assert the GET request was successful
assert.Equal(suite.T(), http.StatusOK, getW.Code)

// Verify the returned data matches the expected updated data
var createdParticipation coursePhaseParticipationDTO.GetCoursePhaseParticipation
err = json.Unmarshal(getW.Body.Bytes(), &createdParticipation)
assert.NoError(suite.T(), err)
assert.Equal(suite.T(), createdParticipation.ID, uuid.MustParse(newId), "Participation ID should match")
assert.Equal(suite.T(), "passed", createdParticipation.PassStatus, "PassStatus should match")
assert.Equal(suite.T(), toBecreatedParticipation.MetaData["other-value"], createdParticipation.MetaData["other-value"], "New Meta data should match")
}

func TestRouterTestSuite(t *testing.T) {
suite.Run(t, new(RouterTestSuite))
}
Loading

0 comments on commit 8a1fb67

Please sign in to comment.