Skip to content

Commit

Permalink
feat: Delete markdown block (#131)
Browse files Browse the repository at this point in the history
* feat(blocks): Add endpoint to delete markdown block

* test(blocks): Add test to ensure teachers can delete markdown blocks
  • Loading branch information
PedroChaparro authored Dec 30, 2023
1 parent 1511156 commit 1aee6d6
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 8 deletions.
47 changes: 46 additions & 1 deletion __tests__/integration/blocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestUpdateMarkdownBlockContent(t *testing.T) {
"name": "Update markdown block content test - laboratory",
"course_uuid": courseUUID,
"opening_date": "2023-12-01T08:00",
"due_date": "2023-12-01T12:00",
"due_date": "3023-12-01T12:00",
})
c.Equal(http.StatusCreated, status)
laboratoryUUID := laboratoryCreationResponse["uuid"].(string)
Expand Down Expand Up @@ -68,6 +68,51 @@ func TestUpdateMarkdownBlockContent(t *testing.T) {
c.Equal("# Updated main title", block["content"].(string))
}

func TestDeleteMarkdownBlock(t *testing.T) {
c := require.New(t)

// Login as a teacher
w, r := PrepareRequest("POST", "/api/v1/session/login", map[string]interface{}{
"email": registeredTeacherEmail,
"password": registeredTeacherPass,
})
router.ServeHTTP(w, r)
cookie := w.Result().Cookies()[0]

// Create a course
courseUUID, status := CreateCourse("Delete markdown block test - course")
c.Equal(http.StatusCreated, status)

// Create a laboratory
laboratoryCreationResponse, status := CreateLaboratory(cookie, map[string]interface{}{
"name": "Delete markdown block test - laboratory",
"course_uuid": courseUUID,
"opening_date": "2023-12-01T08:00",
"due_date": "3023-12-01T12:00",
})
c.Equal(http.StatusCreated, status)
laboratoryUUID := laboratoryCreationResponse["uuid"].(string)

// Create a markdown block
blockCreationResponse, status := CreateMarkdownBlock(cookie, laboratoryUUID)
c.Equal(http.StatusCreated, status)
markdownBlockUUID := blockCreationResponse["uuid"].(string)

// Get the laboratory
response, status := GetLaboratoryByUUID(cookie, laboratoryUUID)
c.Equal(http.StatusOK, status)
c.Equal(1, len(response["markdown_blocks"].([]interface{})))

// Delete the markdown block
_, status = DeleteMarkdownBlock(cookie, markdownBlockUUID)
c.Equal(http.StatusNoContent, status)

// Verify that the block was deleted
response, status = GetLaboratoryByUUID(cookie, laboratoryUUID)
c.Equal(http.StatusOK, status)
c.Equal(0, len(response["markdown_blocks"].([]interface{})))
}

func TestUpdateTestBlock(t *testing.T) {
c := require.New(t)

Expand Down
10 changes: 10 additions & 0 deletions __tests__/integration/blocks_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ func UpdateMarkdownBlockContent(cookie *http.Cookie, blockUUID string, payload m
return jsonResponse, w.Code
}

func DeleteMarkdownBlock(cookie *http.Cookie, blockUUID string) (response map[string]interface{}, statusCode int) {
endpoint := fmt.Sprintf("/api/v1/blocks/markdown_blocks/%s", blockUUID)
w, r := PrepareRequest("DELETE", endpoint, nil)
r.AddCookie(cookie)
router.ServeHTTP(w, r)

jsonResponse := ParseJsonResponse(w.Body)
return jsonResponse, w.Code
}

type UpdateTestBlockUtilsDTO struct {
blockUUID string
languageUUID string
Expand Down
4 changes: 2 additions & 2 deletions __tests__/integration/laboratorires_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestCreateLaboratory(t *testing.T) {
"name": "Create laboratory test - laboratory",
"course_uuid": courseUUID,
"opening_date": "2023-12-01T08:00",
"due_date": "2023-12-01T12:00",
"due_date": "3023-12-01T12:00",
},
ExpectedStatusCode: http.StatusCreated,
},
Expand Down Expand Up @@ -231,7 +231,7 @@ func TestCreateMarkdownBlock(t *testing.T) {
"name": "Create markdown block test - laboratory",
"course_uuid": courseUUID,
"opening_date": "2023-12-01T08:00",
"due_date": "2023-12-01T12:00",
"due_date": "3023-12-01T12:00",
})
laboratoryUUID := laboratoryCreationResponse["uuid"].(string)
c.Equal(http.StatusCreated, status)
Expand Down
11 changes: 11 additions & 0 deletions docs/bruno/blocks/delete-markdown-block.bru
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
meta {
name: delete-markdown-block
type: http
seq: 2
}

delete {
url: {{BASE_URL}}/blocks/markdown_blocks/eb4ef0ee-3865-44c3-8d52-2620fb2653e5
body: none
auth: none
}
15 changes: 15 additions & 0 deletions src/blocks/application/use_cases.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,18 @@ func (useCases *BlocksUseCases) UpdateTestBlock(dto dtos.UpdateTestBlockDTO) (er
// Update the block
return useCases.BlocksRepository.UpdateTestBlock(&dto)
}

func (useCases *BlocksUseCases) DeleteMarkdownBlock(dto dtos.DeleteBlockDTO) (err error) {
// Validate the teacher is the owner of the block
ownsBlock, err := useCases.BlocksRepository.DoesTeacherOwnsMarkdownBlock(dto.TeacherUUID, dto.BlockUUID)
if err != nil {
return err
}

if !ownsBlock {
return errors.TeacherDoesNotOwnBlock{}
}

// Delete the block
return useCases.BlocksRepository.DeleteMarkdownBlock(dto.BlockUUID)
}
6 changes: 5 additions & 1 deletion src/blocks/domain/definitions/blocks_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type BlockRepository interface {
// Update the markdown text of a markdown block
UpdateMarkdownBlockContent(blockUUID string, content string) (err error)

// Functions to check blocks ownership
// Check blocks ownership
DoesTeacherOwnsMarkdownBlock(teacherUUID string, blockUUID string) (bool, error)
DoesTeacherOwnsTestBlock(teacherUUID string, blockUUID string) (bool, error)

Expand All @@ -25,4 +25,8 @@ type BlockRepository interface {

// Update the test block information in the database
UpdateTestBlock(*dtos.UpdateTestBlockDTO) (err error)

// Delete blocks
DeleteMarkdownBlock(blockUUID string) (err error)
DeleteTestBlock(blockUUID string) (err error)
}
5 changes: 5 additions & 0 deletions src/blocks/domain/dtos/blocks_dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,8 @@ type UpdateTestBlockDTO struct {
Name string
NewTestArchive *multipart.File
}

type DeleteBlockDTO struct {
TeacherUUID string
BlockUUID string
}
25 changes: 25 additions & 0 deletions src/blocks/infrastructure/http/controllers.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,28 @@ func (controller *BlocksController) HandleUpdateTestBlock(c *gin.Context) {

c.Status(http.StatusNoContent)
}

func (controller *BlocksController) HandleDeleteMarkdownBlock(c *gin.Context) {
teacherUUID := c.GetString("session_uuid")
blockUUID := c.Param("block_uuid")

// Validate the block UUID
if err := sharedInfrastructure.GetValidator().Var(blockUUID, "uuid4"); err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"message": "Block UUID is not valid",
})
return
}

// Delete the block
err := controller.UseCases.DeleteMarkdownBlock(dtos.DeleteBlockDTO{
TeacherUUID: teacherUUID,
BlockUUID: blockUUID,
})
if err != nil {
c.Error(err)
return
}

c.Status(http.StatusNoContent)
}
7 changes: 7 additions & 0 deletions src/blocks/infrastructure/http/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,11 @@ func StartBlocksRoutes(g *gin.RouterGroup) {
sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}),
controller.HandleUpdateTestBlock,
)

blocksGroup.DELETE(
"/markdown_blocks/:block_uuid",
sharedInfrastructure.WithAuthenticationMiddleware(),
sharedInfrastructure.WithAuthorizationMiddleware([]string{"teacher"}),
controller.HandleDeleteMarkdownBlock,
)
}
86 changes: 82 additions & 4 deletions src/blocks/infrastructure/implementations/blocks_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

"github.com/UPB-Code-Labs/main-api/src/blocks/domain/dtos"
"github.com/UPB-Code-Labs/main-api/src/blocks/domain/errors"
laboratoriesDomainErrors "github.com/UPB-Code-Labs/main-api/src/laboratories/domain/errors"
sharedDomainErrors "github.com/UPB-Code-Labs/main-api/src/shared/domain/errors"
sharedInfrastructure "github.com/UPB-Code-Labs/main-api/src/shared/infrastructure"
)
Expand Down Expand Up @@ -64,7 +65,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsMarkdownBlock(teacher
var laboratoryUUID string
if err := row.Scan(&laboratoryUUID); err != nil {
if err == sql.ErrNoRows {
return false, nil
return false, errors.BlockNotFound{}
}
}

Expand All @@ -83,7 +84,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsMarkdownBlock(teacher
var laboratoryTeacherUUID string
if err := row.Scan(&laboratoryTeacherUUID); err != nil {
if err == sql.ErrNoRows {
return false, nil
return false, laboratoriesDomainErrors.LaboratoryNotFoundError{}
}
}

Expand All @@ -105,7 +106,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsTestBlock(teacherUUID
var laboratoryUUID string
if err := row.Scan(&laboratoryUUID); err != nil {
if err == sql.ErrNoRows {
return false, nil
return false, &errors.BlockNotFound{}
}
}

Expand All @@ -124,7 +125,7 @@ func (repository *BlocksPostgresRepository) DoesTeacherOwnsTestBlock(teacherUUID
var laboratoryTeacherUUID string
if err := row.Scan(&laboratoryTeacherUUID); err != nil {
if err == sql.ErrNoRows {
return false, nil
return false, laboratoriesDomainErrors.LaboratoryNotFoundError{}
}
}

Expand Down Expand Up @@ -334,3 +335,80 @@ func (repository *BlocksPostgresRepository) UpdateTestBlock(dto *dtos.UpdateTest

return nil
}

func (repository *BlocksPostgresRepository) DeleteMarkdownBlock(blockUUID string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

// Get the UUID of the block index
query := `
SELECT block_index_id
FROM markdown_blocks
WHERE id = $1
`

row := repository.Connection.QueryRowContext(ctx, query, blockUUID)
var blockIndexUUID string
if err := row.Scan(&blockIndexUUID); err != nil {
if err == sql.ErrNoRows {
return &errors.BlockNotFound{}
}

return err
}

// After deleting the block index, the block will be deleted automatically due to the `ON DELETE CASCADE` constraint
err = repository.deleteBlockIndex(blockIndexUUID)
if err != nil {
return err
}

return nil
}

func (repository *BlocksPostgresRepository) DeleteTestBlock(blockUUID string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

// Get the UUID of the block index
query := `
SELECT block_index_id
FROM test_blocks
WHERE id = $1
`

row := repository.Connection.QueryRowContext(ctx, query, blockUUID)
var blockIndexUUID string
if err := row.Scan(&blockIndexUUID); err != nil {
if err == sql.ErrNoRows {
return &errors.BlockNotFound{}
}

return err
}

// After deleting the block index, the block will be deleted automatically due to the `ON DELETE CASCADE` constraint
err = repository.deleteBlockIndex(blockIndexUUID)
if err != nil {
return err
}

return nil
}

func (repository *BlocksPostgresRepository) deleteBlockIndex(blockIndexUUID string) (err error) {
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()

query := `
DELETE FROM blocks_index
WHERE id = $1
`

_, err = repository.Connection.ExecContext(ctx, query, blockIndexUUID)
if err != nil {
return err
}

return nil
}

0 comments on commit 1aee6d6

Please sign in to comment.