Skip to content

Commit

Permalink
feat: introduce time tracking record key, add time tracking manager i…
Browse files Browse the repository at this point in the history
…nterface together with extension of local and S3 repository
  • Loading branch information
tommzn committed Dec 29, 2022
1 parent 3cdd86b commit fdf8b6d
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 7 deletions.
11 changes: 11 additions & 0 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ type TimeTracker interface {
ListRecords(string, time.Time, time.Time) ([]TimeTrackingRecord, error)
}

// TimeTrackingRecordManager is used to create, update or delete single time tracking records.
type TimeTrackingRecordManager interface {

// Add creates a new time tracking record with given values. Same time tacking record will be
// returned together with a generated key.
Add(TimeTrackingRecord) (TimeTrackingRecord, error)

// Delete will remove time tracking record by passed key.
Delete(string) error
}

// ReportCalculator creates a time tracking summary based on captured records.
type ReportCalculator interface {

Expand Down
89 changes: 85 additions & 4 deletions local.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package timetracker

import (
"errors"
"fmt"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -30,7 +33,14 @@ func (repo *LocaLRepository) Captured(deviceId string, recordType RecordType, ti
if _, ok := repo.Records[deviceId][date]; !ok {
repo.Records[deviceId][date] = []TimeTrackingRecord{}
}
repo.Records[deviceId][date] = append(repo.Records[deviceId][date], TimeTrackingRecord{DeviceId: deviceId, Type: recordType, Timestamp: timestamp, Estimated: false})
record := TimeTrackingRecord{
DeviceId: deviceId,
Type: recordType,
Timestamp: timestamp,
Estimated: false,
}
record.Key = repo.recordKey(deviceId, record.Timestamp, len(repo.Records[deviceId][date]))
repo.Records[deviceId][date] = append(repo.Records[deviceId][date], record)
return nil
}

Expand All @@ -41,13 +51,84 @@ func (repo *LocaLRepository) ListRecords(deviceId string, start time.Time, end t
if end.Before(start) {
return records, fmt.Errorf("Invalid range: %s - %s", start, end)
}
if deviceREcords, ok := repo.Records[deviceId]; ok {
if deviceRecords, ok := repo.Records[deviceId]; ok {
for isDayBeforeOrEqual(start, end) {
if recordsForDay, ok := deviceREcords[asDate(start)]; ok {
records = append(records, recordsForDay...)
if recordsForDay, ok := deviceRecords[asDate(start)]; ok {
for idx, record := range recordsForDay {
record.Key = repo.recordKey(deviceId, start, idx)
records = append(records, record)
}
}
start = nextDay(start)
}
}
return records, nil
}

// Add creates a new time tracking record with given values. Same time tacking record will be
// returned together with a generated key.
func (repo *LocaLRepository) Add(record TimeTrackingRecord) (TimeTrackingRecord, error) {

deviceId := record.DeviceId
date := asDate(record.Timestamp)
if _, ok := repo.Records[deviceId]; !ok {
repo.Records[deviceId] = make(map[Date][]TimeTrackingRecord)
}
if _, ok := repo.Records[deviceId][date]; !ok {
repo.Records[deviceId][date] = []TimeTrackingRecord{}
}
record.Key = repo.recordKey(deviceId, record.Timestamp, len(repo.Records[deviceId][date]))
repo.Records[deviceId][date] = append(repo.Records[deviceId][date], record)
return record, nil
}

// Delete will remove given time tracking record.
func (repo *LocaLRepository) Delete(key string) error {

keyParts := strings.Split(key, "/")
if len(keyParts) != 3 {
return errors.New("Invalid key passed.")
}

deviceId := keyParts[0]
_, ok := repo.Records[deviceId]
if !ok {
return errors.New("Invalid deviceid: " + deviceId)
}

dateStr := keyParts[1]
timestamp, err := time.Parse("2006-01-02", dateStr)
if err != nil {
return err
}

date := asDate(timestamp)
_, ok1 := repo.Records[deviceId][date]
if !ok1 {
return errors.New("Invalid date: " + dateStr)
}

idx, err := strconv.Atoi(keyParts[2])
if err != nil {
return err
}

if len(repo.Records[deviceId][date])-1 < idx {
return errors.New("Invalid index: " + keyParts[2])
}

repo.Records[deviceId][date] = removeRecordAtIndex(repo.Records[deviceId][date], idx)
return nil
}

// RecordKey compose given part to a record key.
func (repo *LocaLRepository) recordKey(deviceId string, day time.Time, idx int) string {
return fmt.Sprintf("%s/%s/%d", deviceId, asDate(day).String(), idx)
}

// RemoveRecordAtIndex removes time tracking records from given list at specific index.
func removeRecordAtIndex(records []TimeTrackingRecord, index int) []TimeTrackingRecord {
ret := make([]TimeTrackingRecord, 0)
ret = append(ret, records[:index]...)
return append(ret, records[index+1:]...)
}
16 changes: 16 additions & 0 deletions local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ func (suite *LocalRepositoryTestSuite) TestListRecords() {
suite.Len(records3, 0)
}

func (suite *LocalRepositoryTestSuite) TestRecordCrudActions() {

repo := NewLocaLRepository()
record := TimeTrackingRecord{
DeviceId: "Device01",
Type: WORKDAY,
Timestamp: time.Now(),
}

record1, err := repo.Add(record)
suite.Nil(err)
suite.True(len(record1.Key) > 0)

suite.Nil(repo.Delete(record1.Key))
}

func prepareRecords(repo *LocaLRepository, deviceId string) {

durations := []time.Duration{
Expand Down
42 changes: 39 additions & 3 deletions s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,18 +63,24 @@ func (repo *S3Repository) Capture(deviceId string, recordType RecordType) error
// Captured creates a time tracking record for passed point in time.
func (repo *S3Repository) Captured(deviceId string, recordType RecordType, timestamp time.Time) error {

timeTrackingRecord := TimeTrackingRecord{DeviceId: deviceId, Type: recordType, Timestamp: timestamp.UTC()}
content, _ := json.Marshal(timeTrackingRecord)
timeTrackingRecord := TimeTrackingRecord{
DeviceId: deviceId,
Type: recordType,
Timestamp: timestamp.UTC(),
}
objectPath := repo.newS3ObjectPath(deviceId, timeTrackingRecord.Timestamp)
timeTrackingRecord.Key = *objectPath + repo.newRecordId()
content, _ := json.Marshal(timeTrackingRecord)
uploadInput := &s3manager.UploadInput{
Bucket: repo.bucket,
Key: aws.String(*objectPath + repo.newRecordId()),
Key: aws.String(timeTrackingRecord.Key),
Body: bytes.NewReader(content),
}
_, uploadErr := repo.uploader.Upload(uploadInput)
return uploadErr
}

// ListRecords returns all records captured for given device id and time range.
func (repo *S3Repository) ListRecords(deviceId string, start time.Time, end time.Time) ([]TimeTrackingRecord, error) {

records := []TimeTrackingRecord{}
Expand Down Expand Up @@ -117,11 +123,41 @@ func (repo *S3Repository) ListRecords(deviceId string, start time.Time, end time
if decodeErr != nil {
return records, decodeErr
}
timeTrackingRecord.Key = *key
records = append(records, *timeTrackingRecord)
}
return records, nil
}

// Add creates a new time tracking record with given values. Same time tacking record will be
// returned together with a generated key.
func (repo *S3Repository) Add(record TimeTrackingRecord) (TimeTrackingRecord, error) {

objectPath := repo.newS3ObjectPath(record.DeviceId, record.Timestamp)
record.Key = *objectPath + repo.newRecordId()

content, _ := json.Marshal(record)
uploadInput := &s3manager.UploadInput{
Bucket: repo.bucket,
Key: aws.String(record.Key),
Body: bytes.NewReader(content),
}
_, err := repo.uploader.Upload(uploadInput)
return record, err
}

// Delete will remove given time tracking record.
func (repo *S3Repository) Delete(key string) error {

deleteObjectInput := &s3.DeleteObjectInput{
Bucket: repo.bucket,
Key: aws.String(key),
}

_, err := repo.s3.DeleteObject(deleteObjectInput)
return err
}

// NewS3ObjectPath create a S3 object key including passed device id and date.
// Will add a path prefix if it has been defined at creating this repository.
func (repo *S3Repository) newS3ObjectPath(deviceId string, t time.Time) *string {
Expand Down
19 changes: 19 additions & 0 deletions s3_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,25 @@ func (suite *S3TestSuite) TestListRecords() {
suite.Len(records1, 0)
}

func (suite *S3TestSuite) TestRecordCrudActions() {

suite.skipCI()

repo := suite.s3RepoForTest()

record := TimeTrackingRecord{
DeviceId: "Device01",
Type: WORKDAY,
Timestamp: time.Now(),
}

record1, err := repo.Add(record)
suite.Nil(err)
suite.True(len(record1.Key) > 0)

suite.Nil(repo.Delete(record1.Key))
}

func (suite *S3TestSuite) TestPublishReport() {

suite.skipCI()
Expand Down
3 changes: 3 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ type MonthlyReport struct {
// TimeTrackingReport os a single captured time tracking event.
type TimeTrackingRecord struct {

// Key is an unique identifier of a time tracking record.
Key string

// DeviceId is an identifier of a device which captures a time tracking record.
DeviceId string

Expand Down

0 comments on commit fdf8b6d

Please sign in to comment.