Skip to content

Commit

Permalink
Merge pull request #36 from ahilwers/29-create-a-resource-for-a-sync-…
Browse files Browse the repository at this point in the history
…service

29 create a resource for a sync service
  • Loading branch information
ahilwers committed Oct 30, 2023
2 parents ff460a2 + 184f251 commit 0697cdb
Show file tree
Hide file tree
Showing 16 changed files with 1,357 additions and 8 deletions.
10 changes: 7 additions & 3 deletions server/src/cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,16 @@ func main() {
teamRepository := database.NewGormTeamRepository(databaseService.Database)
teamUsecase := usecase.NewTeamUsecase(teamRepository)
teamHandler := rest.NewTeamHandler(tokenVerifier, teamUsecase)
projectUsecase := usecase.NewProjectUsecase(database.NewGormProjectRepository(databaseService.Database,
teamRepository), teamUsecase)

projectUsecase := usecase.NewProjectUsecase(database.NewGormProjectRepository(databaseService.Database, teamRepository), teamUsecase)
projectHandler := rest.NewProjectHandler(tokenVerifier, projectUsecase, teamUsecase)

timeEntryUsecase := usecase.NewTimeEntryUsecase(database.NewGormTimeEntryRepository(databaseService.Database), projectUsecase)
timeEntryHandler := rest.NewTimeEntryHandler(tokenVerifier, timeEntryUsecase)

router := rest.SetupRouter(authMiddleware, teamHandler, projectHandler, timeEntryHandler)
syncUsecase := usecase.NewSyncUsecase(database.NewGormSyncRepository(databaseService.Database))
syncHandler := rest.NewSyncHandler(tokenVerifier, syncUsecase)

router := rest.SetupRouter(authMiddleware, teamHandler, projectHandler, timeEntryHandler, syncHandler)
router.Run()
}
78 changes: 78 additions & 0 deletions server/src/pkg/database/gorm_sync_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package database

import (
"time"
"timeasy-server/pkg/domain/model"
"timeasy-server/pkg/domain/repository"

"github.com/gofrs/uuid"
"gorm.io/gorm"
)

type gormSyncRepository struct {
db *gorm.DB
}

func NewGormSyncRepository(database *gorm.DB) repository.SyncRepository {
return &gormSyncRepository{
db: database,
}
}

func (repo *gormSyncRepository) UpdateAndDeleteData(data model.SyncData) error {
return repo.db.Transaction(func(tx *gorm.DB) error {
err := repo.updateAndDeleteProjects(tx, data)
if err != nil {
return err
}
err = repo.updateAndDeleteTimeEntries(tx, data)
if err != nil {
return err
}
return nil
})
}

func (repo *gormSyncRepository) updateAndDeleteProjects(tx *gorm.DB, data model.SyncData) error {
for _, project := range data.ProjectsToBeUpdated {
if err := tx.Save(&project).Error; err != nil {
return err
}
}
for _, project := range data.ProjectsToBeDeleted {
if err := tx.Delete(&project).Error; err != nil {
return err
}
}
return nil
}

func (repo *gormSyncRepository) updateAndDeleteTimeEntries(tx *gorm.DB, data model.SyncData) error {
for _, timeEntry := range data.TimeEntriesToBeUpdated {
if err := tx.Save(&timeEntry).Error; err != nil {
return err
}
}
for _, timeEntry := range data.TimeEntriesToBeDeleted {
if err := tx.Delete(&timeEntry).Error; err != nil {
return err
}
}
return nil
}

func (repo *gormSyncRepository) GetUpdatedTimeEntriesOfUser(userId uuid.UUID, sinceWhen time.Time) ([]model.TimeEntry, error) {
var updatedEntries []model.TimeEntry
if err := repo.db.Unscoped().Order("start_time desc").Order("end_time desc").Find(&updatedEntries, "user_id=? AND (updated_at >= ? OR created_at >= ? OR deleted_at >= ?)", userId, sinceWhen, sinceWhen, sinceWhen).Error; err != nil {
return nil, err
}
return updatedEntries, nil
}

func (repo *gormSyncRepository) GetUpdatedProjectsOfUser(userId uuid.UUID, sinceWhen time.Time) ([]model.Project, error) {
var updatedProjects []model.Project
if err := repo.db.Unscoped().Order("name").Find(&updatedProjects, "user_id=? AND (updated_at >= ? OR created_at >= ? OR deleted_at >= ?)", userId, sinceWhen, sinceWhen, sinceWhen).Error; err != nil {
return nil, err
}
return updatedProjects, nil
}
22 changes: 22 additions & 0 deletions server/src/pkg/database/gorm_timeentry_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ func (repo *gormTimeEntryRepository) AddTimeEntry(timeEntry *model.TimeEntry) er
return nil
}

func (repo *gormTimeEntryRepository) AddTimeEntryList(timeEntryList []model.TimeEntry) error {
return repo.db.Transaction(func(tx *gorm.DB) error {
for _, timeEntry := range timeEntryList {
if err := tx.Create(&timeEntry).Error; err != nil {
return err
}
}
return nil
})
}

func (repo *gormTimeEntryRepository) GetTimeEntryById(id uuid.UUID) (*model.TimeEntry, error) {
var timeEntry model.TimeEntry
if err := repo.db.First(&timeEntry, id).Error; err != nil {
Expand All @@ -40,6 +51,17 @@ func (repo *gormTimeEntryRepository) UpdateTimeEntry(timeEntry *model.TimeEntry)
return nil
}

func (repo *gormTimeEntryRepository) UpdateTimeEntryList(timeEntryList []model.TimeEntry) error {
return repo.db.Transaction(func(tx *gorm.DB) error {
for _, timeEntry := range timeEntryList {
if err := tx.Save(&timeEntry).Error; err != nil {
return err
}
}
return nil
})
}

func (repo *gormTimeEntryRepository) DeleteTimeEntry(timeEntry *model.TimeEntry) error {
if err := repo.db.Delete(timeEntry).Error; err != nil {
return err
Expand Down
8 changes: 8 additions & 0 deletions server/src/pkg/domain/model/sync_data.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package model

type SyncData struct {
TimeEntriesToBeUpdated []TimeEntry
TimeEntriesToBeDeleted []TimeEntry
ProjectsToBeUpdated []Project
ProjectsToBeDeleted []Project
}
14 changes: 14 additions & 0 deletions server/src/pkg/domain/repository/sync_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package repository

import (
"time"
"timeasy-server/pkg/domain/model"

"github.com/gofrs/uuid"
)

type SyncRepository interface {
UpdateAndDeleteData(data model.SyncData) error
GetUpdatedTimeEntriesOfUser(userId uuid.UUID, sinceWhen time.Time) ([]model.TimeEntry, error)
GetUpdatedProjectsOfUser(userId uuid.UUID, sinceWhen time.Time) ([]model.Project, error)
}
4 changes: 3 additions & 1 deletion server/src/pkg/domain/repository/timeentry_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (

type TimeEntryRepository interface {
AddTimeEntry(project *model.TimeEntry) error
UpdateTimeEntry(project *model.TimeEntry) error
AddTimeEntryList(timeEntryList []model.TimeEntry) error
UpdateTimeEntry(timeEntry *model.TimeEntry) error
UpdateTimeEntryList(timeEntryList []model.TimeEntry) error
DeleteTimeEntry(project *model.TimeEntry) error
GetTimeEntryById(id uuid.UUID) (*model.TimeEntry, error)
GetAllTimeEntriesOfUser(userId uuid.UUID) ([]model.TimeEntry, error)
Expand Down
8 changes: 7 additions & 1 deletion server/src/pkg/transport/rest/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ type HandlerTest struct {
ProjectUsecase usecase.ProjectUsecase
TimeEntryUsecase usecase.TimeEntryUsecase
TeamUsecase usecase.TeamUsecase
SyncUsecase usecase.SyncUsecase
ProjectHandler ProjectHandler
TimeEntryHandler TimeEntryHandler
TeamHandler TeamHandler
SyncHandler SyncHandler
Router *gin.Engine
tokenVerifier TokenVerifier
}
Expand Down Expand Up @@ -90,15 +92,19 @@ func (t *HandlerTest) initUsecases() {

timeEntryRepo := database.NewGormTimeEntryRepository(test.DB)
t.TimeEntryUsecase = usecase.NewTimeEntryUsecase(timeEntryRepo, t.ProjectUsecase)

syncRepo := database.NewGormSyncRepository(test.DB)
t.SyncUsecase = usecase.NewSyncUsecase(syncRepo)
}

func (t *HandlerTest) initHandlers() {
authMiddleware := NewJwtAuthMiddleware(t.tokenVerifier)
t.ProjectHandler = NewProjectHandler(t.tokenVerifier, t.ProjectUsecase, t.TeamUsecase)
t.TimeEntryHandler = NewTimeEntryHandler(t.tokenVerifier, t.TimeEntryUsecase)
t.TeamHandler = NewTeamHandler(t.tokenVerifier, t.TeamUsecase)
t.SyncHandler = NewSyncHandler(t.tokenVerifier, t.SyncUsecase)

t.Router = SetupRouter(authMiddleware, t.TeamHandler, t.ProjectHandler, t.TimeEntryHandler)
t.Router = SetupRouter(authMiddleware, t.TeamHandler, t.ProjectHandler, t.TimeEntryHandler, t.SyncHandler)
}

func AssertErrorMessageEquals(t *testing.T, responseBody []byte, expectedMessage string) {
Expand Down
4 changes: 3 additions & 1 deletion server/src/pkg/transport/rest/routing.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
ginglog "github.com/szuecs/gin-glog"
)

func SetupRouter(authMiddleware AuthMiddleware, teamHandler TeamHandler, projectHandler ProjectHandler, timeEntryHandler TimeEntryHandler) *gin.Engine {
func SetupRouter(authMiddleware AuthMiddleware, teamHandler TeamHandler, projectHandler ProjectHandler, timeEntryHandler TimeEntryHandler, syncHandler SyncHandler) *gin.Engine {
router := gin.Default()

router.Use(ginglog.Logger(3 * time.Second))
Expand All @@ -34,6 +34,8 @@ func SetupRouter(authMiddleware AuthMiddleware, teamHandler TeamHandler, project
protectedGroup.POST("/teams/:id/users", teamHandler.AddUserToTeam)
protectedGroup.DELETE("/teams/:id/users/:userId", teamHandler.DeleteUserFromTeam)
protectedGroup.PUT("/teams/:id/users/:userId/roles", teamHandler.UpdateUserRolesInTeam)
protectedGroup.GET("/sync/changed/:timestamp", syncHandler.GetChangedEntries)
protectedGroup.POST("/sync/changed", syncHandler.SendLocallyChangedEntries)

return router
}
81 changes: 81 additions & 0 deletions server/src/pkg/transport/rest/sync_dtos.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package rest

import (
"encoding/json"
"fmt"
"strings"

"github.com/gofrs/uuid"
)

type ChangeType uint8

const (
NEW ChangeType = iota
CHANGED
DELETED
)

var (
ChangeType_Name = map[uint8]string{
0: "NEW",
1: "CHANGED",
2: "DELETED",
}

ChangeType_Value = map[string]uint8{
"NEW": 0,
"CHANGED": 1,
"DELETED": 2,
}
)

func (c ChangeType) String() string {
return ChangeType_Name[uint8(c)]
}

func (c ChangeType) MarshalJSON() ([]byte, error) {
return json.Marshal(c.String())
}

func (c *ChangeType) UnmarshalJSON(data []byte) (err error) {
var sType string
if err := json.Unmarshal(data, &sType); err != nil {
return err
}
if *c, err = c.parse(sType); err != nil {
return err
}
return nil
}

func (c *ChangeType) parse(sType string) (ChangeType, error) {
sType = strings.TrimSpace(strings.ToUpper(sType))
value, ok := ChangeType_Value[sType]
if !ok {
return ChangeType(0), fmt.Errorf("%v is not a valid change type", sType)
}
return ChangeType(value), nil
}

type SyncEntries struct {
TimeEntries []ChangedTimeEntryDto
Projects []ChangedProjectDto
}

type ChangedTimeEntryDto struct {
Id uuid.UUID
Description string `json:"description" binding:"required"`
StartTimeUTCUnix int64 `json:"startTimeUTCUnix" binding:"required"`
EndTimeUTCUnix int64
ProjectId uuid.UUID `json:"projectId" binding:"required"`
ChangeType ChangeType `json:"changeType" binding:"required"`
ChangeTimestampUTCUnix int64 `json:"changeTimestampUTCUnix" binding:"required"`
}

type ChangedProjectDto struct {
Id uuid.UUID
Name string `json:"name" binding:"required"`
ChangeType ChangeType `json:"changeType" binding:"required"`
ChangeTimestampUTCUnix int64 `json:"changeTimestampUTCUnix" binding:"required"`
}
Loading

0 comments on commit 0697cdb

Please sign in to comment.