Skip to content

Commit 605b67c

Browse files
authored
Merge pull request #6 from michaelcoll/feat/daemon-endpoints
feat : Add daemon endpoints
2 parents 452e383 + 5cfe7f4 commit 605b67c

File tree

11 files changed

+308
-66
lines changed

11 files changed

+308
-66
lines changed

Makefile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ build-web:
1010
cd internal/web \
1111
&& pnpm run build
1212

13+
.PHONY: test
14+
test:
15+
go test -v ./...
16+
1317
run:
1418
go run . serve
1519

go.mod

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ require (
66
github.com/fatih/color v1.13.0
77
github.com/gin-gonic/gin v1.8.1
88
github.com/google/uuid v1.3.0
9-
github.com/michaelcoll/gallery-proto v0.3.0
10-
github.com/spf13/cobra v1.5.0
9+
github.com/michaelcoll/gallery-proto v0.4.1
10+
github.com/spf13/cobra v1.6.0
11+
github.com/stretchr/testify v1.8.0
1112
google.golang.org/grpc v1.50.0
1213
)
1314

1415
require (
16+
github.com/davecgh/go-spew v1.1.1 // indirect
1517
github.com/gin-contrib/sse v0.1.0 // indirect
1618
github.com/go-playground/locales v0.14.0 // indirect
1719
github.com/go-playground/universal-translator v0.18.0 // indirect
@@ -26,13 +28,15 @@ require (
2628
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
2729
github.com/modern-go/reflect2 v1.0.2 // indirect
2830
github.com/pelletier/go-toml/v2 v2.0.5 // indirect
31+
github.com/pmezard/go-difflib v1.0.0 // indirect
2932
github.com/spf13/pflag v1.0.5 // indirect
3033
github.com/ugorji/go/codec v1.2.7 // indirect
31-
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b // indirect
34+
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 // indirect
3235
golang.org/x/net v0.0.0-20221004154528-8021a29435af // indirect
33-
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect
34-
golang.org/x/text v0.3.7 // indirect
35-
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 // indirect
36+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 // indirect
37+
golang.org/x/text v0.3.8 // indirect
38+
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e // indirect
3639
google.golang.org/protobuf v1.28.1 // indirect
3740
gopkg.in/yaml.v2 v2.4.0 // indirect
41+
gopkg.in/yaml.v3 v3.0.1 // indirect
3842
)

go.sum

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@ github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
2727
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
2828
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
2929
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
30-
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
3130
github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc=
3231
github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
3332
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -49,8 +48,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
4948
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
5049
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
5150
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
52-
github.com/michaelcoll/gallery-proto v0.3.0 h1:NgsERwiqX1DhzFdqfN0Z/6BbmaqYHTThD4fUWcIDabg=
53-
github.com/michaelcoll/gallery-proto v0.3.0/go.mod h1:u+8HDPcdiLSkZgArkWvFTD3fsGI7sddH6iZRH0An0aE=
51+
github.com/michaelcoll/gallery-proto v0.4.1 h1:aCZNloCoqKIpc6v+8Q6DndygWaXrB8XcrmYVad85BOA=
52+
github.com/michaelcoll/gallery-proto v0.4.1/go.mod h1:u+8HDPcdiLSkZgArkWvFTD3fsGI7sddH6iZRH0An0aE=
5453
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
5554
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
5655
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -65,8 +64,8 @@ github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTE
6564
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
6665
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
6766
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
68-
github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU=
69-
github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM=
67+
github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI=
68+
github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
7069
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
7170
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7271
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@@ -81,8 +80,8 @@ github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6
8180
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
8281
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
8382
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
84-
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0=
85-
golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
83+
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2 h1:x8vtB3zMecnlqZIwJNUUpwYKYSqCz5jXbiyv0ZJJZeI=
84+
golang.org/x/crypto v0.0.0-20221010152910-d6f0a8c073c2/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
8685
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
8786
golang.org/x/net v0.0.0-20221004154528-8021a29435af h1:wv66FM3rLZGPdxpYL+ApnDe2HzHcTFta3z5nsc13wI4=
8887
golang.org/x/net v0.0.0-20221004154528-8021a29435af/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
@@ -94,17 +93,18 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
9493
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9594
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9695
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
97-
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM=
98-
golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
96+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
97+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
9998
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
10099
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
101-
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
102100
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
101+
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
102+
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
103103
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
104104
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
105105
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
106-
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91 h1:Ezh2cpcnP5Rq60sLensUsFnxh7P6513NLvNtCm9iyJ4=
107-
google.golang.org/genproto v0.0.0-20220930163606-c98284e70a91/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
106+
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e h1:halCgTFuLWDRD61piiNSxPsARANGD3Xl16hPrLgLiIg=
107+
google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U=
108108
google.golang.org/grpc v1.50.0 h1:fPVVDxY9w++VjTZsYvXWqEf9Rqar/e+9zYfxKK+W+YU=
109109
google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
110110
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=

internal/photo/domain/service/daemonService.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,19 +20,24 @@ import (
2020
"context"
2121
"errors"
2222
"fmt"
23-
"google.golang.org/grpc/codes"
24-
"google.golang.org/grpc/status"
2523
"strconv"
2624
"sync"
2725
"time"
2826

2927
"github.com/fatih/color"
3028
"github.com/google/uuid"
29+
"google.golang.org/grpc/codes"
30+
"google.golang.org/grpc/status"
3131

3232
"github.com/michaelcoll/gallery-web/internal/photo/domain/model"
3333
)
3434

35-
const expiresIn = 2
35+
const (
36+
// number of seconds after a daemon is treated as not alive
37+
expiresIn = 3
38+
// number of seconds that will be added to the expiresIn value to calculate if a daemon is alive or not
39+
delta = 2
40+
)
3641

3742
type DaemonService struct {
3843
c PhotoServiceCaller
@@ -89,13 +94,13 @@ func (s *DaemonService) activate(d *model.Daemon) {
8994
color.GreenString(d.Name))
9095
}
9196
s.mu.Lock()
92-
d.NextSee = time.Now().Add(time.Duration(expiresIn) * time.Second)
97+
d.NextSee = time.Now().Add(time.Duration(expiresIn+delta) * time.Second)
9398
d.Alive = true
9499
s.mu.Unlock()
95100
}
96101

97102
func (s *DaemonService) validateDaemonConnection(d *model.Daemon) bool {
98-
_, err := s.c.Exists(context.Background(), *d, "0")
103+
_, err := s.c.Exists(context.Background(), d, "0")
99104
if err != nil {
100105
return false
101106
}
@@ -121,3 +126,28 @@ func (s *DaemonService) Watch() {
121126
time.Sleep(1 * time.Second)
122127
}
123128
}
129+
130+
func (s *DaemonService) List() []*model.Daemon {
131+
daemons := make([]*model.Daemon, len(s.daemons))
132+
133+
i := 0
134+
for _, daemon := range s.daemons {
135+
daemons[i] = daemon
136+
i++
137+
}
138+
139+
return daemons
140+
}
141+
142+
func (s *DaemonService) ById(id uuid.UUID) (*model.Daemon, error) {
143+
daemon, exists := s.daemons[id]
144+
145+
if exists {
146+
if !daemon.Alive {
147+
return nil, status.Error(codes.NotFound, "daemon not active")
148+
}
149+
return daemon, nil
150+
} else {
151+
return nil, status.Error(codes.NotFound, "daemon not found")
152+
}
153+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
/*
2+
* Copyright (c) 2022 Michaël COLL.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package service
18+
19+
import (
20+
"testing"
21+
22+
"github.com/google/uuid"
23+
"github.com/stretchr/testify/assert"
24+
25+
"github.com/michaelcoll/gallery-web/internal/photo/domain/model"
26+
)
27+
28+
func TestDaemonService_HeartBeat(t *testing.T) {
29+
// Given
30+
daemon := givenDaemon("testDaemon1", false)
31+
service := NewDaemonService(nil)
32+
service.daemons[daemon.Id] = daemon
33+
34+
// When
35+
_ = service.HeartBeat(daemon.Id)
36+
37+
// Then
38+
assert.Equal(t, true, daemon.Alive, "should be alive")
39+
40+
}
41+
42+
func TestDaemonService_List(t *testing.T) {
43+
// Given
44+
daemon0 := givenDaemon("testDaemon0", false)
45+
daemon1 := givenDaemon("testDaemon1", false)
46+
daemon2 := givenDaemon("testDaemon2", false)
47+
service := NewDaemonService(nil)
48+
service.daemons[daemon0.Id] = daemon0
49+
service.daemons[daemon1.Id] = daemon1
50+
service.daemons[daemon2.Id] = daemon2
51+
52+
// When
53+
list := service.List()
54+
55+
// Then
56+
assert.Contains(t, list, daemon0, "should contain testDaemon0")
57+
assert.Contains(t, list, daemon1, "should contain testDaemon1")
58+
assert.Contains(t, list, daemon2, "should contain testDaemon2")
59+
}
60+
61+
func TestDaemonService_ById(t *testing.T) {
62+
// Given
63+
daemon0 := givenDaemon("testDaemon0", true)
64+
daemon1 := givenDaemon("testDaemon1", false)
65+
service := NewDaemonService(nil)
66+
service.daemons[daemon0.Id] = daemon0
67+
service.daemons[daemon1.Id] = daemon1
68+
69+
// When
70+
daemon, err := service.ById(daemon0.Id)
71+
if err != nil {
72+
assert.Fail(t, "should get a daemon (%v)", err)
73+
}
74+
75+
// Then
76+
assert.Equal(t, daemon0, daemon, "should be testDaemon0")
77+
}
78+
79+
func TestDaemonService_ById_Inactive(t *testing.T) {
80+
// Given
81+
daemon0 := givenDaemon("testDaemon0", true)
82+
daemon1 := givenDaemon("testDaemon1", false)
83+
service := NewDaemonService(nil)
84+
service.daemons[daemon0.Id] = daemon0
85+
service.daemons[daemon1.Id] = daemon1
86+
87+
// When
88+
_, err := service.ById(daemon1.Id)
89+
90+
// Then
91+
assert.Errorf(t, err, "daemon not active")
92+
}
93+
94+
func givenDaemon(name string, alive bool) *model.Daemon {
95+
return &model.Daemon{
96+
Id: uuid.New(),
97+
Name: name,
98+
Alive: alive,
99+
}
100+
}

internal/photo/domain/service/photoService.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,14 @@ func NewPhotoService(c PhotoServiceCaller) PhotoService {
3030
return PhotoService{c: c}
3131
}
3232

33-
func (s *PhotoService) List(ctx context.Context) ([]*model.Photo, error) {
34-
return s.c.List(ctx, model.Daemon{Hostname: "localhost", Port: 9000})
33+
func (s *PhotoService) List(ctx context.Context, daemon *model.Daemon) ([]*model.Photo, error) {
34+
return s.c.List(ctx, daemon)
3535
}
3636

37-
func (s *PhotoService) GetByHash(ctx context.Context, hash string) (*model.Photo, error) {
38-
return s.c.GetByHash(ctx, model.Daemon{Hostname: "localhost", Port: 9000}, hash)
37+
func (s *PhotoService) GetByHash(ctx context.Context, daemon *model.Daemon, hash string) (*model.Photo, error) {
38+
return s.c.GetByHash(ctx, daemon, hash)
3939
}
4040

41-
func (s *PhotoService) ContentByHash(ctx context.Context, hash string) ([]byte, error) {
42-
return s.c.ContentByHash(ctx, model.Daemon{Hostname: "localhost", Port: 9000}, hash)
41+
func (s *PhotoService) ContentByHash(ctx context.Context, daemon *model.Daemon, hash string) ([]byte, string, error) {
42+
return s.c.ContentByHash(ctx, daemon, hash)
4343
}

internal/photo/domain/service/photoServiceCaller.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import (
2323
)
2424

2525
type PhotoServiceCaller interface {
26-
List(ctx context.Context, d model.Daemon) ([]*model.Photo, error)
27-
GetByHash(ctx context.Context, d model.Daemon, hash string) (*model.Photo, error)
28-
Exists(ctx context.Context, d model.Daemon, hash string) (bool, error)
29-
ContentByHash(ctx context.Context, d model.Daemon, hash string) ([]byte, error)
26+
List(ctx context.Context, d *model.Daemon) ([]*model.Photo, error)
27+
GetByHash(ctx context.Context, d *model.Daemon, hash string) (*model.Photo, error)
28+
Exists(ctx context.Context, d *model.Daemon, hash string) (bool, error)
29+
ContentByHash(ctx context.Context, d *model.Daemon, hash string) ([]byte, string, error)
3030
}

0 commit comments

Comments
 (0)