Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix course identifiers #19

Merged
merged 6 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
10 changes: 6 additions & 4 deletions db/migrations/1657379568_create_courses_table.up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ BEGIN;

CREATE TABLE courses
(
id bigserial CONSTRAINT courses_pk PRIMARY KEY,
id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
rluders marked this conversation as resolved.
Show resolved Hide resolved
uuid uuid DEFAULT uuid_generate_v4() NOT NULL,
code varchar NOT NULL UNIQUE,
code varchar NOT NULL,
rluders marked this conversation as resolved.
Show resolved Hide resolved
name varchar NOT NULL,
underline varchar NOT NULL,
image varchar NULL,
Expand All @@ -16,9 +16,11 @@ CREATE TABLE courses
deleted_at timestamp
);

CREATE UNIQUE INDEX courses_id_uindex
ON courses (id);
COMMENT ON COLUMN courses.deleted_at IS 'Timestamp indicating when a course was softly deleted, allowing for data recovery and historical queries without permanently removing the record from the database. A NULL value means the subscription is active.';
marianysilva marked this conversation as resolved.
Show resolved Hide resolved

CREATE UNIQUE INDEX courses_uuid_uindex
ON courses (uuid);
CREATE UNIQUE INDEX courses_code_uindex
ON courses (code, deleted_at) NULLS NOT DISTINCT;

COMMIT;
46 changes: 29 additions & 17 deletions internal/course/database/course_queries.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,39 @@
package database

import (
"fmt"
)

const (
createCourse = "create course"
deleteCourse = "delete course by uuid"
getCourse = "get course by uuid"
listCourse = "list course"
updateCourseByID = "update course by uuid"
updateCourseByCode = "update course by code"
returningColumns = "uuid, code, name, underline, image, image_cover, excerpt, description, created_at, updated_at"
// CREATE.
rluders marked this conversation as resolved.
Show resolved Hide resolved
createCourse = "create course"
// READ.
listCourse = "list course"
getCourse = "get course by uuid"
// UPDATE.
updateCourse = "update course by uuid"
// DELETE.
deleteCourse = "delete course by uuid"
)

func queriesCourse() map[string]string {
return map[string]string{
createCourse: `INSERT INTO
courses (code, name, underline, image, image_cover, excerpt, description)
VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
// CREATE.
createCourse: fmt.Sprintf(`INSERT INTO courses (
code, name, underline, image, image_cover, excerpt, description
) VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING %s`, returningColumns),
// READ.
listCourse: fmt.Sprintf("SELECT %s FROM courses WHERE deleted_at IS NULL", returningColumns),
getCourse: fmt.Sprintf("SELECT %s FROM courses WHERE uuid = $1 AND deleted_at IS NULL", returningColumns),
marianysilva marked this conversation as resolved.
Show resolved Hide resolved
// UPDATE.
updateCourse: fmt.Sprintf(`UPDATE courses SET
code = $1, name = $2, underline = $3, image = $4, image_cover = $5, excerpt = $6, description = $7
WHERE uuid = $8
AND deleted_at IS NULL
RETURNING %s`, returningColumns),
// DELETE.
deleteCourse: "UPDATE courses SET deleted_at = NOW() WHERE uuid = $1 AND deleted_at IS NULL",
getCourse: "SELECT * FROM courses WHERE uuid = $1 AND deleted_at IS NULL",
listCourse: "SELECT * FROM courses WHERE deleted_at IS NULL",
updateCourseByID: `UPDATE courses
SET code = $1, name = $2, underline = $3, image = $4, image_cover = $5, excerpt = $6, description = $7
WHERE uuid = $8 AND deleted_at IS NULL RETURNING *`,
updateCourseByCode: `UPDATE courses
SET name = $1, underline = $2, image = $3, image_cover = $4, excerpt = $5, description = $6
WHERE code = $7 AND deleted_at IS NULL RETURNING *`,
}
}
42 changes: 9 additions & 33 deletions internal/course/database/course_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ func (r CourseRepository) statement(s string) (*sqlx.Stmt, error) {
return stmt, nil
}

// Course get the Course by given id.
func (r CourseRepository) Course(id uuid.UUID) (domain.Course, error) {
// Course get the Course by given uuid.
func (r CourseRepository) Course(courseUUID uuid.UUID) (domain.Course, error) {
stmt, err := r.statement(getCourse)
if err != nil {
return domain.Course{}, err
}

var c domain.Course
if err := stmt.Get(&c, id); err != nil {
if err := stmt.Get(&c, courseUUID); err != nil {
return domain.Course{}, errors.WrapErrorf(err, errors.ErrCodeUnknown, "error getting course")
}
return c, nil
Expand Down Expand Up @@ -86,9 +86,9 @@ func (r CourseRepository) CreateCourse(c *domain.Course) error {
return nil
}

// UpdateCourseByID update the given course by ID.
func (r CourseRepository) UpdateCourseByID(c *domain.Course) error {
stmt, err := r.statement(updateCourseByID)
// UpdateCourse update the given course by ID.
func (r CourseRepository) UpdateCourse(c *domain.Course) error {
stmt, err := r.statement(updateCourse)
if err != nil {
return err
}
Expand All @@ -111,38 +111,14 @@ func (r CourseRepository) UpdateCourseByID(c *domain.Course) error {
return nil
}

func (r CourseRepository) UpdateCourseByCode(c *domain.Course) error {
marianysilva marked this conversation as resolved.
Show resolved Hide resolved
stmt, err := r.statement(updateCourseByCode)
if err != nil {
return err
}

args := []interface{}{
// set
c.Name,
c.Underline,
c.Image,
c.ImageCover,
c.Excerpt,
c.Description,
// where
c.Code,
}

if err := stmt.Get(c, args...); err != nil {
return errors.WrapErrorf(err, errors.ErrCodeUnknown, "error updating course")
}
return nil
}

// DeleteCourse soft delete the course by given id.
func (r CourseRepository) DeleteCourse(id uuid.UUID) error {
// DeleteCourse soft delete the course by given uuid.
func (r CourseRepository) DeleteCourse(courseUUID uuid.UUID) error {
stmt, err := r.statement(deleteCourse)
if err != nil {
return err
}

if _, err := stmt.Exec(id); err != nil {
if _, err := stmt.Exec(courseUUID); err != nil {
return errors.WrapErrorf(err, errors.ErrCodeUnknown, "error deleting course")
}
return nil
Expand Down
56 changes: 26 additions & 30 deletions internal/course/database/course_repository_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (

var (
course = domain.Course{
ID: 1,
UUID: utils.CourseUUID,
Code: "SUME123",
Name: "Course Name",
Expand All @@ -25,7 +24,6 @@ var (
Description: "Course Description",
CreatedAt: utils.Now,
UpdatedAt: utils.Now,
DeletedAt: nil,
}
)

Expand All @@ -34,13 +32,13 @@ func newCourseTestDB() (*sqlx.DB, sqlmock.Sqlmock, map[string]*sqlmock.ExpectedP
}

func TestRepository_Course(t *testing.T) {
validRows := sqlmock.NewRows([]string{"id", "uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at", "deleted_at"}).
AddRow(course.ID, course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt, course.DeletedAt)
validRows := sqlmock.NewRows([]string{"uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at"}).
AddRow(course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt)

type args struct {
id uuid.UUID
courseUUID uuid.UUID
}

tests := []struct {
Expand All @@ -52,14 +50,14 @@ func TestRepository_Course(t *testing.T) {
}{
{
name: "get course",
args: args{id: course.UUID},
args: args{courseUUID: course.UUID},
rows: validRows,
want: course,
wantErr: false,
},
{
name: "course not found error",
args: args{id: uuid.MustParse("6cd7a01c-ff18-4cfb-9b35-16e710115c5f")},
args: args{courseUUID: uuid.MustParse("6cd7a01c-ff18-4cfb-9b35-16e710115c5f")},
rows: utils.EmptyRows,
want: domain.Course{},
wantErr: true,
Expand All @@ -83,7 +81,7 @@ func TestRepository_Course(t *testing.T) {

prep.ExpectQuery().WithArgs(utils.CourseUUID).WillReturnRows(validRows)

got, err := r.Course(tt.args.id)
got, err := r.Course(tt.args.courseUUID)
if (err != nil) != tt.wantErr {
t.Errorf("Course() error = %v, wantErr %v", err, tt.wantErr)
return
Expand All @@ -96,13 +94,13 @@ func TestRepository_Course(t *testing.T) {
}

func TestRepository_Courses(t *testing.T) {
validRows := sqlmock.NewRows([]string{"id", "uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at", "deleted_at"}).
AddRow(course.ID, course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt, course.DeletedAt).
AddRow(2, uuid.MustParse("7aec21ad-2fa8-4ddd-b5af-073144031ecc"), course.Code, course.Name,
validRows := sqlmock.NewRows([]string{"uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at"}).
AddRow(course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt).
AddRow(uuid.MustParse("7aec21ad-2fa8-4ddd-b5af-073144031ecc"), course.Code, course.Name,
course.Underline, course.Image, course.ImageCover, course.Excerpt, course.Description, course.CreatedAt,
course.UpdatedAt, course.DeletedAt)
course.UpdatedAt)

tests := []struct {
name string
Expand Down Expand Up @@ -154,10 +152,10 @@ func TestRepository_Courses(t *testing.T) {
}

func TestRepository_CreateCourse(t *testing.T) {
validRows := sqlmock.NewRows([]string{"id", "uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at", "deleted_at"}).
AddRow(course.ID, course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt, course.DeletedAt)
validRows := sqlmock.NewRows([]string{"uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at"}).
AddRow(course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt)

type args struct {
c *domain.Course
Expand Down Expand Up @@ -207,11 +205,11 @@ func TestRepository_CreateCourse(t *testing.T) {
}
}

func TestRepository_UpdateCourseByID(t *testing.T) {
validRows := sqlmock.NewRows([]string{"id", "uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at", "deleted_at"}).
AddRow(course.ID, course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt, course.DeletedAt)
func TestRepository_UpdateCourse(t *testing.T) {
validRows := sqlmock.NewRows([]string{"uuid", "code", "name", "underline", "image", "image_cover", "excerpt",
"description", "created_at", "updated_at"}).
AddRow(course.UUID, course.Code, course.Name, course.Underline, course.Image, course.ImageCover,
course.Excerpt, course.Description, course.CreatedAt, course.UpdatedAt)

type args struct {
c *domain.Course
Expand Down Expand Up @@ -247,18 +245,16 @@ func TestRepository_UpdateCourseByID(t *testing.T) {
if err != nil {
t.Fatalf("an error '%s' was not expected when creating the CourseRepository", err)
}
prep, ok := stmts[updateCourseByID]
prep, ok := stmts[updateCourse]
if !ok {
t.Fatalf("prepared statement %s not found", updateCourseByID)
t.Fatalf("prepared statement %s not found", updateCourse)
}

prep.ExpectQuery().WillReturnRows(tt.rows)

if err := r.UpdateCourseByID(tt.args.c); (err != nil) != tt.wantErr {
if err := r.UpdateCourse(tt.args.c); (err != nil) != tt.wantErr {
t.Errorf("UpdateCourse() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

// TODO Test_UpdateCourseByCode
23 changes: 10 additions & 13 deletions internal/course/domain/course.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,15 @@ import (
"github.com/google/uuid"
)

// Course struct.
type Course struct {
ID uint `json:"id"`
UUID uuid.UUID `json:"uuid"`
Code string `json:"code"`
Name string `json:"name"`
Underline string `json:"underline"`
Image string `json:"image"`
ImageCover string `db:"image_cover" json:"image_cover"`
Excerpt string `json:"excerpt"`
Description string `json:"description"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
DeletedAt *time.Time `db:"deleted_at" json:"deleted_at"`
UUID uuid.UUID `json:"uuid"`
Code string `json:"code"`
Name string `json:"name"`
Underline string `json:"underline"`
Image string `json:"image,omitempty"`
ImageCover string `db:"image_cover" json:"image_cover,omitempty"`
Excerpt string `json:"excerpt"`
Description string `json:"description,omitempty"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
7 changes: 3 additions & 4 deletions internal/course/domain/course_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,9 @@ package domain
import "github.com/google/uuid"

type CourseRepository interface {
Course(id uuid.UUID) (Course, error)
Course(courseUUID uuid.UUID) (Course, error)
Courses() ([]Course, error)
CreateCourse(c *Course) error
UpdateCourseByID(c *Course) error
UpdateCourseByCode(c *Course) error
DeleteCourse(id uuid.UUID) error
UpdateCourse(c *Course) error
DeleteCourse(courseUUID uuid.UUID) error
}
21 changes: 5 additions & 16 deletions internal/course/domain/course_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"github.com/google/uuid"
)

func (s *Service) Course(_ context.Context, id uuid.UUID) (Course, error) {
c, err := s.courses.Course(id)
func (s *Service) Course(_ context.Context, courseUUID uuid.UUID) (Course, error) {
c, err := s.courses.Course(courseUUID)
if err != nil {
return Course{}, fmt.Errorf("service can't find course: %w", err)
}
Expand All @@ -34,25 +34,14 @@ func (s *Service) CreateCourse(_ context.Context, c *Course) error {
}

func (s *Service) UpdateCourse(_ context.Context, c *Course) error {
exists, err := s.courses.Course(c.UUID)
if err != nil {
return err
}

if exists.Code == c.Code {
err = s.courses.UpdateCourseByCode(c)
} else {
err = s.courses.UpdateCourseByID(c)
}

if err != nil {
if err := s.courses.UpdateCourse(c); err != nil {
return fmt.Errorf("service can't update course: %w", err)
}
return nil
}

func (s *Service) DeleteCourse(_ context.Context, id uuid.UUID) error {
if err := s.courses.DeleteCourse(id); err != nil {
func (s *Service) DeleteCourse(_ context.Context, courseUUID uuid.UUID) error {
if err := s.courses.DeleteCourse(courseUUID); err != nil {
return fmt.Errorf("service can't delete course: %w", err)
}

Expand Down
Loading