Skip to content

Commit

Permalink
Changed time entries can be fetched via service now. #29
Browse files Browse the repository at this point in the history
  • Loading branch information
ahilwers committed Sep 9, 2023
1 parent bbcf47c commit 413be77
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 12 deletions.
3 changes: 2 additions & 1 deletion server/src/cmd/service/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ func main() {
projectHandler := rest.NewProjectHandler(tokenVerifier, projectUsecase, teamUsecase)
timeEntryUsecase := usecase.NewTimeEntryUsecase(database.NewGormTimeEntryRepository(databaseService.Database), projectUsecase)
timeEntryHandler := rest.NewTimeEntryHandler(tokenVerifier, timeEntryUsecase)
syncHandler := rest.NewSyncHandler(tokenVerifier, timeEntryUsecase)

router := rest.SetupRouter(authMiddleware, teamHandler, projectHandler, timeEntryHandler)
router := rest.SetupRouter(authMiddleware, teamHandler, projectHandler, timeEntryHandler, syncHandler)
router.Run()
}
4 changes: 3 additions & 1 deletion server/src/pkg/transport/rest/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type HandlerTest struct {
ProjectHandler ProjectHandler
TimeEntryHandler TimeEntryHandler
TeamHandler TeamHandler
SyncHandler SyncHandler
Router *gin.Engine
tokenVerifier TokenVerifier
}
Expand Down Expand Up @@ -97,8 +98,9 @@ func (t *HandlerTest) initHandlers() {
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.TimeEntryUsecase)

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
3 changes: 2 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,7 @@ 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.GetChangedTimeEntries)

return router
}
13 changes: 7 additions & 6 deletions server/src/pkg/transport/rest/sync_dtos.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ func (c *ChangeType) parse(sType string) (ChangeType, error) {
}

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"`
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"`
}
54 changes: 51 additions & 3 deletions server/src/pkg/transport/rest/sync_handler.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
package rest

import (
"net/http"
"strconv"
"time"
"timeasy-server/pkg/usecase"

"github.com/gin-gonic/gin"
)

Expand All @@ -12,16 +17,59 @@ type SyncHandler interface {
}

type syncHandler struct {
tokenVerifier TokenVerifier
tokenVerifier TokenVerifier
timeEntryUsecase usecase.TimeEntryUsecase
}

func NewSyncHandler(tokenVerifier TokenVerifier) SyncHandler {
func NewSyncHandler(tokenVerifier TokenVerifier, timeEntryUsecase usecase.TimeEntryUsecase) SyncHandler {
return &syncHandler{
tokenVerifier: tokenVerifier,
tokenVerifier: tokenVerifier,
timeEntryUsecase: timeEntryUsecase,
}
}

func (handler *syncHandler) GetChangedTimeEntries(context *gin.Context) {
token, err := handler.tokenVerifier.VerifyToken(context)
if err != nil {
context.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
userId, err := token.GetUserId()
if err != nil {
context.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
timeParam := context.Param("timestamp")
unixTime, err := strconv.ParseInt(timeParam, 10, 64)
if err != nil {
context.JSON(http.StatusBadRequest, gin.H{"error": "please provide a valid unix timestamp"})
return
}

var syncEntries []ChangedTimeEntryDto
entries, err := handler.timeEntryUsecase.GetChangedEntries(userId, time.Unix(unixTime, 0))
for _, entry := range entries {
changeType := CHANGED
changeTime := entry.UpdatedAt
if !entry.DeletedAt.Time.IsZero() {
changeType = DELETED
changeTime = entry.DeletedAt.Time
} else if entry.CreatedAt == entry.UpdatedAt {
changeType = NEW
changeTime = entry.CreatedAt
}
syncEntry := ChangedTimeEntryDto{
Id: entry.ID,
Description: entry.Description,
StartTimeUTCUnix: entry.StartTime.Unix(),
EndTimeUTCUnix: entry.EndTime.Unix(),
ProjectId: entry.ProjectId,
ChangeType: changeType,
ChangeTimestampUTCUnix: changeTime.Unix(),
}
syncEntries = append(syncEntries, syncEntry)
}
context.JSON(http.StatusOK, syncEntries)
}

func (handler *syncHandler) SendLocallyChangedTimeEntries(context *gin.Context) {
Expand Down
101 changes: 101 additions & 0 deletions server/src/pkg/transport/rest/sync_handler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package rest

import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"time"
"timeasy-server/pkg/domain/model"

"github.com/gofrs/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)

func Test_syncHandler_GetChangedTimeEntries(t *testing.T) {
userId, err := uuid.NewV4()
assert.Nil(t, err)
token := authTokenMock{}
token.On("GetUserId").Return(userId, nil)
token.On("HasRole", model.RoleUser).Return(true, nil)
token.On("HasRole", model.RoleAdmin).Return(false, nil)

verifier := tokenVerifierMock{}
verifier.On("VerifyToken", mock.Anything).Return(&token, nil)

handlerTest := NewHandlerTest(&verifier)
teardownTest := handlerTest.SetupTest(t)
defer teardownTest(t)

project := model.Project{
Name: "project",
UserId: userId,
}
err = handlerTest.ProjectUsecase.AddProject(&project)
assert.Nil(t, err)

startTime := time.Date(2023, 1, 28, 11, 0, 0, 0, time.UTC)

unchangedTimeEntry := model.TimeEntry{
Description: "unchanged_timeentry",
StartTime: startTime,
ProjectId: project.ID,
UserId: userId,
}
unchangedTimeEntry.UpdatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
unchangedTimeEntry.CreatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
err = handlerTest.TimeEntryUsecase.AddTimeEntry(&unchangedTimeEntry)
assert.Nil(t, err)

updatedTimeEntry := model.TimeEntry{
Description: "original_timeentry",
StartTime: startTime.Add(time.Hour),
ProjectId: project.ID,
UserId: userId,
}
updatedTimeEntry.UpdatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
updatedTimeEntry.CreatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
err = handlerTest.TimeEntryUsecase.AddTimeEntry(&updatedTimeEntry)
assert.Nil(t, err)
updatedTimeEntry.Description = "updated_timeetry"
err = handlerTest.TimeEntryUsecase.UpdateTimeEntry(&updatedTimeEntry)
assert.Nil(t, err)

deletedTimeEntry := model.TimeEntry{
Description: "deleted_timeentry",
StartTime: startTime.Add(time.Hour).Add(time.Hour),
ProjectId: project.ID,
UserId: userId,
}
deletedTimeEntry.UpdatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
deletedTimeEntry.CreatedAt = time.Date(2023, 8, 1, 0, 0, 0, 0, time.UTC)
err = handlerTest.TimeEntryUsecase.AddTimeEntry(&deletedTimeEntry)
assert.Nil(t, err)
err = handlerTest.TimeEntryUsecase.DeleteTimeEntry(deletedTimeEntry.ID)
assert.Nil(t, err)

w := httptest.NewRecorder()

req, _ := http.NewRequest("GET", fmt.Sprintf("/api/v1/sync/changed/%v", time.Now().Unix()), nil)
handlerTest.Router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)

var syncEntries []ChangedTimeEntryDto
json.Unmarshal(w.Body.Bytes(), &syncEntries)
assert.Equal(t, 2, len(syncEntries))

assert.Equal(t, deletedTimeEntry.Description, syncEntries[0].Description)
assert.Equal(t, deletedTimeEntry.StartTime, time.Unix(syncEntries[0].StartTimeUTCUnix, 0).UTC())
assert.Equal(t, deletedTimeEntry.EndTime, time.Unix(syncEntries[0].EndTimeUTCUnix, 0).UTC())
assert.Equal(t, deletedTimeEntry.ProjectId, syncEntries[0].ProjectId)
assert.Equal(t, DELETED, syncEntries[0].ChangeType)

assert.Equal(t, updatedTimeEntry.Description, syncEntries[1].Description)
assert.Equal(t, updatedTimeEntry.StartTime, time.Unix(syncEntries[1].StartTimeUTCUnix, 0).UTC())
assert.Equal(t, updatedTimeEntry.EndTime, time.Unix(syncEntries[1].EndTimeUTCUnix, 0).UTC())
assert.Equal(t, updatedTimeEntry.ProjectId, syncEntries[1].ProjectId)
assert.Equal(t, CHANGED, syncEntries[1].ChangeType)

}

0 comments on commit 413be77

Please sign in to comment.