From 46325967fe790358aa3b90abc29c33af43b8c900 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:02:13 +0900 Subject: [PATCH 1/7] refactor(image): add repository update operation --- internal/app/image/memory_repository.go | 13 ++++++ internal/app/image/repository.go | 1 + internal/app/image/sqlite_repository.go | 48 ++++++++++++++++++++ internal/app/image/sqlite_repository_test.go | 7 ++- 4 files changed, 67 insertions(+), 2 deletions(-) diff --git a/internal/app/image/memory_repository.go b/internal/app/image/memory_repository.go index c4fb3eb..8b639e8 100644 --- a/internal/app/image/memory_repository.go +++ b/internal/app/image/memory_repository.go @@ -49,6 +49,19 @@ func (r *MemoryRepository) Get(id string) (Image, error) { return image, nil } +func (r *MemoryRepository) Update(image Image) (Image, error) { + r.mu.Lock() + defer r.mu.Unlock() + + if _, ok := r.images[image.ID]; !ok { + return Image{}, ErrImageNotFound + } + + r.images[image.ID] = image + + return image, nil +} + func (r *MemoryRepository) Delete(id string) error { r.mu.Lock() defer r.mu.Unlock() diff --git a/internal/app/image/repository.go b/internal/app/image/repository.go index ac4bb3b..f4a8324 100644 --- a/internal/app/image/repository.go +++ b/internal/app/image/repository.go @@ -4,6 +4,7 @@ type Repository interface { Create(image Image) Image List() []Image Get(id string) (Image, error) + Update(image Image) (Image, error) Delete(id string) error Reset() } diff --git a/internal/app/image/sqlite_repository.go b/internal/app/image/sqlite_repository.go index 5d60328..d74dce5 100644 --- a/internal/app/image/sqlite_repository.go +++ b/internal/app/image/sqlite_repository.go @@ -160,6 +160,54 @@ WHERE id = ?`, id) return image, nil } +func (r *SQLiteRepository) Update(image Image) (Image, error) { + tagsJSON, err := json.Marshal(image.Tags) + if err != nil { + return Image{}, fmt.Errorf("marshal image tags: %w", err) + } + + result, err := r.db.Exec( + `UPDATE images + SET name = ?, + status = ?, + container_format = ?, + disk_format = ?, + min_disk = ?, + min_ram = ?, + protected = ?, + visibility = ?, + tags_json = ?, + created_at = ?, + updated_at = ? + WHERE id = ?`, + image.Name, + image.Status, + image.ContainerFormat, + image.DiskFormat, + image.MinDisk, + image.MinRAM, + boolToInt(image.Protected), + image.Visibility, + string(tagsJSON), + image.CreatedAt, + image.UpdatedAt, + image.ID, + ) + if err != nil { + return Image{}, err + } + + rowsAffected, err := result.RowsAffected() + if err != nil { + return Image{}, err + } + if rowsAffected == 0 { + return Image{}, ErrImageNotFound + } + + return image, nil +} + func (r *SQLiteRepository) Delete(id string) error { result, err := r.db.Exec(`DELETE FROM images WHERE id = ?`, id) if err != nil { diff --git a/internal/app/image/sqlite_repository_test.go b/internal/app/image/sqlite_repository_test.go index 0542a3b..f63538a 100644 --- a/internal/app/image/sqlite_repository_test.go +++ b/internal/app/image/sqlite_repository_test.go @@ -43,14 +43,17 @@ func (s *SQLiteRepositorySuite) TestCreateListAndGetImage() { CreatedAt: "2026-07-03T00:00:00Z", UpdatedAt: "2026-07-03T00:00:00Z", }) + created.Name = "ubuntu-updated" + updated, err := s.repository.Update(created) + s.Require().NoError(err) listed := s.repository.List() found, err := s.repository.Get(created.ID) s.Require().NoError(err) s.Assert().Len(listed, 1) - s.Assert().Equal(created, listed[0]) - s.Assert().Equal(created, found) + s.Assert().Equal(updated, listed[0]) + s.Assert().Equal(updated, found) } func (s *SQLiteRepositorySuite) TestDeleteImage() { From c78aba93783031c5be8ba508c2b1e76cd31bc66d Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:04:17 +0900 Subject: [PATCH 2/7] feat(image): add image update endpoint --- internal/api/image/image.go | 26 +++++++++++ internal/api/image/image_test.go | 22 +++++++++ internal/api/image/router.go | 1 + internal/api/image/update_dto.go | 78 ++++++++++++++++++++++++++++++++ internal/app/image/update.go | 39 ++++++++++++++++ 5 files changed, 166 insertions(+) create mode 100644 internal/api/image/update_dto.go create mode 100644 internal/app/image/update.go diff --git a/internal/api/image/image.go b/internal/api/image/image.go index 364f23b..e9abce6 100644 --- a/internal/api/image/image.go +++ b/internal/api/image/image.go @@ -41,6 +41,32 @@ func (h Handler) getImage(w http.ResponseWriter, r *http.Request) { respond.JSON(w, http.StatusOK, toImageDocument(image)) } +func (h Handler) updateImage(w http.ResponseWriter, r *http.Request) { + var operations []imagePatchOperation + if err := json.NewDecoder(r.Body).Decode(&operations); err != nil { + respond.Error(w, http.StatusBadRequest, "invalid JSON request body") + return + } + + patch, err := toPatchImage(operations) + if err != nil { + respond.Error(w, http.StatusBadRequest, "invalid image patch") + return + } + + image, err := h.service.Update(chi.URLParam(r, "image_id"), patch) + if errors.Is(err, appimage.ErrImageNotFound) { + respond.Error(w, http.StatusNotFound, "image not found") + return + } + if err != nil { + respond.Error(w, http.StatusInternalServerError, "image update failed") + return + } + + respond.JSON(w, http.StatusOK, toImageDocument(image)) +} + func (h Handler) deleteImage(w http.ResponseWriter, r *http.Request) { err := h.service.Delete(chi.URLParam(r, "image_id")) if errors.Is(err, appimage.ErrImageNotFound) { diff --git a/internal/api/image/image_test.go b/internal/api/image/image_test.go index b6b5ed5..f8bf184 100644 --- a/internal/api/image/image_test.go +++ b/internal/api/image/image_test.go @@ -63,6 +63,28 @@ func (s *ImageSuite) TestGetImage() { s.Assert().Equal("ubuntu", found.Name) } +func (s *ImageSuite) TestUpdateImage() { + created := s.createImage("ubuntu") + + updated, err := images.Update( + s.T().Context(), + testhelper.ServiceClient(s.server.URL), + created.ID, + images.UpdateOpts{ + images.ReplaceImageName{NewName: "ubuntu-updated"}, + images.ReplaceImageMinDisk{NewMinDisk: 2}, + images.ReplaceImageTags{NewTags: []string{"linux", "test"}}, + }, + ).Extract() + s.Require().NoError(err) + s.Require().NotNil(updated) + + s.Assert().Equal(created.ID, updated.ID) + s.Assert().Equal("ubuntu-updated", updated.Name) + s.Assert().Equal(2, updated.MinDiskGigabytes) + s.Assert().Equal([]string{"linux", "test"}, updated.Tags) +} + func (s *ImageSuite) TestDeleteImage() { created := s.createImage("ubuntu") diff --git a/internal/api/image/router.go b/internal/api/image/router.go index 83c3220..1295269 100644 --- a/internal/api/image/router.go +++ b/internal/api/image/router.go @@ -44,6 +44,7 @@ func (h Handler) Router() http.Handler { router.Get("/images", h.listImages) router.Post("/images", h.createImage) router.Get("/images/{image_id}", h.getImage) + router.Patch("/images/{image_id}", h.updateImage) router.Delete("/images/{image_id}", h.deleteImage) return router diff --git a/internal/api/image/update_dto.go b/internal/api/image/update_dto.go new file mode 100644 index 0000000..7dc1dfc --- /dev/null +++ b/internal/api/image/update_dto.go @@ -0,0 +1,78 @@ +package image + +import ( + "encoding/json" + "fmt" + + appimage "github.com/JSYoo5B/SandStack/internal/app/image" +) + +type imagePatchOperation struct { + Op string `json:"op"` + Path string `json:"path"` + Value json.RawMessage `json:"value"` +} + +func toPatchImage(operations []imagePatchOperation) (appimage.PatchImage, error) { + var patch appimage.PatchImage + for _, operation := range operations { + if operation.Op != "replace" { + return appimage.PatchImage{}, fmt.Errorf("unsupported patch operation") + } + + if err := applyPatchOperation(&patch, operation); err != nil { + return appimage.PatchImage{}, err + } + } + + return patch, nil +} + +func applyPatchOperation( + patch *appimage.PatchImage, + operation imagePatchOperation, +) error { + switch operation.Path { + case "/name": + var value string + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.Name = &value + case "/min_disk": + var value int + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.MinDisk = &value + case "/min_ram": + var value int + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.MinRAM = &value + case "/protected": + var value bool + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.Protected = &value + case "/visibility": + var value string + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.Visibility = &value + case "/tags": + var value []string + if err := json.Unmarshal(operation.Value, &value); err != nil { + return err + } + patch.Tags = value + patch.HasTags = true + default: + return fmt.Errorf("unsupported patch path") + } + + return nil +} diff --git a/internal/app/image/update.go b/internal/app/image/update.go new file mode 100644 index 0000000..2cad276 --- /dev/null +++ b/internal/app/image/update.go @@ -0,0 +1,39 @@ +package image + +type PatchImage struct { + Name *string + MinDisk *int + MinRAM *int + Protected *bool + Visibility *string + Tags []string + HasTags bool +} + +func (s *Service) Update(id string, patch PatchImage) (Image, error) { + image, err := s.repository.Get(id) + if err != nil { + return Image{}, err + } + + if patch.Name != nil { + image.Name = *patch.Name + } + if patch.MinDisk != nil { + image.MinDisk = *patch.MinDisk + } + if patch.MinRAM != nil { + image.MinRAM = *patch.MinRAM + } + if patch.Protected != nil { + image.Protected = *patch.Protected + } + if patch.Visibility != nil { + image.Visibility = *patch.Visibility + } + if patch.HasTags { + image.Tags = patch.Tags + } + + return s.repository.Update(image) +} From 65d0b96df274cac8cba757d4dd50bb19472fcff2 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:05:48 +0900 Subject: [PATCH 3/7] feat(volume): add volume update endpoint --- internal/api/volume/router.go | 1 + internal/api/volume/volume.go | 25 +++++++++++++++++++++++++ internal/api/volume/volume_dto.go | 16 ++++++++++++++++ internal/api/volume/volume_test.go | 26 ++++++++++++++++++++++++++ internal/app/volume/update.go | 26 ++++++++++++++++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 internal/app/volume/update.go diff --git a/internal/api/volume/router.go b/internal/api/volume/router.go index 8e37ed5..b534c08 100644 --- a/internal/api/volume/router.go +++ b/internal/api/volume/router.go @@ -45,6 +45,7 @@ func (h Handler) Router() http.Handler { router.Get("/{project_id}/volumes/detail", h.listVolumes) router.Post("/{project_id}/volumes", h.createVolume) router.Get("/{project_id}/volumes/{volume_id}", h.getVolume) + router.Put("/{project_id}/volumes/{volume_id}", h.updateVolume) router.Delete("/{project_id}/volumes/{volume_id}", h.deleteVolume) router.Get("/{project_id}/types", h.listVolumeTypes) router.Get("/{project_id}/types/{type_id}", h.getVolumeType) diff --git a/internal/api/volume/volume.go b/internal/api/volume/volume.go index 450fc31..fe52647 100644 --- a/internal/api/volume/volume.go +++ b/internal/api/volume/volume.go @@ -45,6 +45,31 @@ func (h Handler) getVolume(w http.ResponseWriter, r *http.Request) { }) } +func (h Handler) updateVolume(w http.ResponseWriter, r *http.Request) { + var request updateVolumeRequest + if err := json.NewDecoder(r.Body).Decode(&request); err != nil { + respond.Error(w, http.StatusBadRequest, "invalid JSON request body") + return + } + + volume, err := h.service.Update( + chi.URLParam(r, "volume_id"), + request.updateVolume(), + ) + if errors.Is(err, appvolume.ErrVolumeNotFound) { + respond.Error(w, http.StatusNotFound, "volume not found") + return + } + if err != nil { + respond.Error(w, http.StatusInternalServerError, "volume update failed") + return + } + + respond.JSON(w, http.StatusOK, volumeResponse{ + Volume: toVolumeDocument(volume), + }) +} + func (h Handler) deleteVolume(w http.ResponseWriter, r *http.Request) { err := h.service.Delete(chi.URLParam(r, "volume_id")) if errors.Is(err, appvolume.ErrVolumeNotFound) { diff --git a/internal/api/volume/volume_dto.go b/internal/api/volume/volume_dto.go index 4e5a0dc..44ecf62 100644 --- a/internal/api/volume/volume_dto.go +++ b/internal/api/volume/volume_dto.go @@ -12,6 +12,14 @@ type createVolumeRequest struct { } `json:"volume"` } +type updateVolumeRequest struct { + Volume struct { + Name *string `json:"name"` + Description *string `json:"description"` + Metadata map[string]string `json:"metadata"` + } `json:"volume"` +} + func (r createVolumeRequest) createVolume() appvolume.CreateVolume { return appvolume.CreateVolume{ Size: r.Volume.Size, @@ -22,6 +30,14 @@ func (r createVolumeRequest) createVolume() appvolume.CreateVolume { } } +func (r updateVolumeRequest) updateVolume() appvolume.UpdateVolume { + return appvolume.UpdateVolume{ + Name: r.Volume.Name, + Description: r.Volume.Description, + Metadata: r.Volume.Metadata, + } +} + type volumeListResponse struct { Volumes []volumeDocument `json:"volumes"` } diff --git a/internal/api/volume/volume_test.go b/internal/api/volume/volume_test.go index de13521..12ccc3f 100644 --- a/internal/api/volume/volume_test.go +++ b/internal/api/volume/volume_test.go @@ -64,6 +64,32 @@ func (s *VolumeSuite) TestGetVolume() { s.Assert().Equal("database", found.Name) } +func (s *VolumeSuite) TestUpdateVolume() { + created := s.createVolume("database") + name := "database-updated" + description := "updated description" + + updated, err := volumes.Update( + s.T().Context(), + testhelper.ServiceClient(s.server.URL+"/demo"), + created.ID, + volumes.UpdateOpts{ + Name: &name, + Description: &description, + Metadata: map[string]string{ + "role": "database", + }, + }, + ).Extract() + s.Require().NoError(err) + s.Require().NotNil(updated) + + s.Assert().Equal(created.ID, updated.ID) + s.Assert().Equal("database-updated", updated.Name) + s.Assert().Equal("updated description", updated.Description) + s.Assert().Equal(map[string]string{"role": "database"}, updated.Metadata) +} + func (s *VolumeSuite) TestDeleteVolume() { created := s.createVolume("database") diff --git a/internal/app/volume/update.go b/internal/app/volume/update.go new file mode 100644 index 0000000..c7bc114 --- /dev/null +++ b/internal/app/volume/update.go @@ -0,0 +1,26 @@ +package volume + +type UpdateVolume struct { + Name *string + Description *string + Metadata map[string]string +} + +func (s *Service) Update(id string, input UpdateVolume) (Volume, error) { + volume, err := s.repository.Get(id) + if err != nil { + return Volume{}, err + } + + if input.Name != nil { + volume.Name = *input.Name + } + if input.Description != nil { + volume.Description = *input.Description + } + if input.Metadata != nil { + volume.Metadata = input.Metadata + } + + return s.repository.Update(volume) +} From 4908429846e40724bf1a89d6b798834d26909604 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:11:44 +0900 Subject: [PATCH 4/7] refactor(image): move repositories to store layer --- internal/api/image/router.go | 12 ++++++- internal/api/router.go | 9 ++++- internal/app/image/service.go | 15 --------- internal/app/image/service_test.go | 9 ++++- .../{app => store}/image/memory_repository.go | 28 +++++++++------- .../{app => store}/image/sqlite_repository.go | 33 ++++++++++--------- .../image/sqlite_repository_test.go | 23 ++++++------- 7 files changed, 72 insertions(+), 57 deletions(-) rename internal/{app => store}/image/memory_repository.go (61%) rename internal/{app => store}/image/sqlite_repository.go (83%) rename internal/{app => store}/image/sqlite_repository_test.go (80%) diff --git a/internal/api/image/router.go b/internal/api/image/router.go index 1295269..9d9e1f3 100644 --- a/internal/api/image/router.go +++ b/internal/api/image/router.go @@ -4,7 +4,10 @@ import ( "net/http" appimage "github.com/JSYoo5B/SandStack/internal/app/image" + "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/config" + "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storeimage "github.com/JSYoo5B/SandStack/internal/store/image" "github.com/go-chi/chi/v5" ) @@ -25,7 +28,14 @@ func NewRouterWithService( } func NewHandler(cfg config.Config) Handler { - return NewHandlerWithService(cfg, appimage.NewService()) + return NewHandlerWithService( + cfg, + appimage.NewServiceWithRuntime( + storeimage.NewMemoryRepository(), + clock.Wall(), + idgen.Random(), + ), + ) } func NewHandlerWithService( diff --git a/internal/api/router.go b/internal/api/router.go index 2426962..d399902 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -15,7 +15,10 @@ import ( appnetwork "github.com/JSYoo5B/SandStack/internal/app/network" "github.com/JSYoo5B/SandStack/internal/app/requestlog" appvolume "github.com/JSYoo5B/SandStack/internal/app/volume" + "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/config" + "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storeimage "github.com/JSYoo5B/SandStack/internal/store/image" "github.com/go-chi/chi/v5" ) @@ -26,7 +29,11 @@ func NewRouter(cfg config.Config) http.Handler { router.Use(recordRequests(requests)) identityHandler := identity.NewHandler(cfg) computeService := appcompute.NewService() - imageService := appimage.NewService() + imageService := appimage.NewServiceWithRuntime( + storeimage.NewMemoryRepository(), + clock.Wall(), + idgen.Random(), + ) networkService := appnetwork.NewService() volumeService := appvolume.NewService() diff --git a/internal/app/image/service.go b/internal/app/image/service.go index 35f107a..91b4815 100644 --- a/internal/app/image/service.go +++ b/internal/app/image/service.go @@ -16,22 +16,7 @@ type Service struct { idGen idgen.Generator } -func NewService() *Service { - return NewServiceWithRuntime(clock.Wall(), idgen.Random()) -} - -func NewServiceWithClock(clock clock.Clock) *Service { - return NewServiceWithRuntime(clock, idgen.Random()) -} - func NewServiceWithRuntime( - clock clock.Clock, - idGen idgen.Generator, -) *Service { - return NewServiceWithRepository(NewMemoryRepository(), clock, idGen) -} - -func NewServiceWithRepository( repository Repository, clock clock.Clock, idGen idgen.Generator, diff --git a/internal/app/image/service_test.go b/internal/app/image/service_test.go index 0441b60..911546f 100644 --- a/internal/app/image/service_test.go +++ b/internal/app/image/service_test.go @@ -7,6 +7,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/app/image" "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storeimage "github.com/JSYoo5B/SandStack/internal/store/image" "github.com/stretchr/testify/suite" ) @@ -20,7 +21,11 @@ func TestServiceSuite(t *testing.T) { func (s *ServiceSuite) TestCreateImageUsesInjectedClock() { now := time.Date(2026, 6, 23, 8, 30, 0, 0, time.UTC) - service := image.NewServiceWithClock(clock.Fixed(now)) + service := image.NewServiceWithRuntime( + storeimage.NewMemoryRepository(), + clock.Fixed(now), + idgen.Random(), + ) created := service.Create(image.CreateImage{ Name: "ubuntu", @@ -34,6 +39,7 @@ func (s *ServiceSuite) TestCreateImageUsesInjectedClock() { func (s *ServiceSuite) TestCreateImageUsesInjectedIDGenerator() { service := image.NewServiceWithRuntime( + storeimage.NewMemoryRepository(), clock.Fixed(time.Time{}), idgen.Fixed("image-id"), ) @@ -49,6 +55,7 @@ func (s *ServiceSuite) TestCreateImageUsesInjectedIDGenerator() { func (s *ServiceSuite) TestResetClearsImages() { service := image.NewServiceWithRuntime( + storeimage.NewMemoryRepository(), clock.Fixed(time.Time{}), idgen.Fixed("image-id"), ) diff --git a/internal/app/image/memory_repository.go b/internal/store/image/memory_repository.go similarity index 61% rename from internal/app/image/memory_repository.go rename to internal/store/image/memory_repository.go index 8b639e8..37b0cf8 100644 --- a/internal/app/image/memory_repository.go +++ b/internal/store/image/memory_repository.go @@ -1,21 +1,25 @@ package image -import "sync" +import ( + "sync" + + appimage "github.com/JSYoo5B/SandStack/internal/app/image" +) type MemoryRepository struct { mu sync.RWMutex ids []string - images map[string]Image + images map[string]appimage.Image } func NewMemoryRepository() *MemoryRepository { return &MemoryRepository{ ids: []string{}, - images: map[string]Image{}, + images: map[string]appimage.Image{}, } } -func (r *MemoryRepository) Create(image Image) Image { +func (r *MemoryRepository) Create(image appimage.Image) appimage.Image { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemoryRepository) Create(image Image) Image { return image } -func (r *MemoryRepository) List() []Image { +func (r *MemoryRepository) List() []appimage.Image { r.mu.RLock() defer r.mu.RUnlock() - images := make([]Image, 0, len(r.ids)) + images := make([]appimage.Image, 0, len(r.ids)) for _, id := range r.ids { images = append(images, r.images[id]) } @@ -37,24 +41,24 @@ func (r *MemoryRepository) List() []Image { return images } -func (r *MemoryRepository) Get(id string) (Image, error) { +func (r *MemoryRepository) Get(id string) (appimage.Image, error) { r.mu.RLock() defer r.mu.RUnlock() image, ok := r.images[id] if !ok { - return Image{}, ErrImageNotFound + return appimage.Image{}, appimage.ErrImageNotFound } return image, nil } -func (r *MemoryRepository) Update(image Image) (Image, error) { +func (r *MemoryRepository) Update(image appimage.Image) (appimage.Image, error) { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.images[image.ID]; !ok { - return Image{}, ErrImageNotFound + return appimage.Image{}, appimage.ErrImageNotFound } r.images[image.ID] = image @@ -67,7 +71,7 @@ func (r *MemoryRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.images[id]; !ok { - return ErrImageNotFound + return appimage.ErrImageNotFound } delete(r.images, id) @@ -86,5 +90,5 @@ func (r *MemoryRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.images = map[string]Image{} + r.images = map[string]appimage.Image{} } diff --git a/internal/app/image/sqlite_repository.go b/internal/store/image/sqlite_repository.go similarity index 83% rename from internal/app/image/sqlite_repository.go rename to internal/store/image/sqlite_repository.go index d74dce5..af99498 100644 --- a/internal/app/image/sqlite_repository.go +++ b/internal/store/image/sqlite_repository.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + appimage "github.com/JSYoo5B/SandStack/internal/app/image" _ "modernc.org/sqlite" ) @@ -53,7 +54,7 @@ func (r *SQLiteRepository) Close() error { return r.db.Close() } -func (r *SQLiteRepository) Create(image Image) Image { +func (r *SQLiteRepository) Create(image appimage.Image) appimage.Image { tagsJSON, err := json.Marshal(image.Tags) if err != nil { panic(fmt.Errorf("marshal image tags: %w", err)) @@ -94,7 +95,7 @@ func (r *SQLiteRepository) Create(image Image) Image { return image } -func (r *SQLiteRepository) List() []Image { +func (r *SQLiteRepository) List() []appimage.Image { rows, err := r.db.Query(` SELECT id, @@ -116,7 +117,7 @@ ORDER BY sequence`) } defer rows.Close() - images := []Image{} + images := []appimage.Image{} for rows.Next() { image, err := scanImage(rows) if err != nil { @@ -131,7 +132,7 @@ ORDER BY sequence`) return images } -func (r *SQLiteRepository) Get(id string) (Image, error) { +func (r *SQLiteRepository) Get(id string) (appimage.Image, error) { row := r.db.QueryRow(` SELECT id, @@ -151,19 +152,19 @@ WHERE id = ?`, id) image, err := scanImage(row) if errors.Is(err, sql.ErrNoRows) { - return Image{}, ErrImageNotFound + return appimage.Image{}, appimage.ErrImageNotFound } if err != nil { - return Image{}, err + return appimage.Image{}, err } return image, nil } -func (r *SQLiteRepository) Update(image Image) (Image, error) { +func (r *SQLiteRepository) Update(image appimage.Image) (appimage.Image, error) { tagsJSON, err := json.Marshal(image.Tags) if err != nil { - return Image{}, fmt.Errorf("marshal image tags: %w", err) + return appimage.Image{}, fmt.Errorf("marshal image tags: %w", err) } result, err := r.db.Exec( @@ -194,15 +195,15 @@ func (r *SQLiteRepository) Update(image Image) (Image, error) { image.ID, ) if err != nil { - return Image{}, err + return appimage.Image{}, err } rowsAffected, err := result.RowsAffected() if err != nil { - return Image{}, err + return appimage.Image{}, err } if rowsAffected == 0 { - return Image{}, ErrImageNotFound + return appimage.Image{}, appimage.ErrImageNotFound } return image, nil @@ -219,7 +220,7 @@ func (r *SQLiteRepository) Delete(id string) error { return err } if rowsAffected == 0 { - return ErrImageNotFound + return appimage.ErrImageNotFound } return nil @@ -235,8 +236,8 @@ type imageScanner interface { Scan(dest ...any) error } -func scanImage(scanner imageScanner) (Image, error) { - var image Image +func scanImage(scanner imageScanner) (appimage.Image, error) { + var image appimage.Image var protected int var tagsJSON string @@ -254,11 +255,11 @@ func scanImage(scanner imageScanner) (Image, error) { &image.CreatedAt, &image.UpdatedAt, ); err != nil { - return Image{}, err + return appimage.Image{}, err } if err := json.Unmarshal([]byte(tagsJSON), &image.Tags); err != nil { - return Image{}, err + return appimage.Image{}, err } image.Protected = protected != 0 diff --git a/internal/app/image/sqlite_repository_test.go b/internal/store/image/sqlite_repository_test.go similarity index 80% rename from internal/app/image/sqlite_repository_test.go rename to internal/store/image/sqlite_repository_test.go index f63538a..b203a07 100644 --- a/internal/app/image/sqlite_repository_test.go +++ b/internal/store/image/sqlite_repository_test.go @@ -4,13 +4,14 @@ import ( "path/filepath" "testing" - "github.com/JSYoo5B/SandStack/internal/app/image" + appimage "github.com/JSYoo5B/SandStack/internal/app/image" + storeimage "github.com/JSYoo5B/SandStack/internal/store/image" "github.com/stretchr/testify/suite" ) type SQLiteRepositorySuite struct { suite.Suite - repository *image.SQLiteRepository + repository *storeimage.SQLiteRepository } func TestSQLiteRepositorySuite(t *testing.T) { @@ -18,7 +19,7 @@ func TestSQLiteRepositorySuite(t *testing.T) { } func (s *SQLiteRepositorySuite) SetupTest() { - repository, err := image.OpenSQLiteRepository(":memory:") + repository, err := storeimage.OpenSQLiteRepository(":memory:") s.Require().NoError(err) s.repository = repository @@ -28,8 +29,8 @@ func (s *SQLiteRepositorySuite) TearDownTest() { s.Require().NoError(s.repository.Close()) } -func (s *SQLiteRepositorySuite) TestCreateListAndGetImage() { - created := s.repository.Create(image.Image{ +func (s *SQLiteRepositorySuite) TestCreateListGetAndUpdateImage() { + created := s.repository.Create(appimage.Image{ ID: "img-1", Name: "ubuntu", Status: "queued", @@ -57,7 +58,7 @@ func (s *SQLiteRepositorySuite) TestCreateListAndGetImage() { } func (s *SQLiteRepositorySuite) TestDeleteImage() { - created := s.repository.Create(image.Image{ + created := s.repository.Create(appimage.Image{ ID: "img-1", Name: "ubuntu", Status: "queued", @@ -73,12 +74,12 @@ func (s *SQLiteRepositorySuite) TestDeleteImage() { s.Require().NoError(err) _, err = s.repository.Get(created.ID) - s.Require().ErrorIs(err, image.ErrImageNotFound) + s.Require().ErrorIs(err, appimage.ErrImageNotFound) s.Assert().Empty(s.repository.List()) } func (s *SQLiteRepositorySuite) TestResetClearsImages() { - s.repository.Create(image.Image{ + s.repository.Create(appimage.Image{ ID: "img-1", Name: "ubuntu", Status: "queued", @@ -97,10 +98,10 @@ func (s *SQLiteRepositorySuite) TestResetClearsImages() { func (s *SQLiteRepositorySuite) TestFileBackedDatabasePersistsImages() { path := filepath.Join(s.T().TempDir(), "sandstack.db") - repository, err := image.OpenSQLiteRepository(path) + repository, err := storeimage.OpenSQLiteRepository(path) s.Require().NoError(err) - created := repository.Create(image.Image{ + created := repository.Create(appimage.Image{ ID: "img-1", Name: "ubuntu", Status: "queued", @@ -113,7 +114,7 @@ func (s *SQLiteRepositorySuite) TestFileBackedDatabasePersistsImages() { }) s.Require().NoError(repository.Close()) - reopened, err := image.OpenSQLiteRepository(path) + reopened, err := storeimage.OpenSQLiteRepository(path) s.Require().NoError(err) defer reopened.Close() From 3b4776a9c1869af60a5f09905c0042c0a1529045 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:14:22 +0900 Subject: [PATCH 5/7] refactor(compute): move repositories to store layer --- internal/api/compute/router.go | 12 ++++++- internal/api/router.go | 7 ++++- internal/app/compute/server_test.go | 10 +++++- internal/app/compute/service.go | 19 ------------ .../compute/memory_server_repository.go | 30 +++++++++++------- .../compute/sqlite_server_repository.go | 31 ++++++++++--------- .../compute/sqlite_server_repository_test.go | 17 +++++----- 7 files changed, 69 insertions(+), 57 deletions(-) rename internal/{app => store}/compute/memory_server_repository.go (59%) rename internal/{app => store}/compute/sqlite_server_repository.go (83%) rename internal/{app => store}/compute/sqlite_server_repository_test.go (80%) diff --git a/internal/api/compute/router.go b/internal/api/compute/router.go index d2171e7..59834b2 100644 --- a/internal/api/compute/router.go +++ b/internal/api/compute/router.go @@ -4,7 +4,10 @@ import ( "net/http" appcompute "github.com/JSYoo5B/SandStack/internal/app/compute" + "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/config" + "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" "github.com/go-chi/chi/v5" ) @@ -25,7 +28,14 @@ func NewRouterWithService( } func NewHandler(cfg config.Config) Handler { - return NewHandlerWithService(cfg, appcompute.NewService()) + return NewHandlerWithService( + cfg, + appcompute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), + clock.Wall(), + idgen.Random(), + ), + ) } func NewHandlerWithService( diff --git a/internal/api/router.go b/internal/api/router.go index d399902..7754c93 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -18,6 +18,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/config" "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" storeimage "github.com/JSYoo5B/SandStack/internal/store/image" "github.com/go-chi/chi/v5" ) @@ -28,7 +29,11 @@ func NewRouter(cfg config.Config) http.Handler { requests := requestlog.NewService() router.Use(recordRequests(requests)) identityHandler := identity.NewHandler(cfg) - computeService := appcompute.NewService() + computeService := appcompute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), + clock.Wall(), + idgen.Random(), + ) imageService := appimage.NewServiceWithRuntime( storeimage.NewMemoryRepository(), clock.Wall(), diff --git a/internal/app/compute/server_test.go b/internal/app/compute/server_test.go index 7ce4ec0..449f25b 100644 --- a/internal/app/compute/server_test.go +++ b/internal/app/compute/server_test.go @@ -7,6 +7,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/app/compute" "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" "github.com/stretchr/testify/suite" ) @@ -20,7 +21,11 @@ func TestServerSuite(t *testing.T) { func (s *ServerSuite) TestCreateServerUsesInjectedClock() { now := time.Date(2026, 6, 23, 8, 30, 0, 0, time.UTC) - service := compute.NewServiceWithClock(clock.Fixed(now)) + service := compute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), + clock.Fixed(now), + idgen.Random(), + ) server := service.CreateServer(compute.CreateServer{ Name: "web", @@ -34,6 +39,7 @@ func (s *ServerSuite) TestCreateServerUsesInjectedClock() { func (s *ServerSuite) TestCreateServerUsesInjectedIDGenerator() { service := compute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), clock.Fixed(time.Time{}), idgen.Fixed("server-id"), ) @@ -50,6 +56,7 @@ func (s *ServerSuite) TestCreateServerUsesInjectedIDGenerator() { func (s *ServerSuite) TestGetServerActivatesCreatedServer() { now := time.Date(2026, 6, 23, 8, 30, 0, 0, time.UTC) service := compute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), clock.Fixed(now), idgen.Fixed("server-id"), ) @@ -70,6 +77,7 @@ func (s *ServerSuite) TestGetServerActivatesCreatedServer() { func (s *ServerSuite) TestResetClearsServers() { service := compute.NewServiceWithRuntime( + storecompute.NewMemoryServerRepository(), clock.Fixed(time.Time{}), idgen.Fixed("server-id"), ) diff --git a/internal/app/compute/service.go b/internal/app/compute/service.go index bcf8463..fbf2c18 100644 --- a/internal/app/compute/service.go +++ b/internal/app/compute/service.go @@ -12,26 +12,7 @@ type Service struct { idGen idgen.Generator } -func NewService() *Service { - return NewServiceWithRuntime(clock.Wall(), idgen.Random()) -} - -func NewServiceWithClock(clock clock.Clock) *Service { - return NewServiceWithRuntime(clock, idgen.Random()) -} - func NewServiceWithRuntime( - clock clock.Clock, - idGen idgen.Generator, -) *Service { - return NewServiceWithRepository( - NewMemoryServerRepository(), - clock, - idGen, - ) -} - -func NewServiceWithRepository( serverRepository ServerRepository, clock clock.Clock, idGen idgen.Generator, diff --git a/internal/app/compute/memory_server_repository.go b/internal/store/compute/memory_server_repository.go similarity index 59% rename from internal/app/compute/memory_server_repository.go rename to internal/store/compute/memory_server_repository.go index 96789bc..1fd77a9 100644 --- a/internal/app/compute/memory_server_repository.go +++ b/internal/store/compute/memory_server_repository.go @@ -1,21 +1,25 @@ package compute -import "sync" +import ( + "sync" + + appcompute "github.com/JSYoo5B/SandStack/internal/app/compute" +) type MemoryServerRepository struct { mu sync.RWMutex ids []string - servers map[string]Server + servers map[string]appcompute.Server } func NewMemoryServerRepository() *MemoryServerRepository { return &MemoryServerRepository{ ids: []string{}, - servers: map[string]Server{}, + servers: map[string]appcompute.Server{}, } } -func (r *MemoryServerRepository) Create(server Server) Server { +func (r *MemoryServerRepository) Create(server appcompute.Server) appcompute.Server { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemoryServerRepository) Create(server Server) Server { return server } -func (r *MemoryServerRepository) List() []Server { +func (r *MemoryServerRepository) List() []appcompute.Server { r.mu.RLock() defer r.mu.RUnlock() - servers := make([]Server, 0, len(r.ids)) + servers := make([]appcompute.Server, 0, len(r.ids)) for _, id := range r.ids { servers = append(servers, r.servers[id]) } @@ -37,24 +41,26 @@ func (r *MemoryServerRepository) List() []Server { return servers } -func (r *MemoryServerRepository) Get(id string) (Server, error) { +func (r *MemoryServerRepository) Get(id string) (appcompute.Server, error) { r.mu.RLock() defer r.mu.RUnlock() server, ok := r.servers[id] if !ok { - return Server{}, ErrServerNotFound + return appcompute.Server{}, appcompute.ErrServerNotFound } return server, nil } -func (r *MemoryServerRepository) Update(server Server) (Server, error) { +func (r *MemoryServerRepository) Update( + server appcompute.Server, +) (appcompute.Server, error) { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.servers[server.ID]; !ok { - return Server{}, ErrServerNotFound + return appcompute.Server{}, appcompute.ErrServerNotFound } r.servers[server.ID] = server @@ -67,7 +73,7 @@ func (r *MemoryServerRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.servers[id]; !ok { - return ErrServerNotFound + return appcompute.ErrServerNotFound } delete(r.servers, id) @@ -86,5 +92,5 @@ func (r *MemoryServerRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.servers = map[string]Server{} + r.servers = map[string]appcompute.Server{} } diff --git a/internal/app/compute/sqlite_server_repository.go b/internal/store/compute/sqlite_server_repository.go similarity index 83% rename from internal/app/compute/sqlite_server_repository.go rename to internal/store/compute/sqlite_server_repository.go index 70cc823..f218a40 100644 --- a/internal/app/compute/sqlite_server_repository.go +++ b/internal/store/compute/sqlite_server_repository.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + appcompute "github.com/JSYoo5B/SandStack/internal/app/compute" _ "modernc.org/sqlite" ) @@ -54,7 +55,7 @@ func (r *SQLiteServerRepository) Close() error { return r.db.Close() } -func (r *SQLiteServerRepository) Create(server Server) Server { +func (r *SQLiteServerRepository) Create(server appcompute.Server) appcompute.Server { metadataJSON := marshalStringMap(server.Metadata) _, err := r.db.Exec( @@ -90,7 +91,7 @@ func (r *SQLiteServerRepository) Create(server Server) Server { return server } -func (r *SQLiteServerRepository) List() []Server { +func (r *SQLiteServerRepository) List() []appcompute.Server { rows, err := r.db.Query(` SELECT id, @@ -111,7 +112,7 @@ ORDER BY sequence`) } defer rows.Close() - servers := []Server{} + servers := []appcompute.Server{} for rows.Next() { server, err := scanServer(rows) if err != nil { @@ -126,7 +127,7 @@ ORDER BY sequence`) return servers } -func (r *SQLiteServerRepository) Get(id string) (Server, error) { +func (r *SQLiteServerRepository) Get(id string) (appcompute.Server, error) { row := r.db.QueryRow(` SELECT id, @@ -145,16 +146,16 @@ WHERE id = ?`, id) server, err := scanServer(row) if errors.Is(err, sql.ErrNoRows) { - return Server{}, ErrServerNotFound + return appcompute.Server{}, appcompute.ErrServerNotFound } if err != nil { - return Server{}, err + return appcompute.Server{}, err } return server, nil } -func (r *SQLiteServerRepository) Update(server Server) (Server, error) { +func (r *SQLiteServerRepository) Update(server appcompute.Server) (appcompute.Server, error) { metadataJSON := marshalStringMap(server.Metadata) result, err := r.db.Exec( `UPDATE compute_servers @@ -182,15 +183,15 @@ func (r *SQLiteServerRepository) Update(server Server) (Server, error) { server.ID, ) if err != nil { - return Server{}, err + return appcompute.Server{}, err } rowsAffected, err := result.RowsAffected() if err != nil { - return Server{}, err + return appcompute.Server{}, err } if rowsAffected == 0 { - return Server{}, ErrServerNotFound + return appcompute.Server{}, appcompute.ErrServerNotFound } return server, nil @@ -207,7 +208,7 @@ func (r *SQLiteServerRepository) Delete(id string) error { return err } if rowsAffected == 0 { - return ErrServerNotFound + return appcompute.ErrServerNotFound } return nil @@ -223,8 +224,8 @@ type serverScanner interface { Scan(dest ...any) error } -func scanServer(scanner serverScanner) (Server, error) { - var server Server +func scanServer(scanner serverScanner) (appcompute.Server, error) { + var server appcompute.Server var metadataJSON string if err := scanner.Scan( @@ -240,11 +241,11 @@ func scanServer(scanner serverScanner) (Server, error) { &server.UpdatedAt, &metadataJSON, ); err != nil { - return Server{}, err + return appcompute.Server{}, err } if err := json.Unmarshal([]byte(metadataJSON), &server.Metadata); err != nil { - return Server{}, err + return appcompute.Server{}, err } return server, nil diff --git a/internal/app/compute/sqlite_server_repository_test.go b/internal/store/compute/sqlite_server_repository_test.go similarity index 80% rename from internal/app/compute/sqlite_server_repository_test.go rename to internal/store/compute/sqlite_server_repository_test.go index 57c5c9b..eeded9d 100644 --- a/internal/app/compute/sqlite_server_repository_test.go +++ b/internal/store/compute/sqlite_server_repository_test.go @@ -4,13 +4,14 @@ import ( "path/filepath" "testing" - "github.com/JSYoo5B/SandStack/internal/app/compute" + appcompute "github.com/JSYoo5B/SandStack/internal/app/compute" + storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" "github.com/stretchr/testify/suite" ) type SQLiteServerRepositorySuite struct { suite.Suite - repository *compute.SQLiteServerRepository + repository *storecompute.SQLiteServerRepository } func TestSQLiteServerRepositorySuite(t *testing.T) { @@ -18,7 +19,7 @@ func TestSQLiteServerRepositorySuite(t *testing.T) { } func (s *SQLiteServerRepositorySuite) SetupTest() { - repository, err := compute.OpenSQLiteServerRepository(":memory:") + repository, err := storecompute.OpenSQLiteServerRepository(":memory:") s.Require().NoError(err) s.repository = repository @@ -51,7 +52,7 @@ func (s *SQLiteServerRepositorySuite) TestDeleteServer() { s.Require().NoError(err) _, err = s.repository.Get(created.ID) - s.Require().ErrorIs(err, compute.ErrServerNotFound) + s.Require().ErrorIs(err, appcompute.ErrServerNotFound) s.Assert().Empty(s.repository.List()) } @@ -65,13 +66,13 @@ func (s *SQLiteServerRepositorySuite) TestResetClearsServers() { func (s *SQLiteServerRepositorySuite) TestFileBackedDatabasePersistsServers() { path := filepath.Join(s.T().TempDir(), "sandstack.db") - repository, err := compute.OpenSQLiteServerRepository(path) + repository, err := storecompute.OpenSQLiteServerRepository(path) s.Require().NoError(err) created := repository.Create(serverFixture("srv-1")) s.Require().NoError(repository.Close()) - reopened, err := compute.OpenSQLiteServerRepository(path) + reopened, err := storecompute.OpenSQLiteServerRepository(path) s.Require().NoError(err) defer reopened.Close() @@ -81,8 +82,8 @@ func (s *SQLiteServerRepositorySuite) TestFileBackedDatabasePersistsServers() { s.Assert().Equal(created, found) } -func serverFixture(id string) compute.Server { - return compute.Server{ +func serverFixture(id string) appcompute.Server { + return appcompute.Server{ ID: id, Name: "web", ImageID: "img-1", From 985585a9a61a5b8abe086e70586b8d8975fc5825 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:16:10 +0900 Subject: [PATCH 6/7] refactor(volume): move repositories to store layer --- internal/api/router.go | 7 ++++- internal/api/volume/router.go | 12 ++++++- internal/app/volume/service.go | 15 --------- internal/app/volume/volume_test.go | 10 +++++- .../volume/memory_repository.go | 30 +++++++++++------- .../volume/sqlite_repository.go | 31 ++++++++++--------- .../volume/sqlite_repository_test.go | 17 +++++----- 7 files changed, 69 insertions(+), 53 deletions(-) rename internal/{app => store}/volume/memory_repository.go (60%) rename internal/{app => store}/volume/sqlite_repository.go (84%) rename internal/{app => store}/volume/sqlite_repository_test.go (81%) diff --git a/internal/api/router.go b/internal/api/router.go index 7754c93..6e4db08 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -20,6 +20,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/platform/idgen" storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" storeimage "github.com/JSYoo5B/SandStack/internal/store/image" + storevolume "github.com/JSYoo5B/SandStack/internal/store/volume" "github.com/go-chi/chi/v5" ) @@ -40,7 +41,11 @@ func NewRouter(cfg config.Config) http.Handler { idgen.Random(), ) networkService := appnetwork.NewService() - volumeService := appvolume.NewService() + volumeService := appvolume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), + clock.Wall(), + idgen.Random(), + ) router.Mount("/_sandstack", admin.NewRouterWithState(func() { computeService.Reset() diff --git a/internal/api/volume/router.go b/internal/api/volume/router.go index b534c08..604d1d8 100644 --- a/internal/api/volume/router.go +++ b/internal/api/volume/router.go @@ -4,7 +4,10 @@ import ( "net/http" appvolume "github.com/JSYoo5B/SandStack/internal/app/volume" + "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/config" + "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storevolume "github.com/JSYoo5B/SandStack/internal/store/volume" "github.com/go-chi/chi/v5" ) @@ -25,7 +28,14 @@ func NewRouterWithService( } func NewHandler(cfg config.Config) Handler { - return NewHandlerWithService(cfg, appvolume.NewService()) + return NewHandlerWithService( + cfg, + appvolume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), + clock.Wall(), + idgen.Random(), + ), + ) } func NewHandlerWithService( diff --git a/internal/app/volume/service.go b/internal/app/volume/service.go index 3076014..28c0c90 100644 --- a/internal/app/volume/service.go +++ b/internal/app/volume/service.go @@ -12,22 +12,7 @@ type Service struct { idGen idgen.Generator } -func NewService() *Service { - return NewServiceWithRuntime(clock.Wall(), idgen.Random()) -} - -func NewServiceWithClock(clock clock.Clock) *Service { - return NewServiceWithRuntime(clock, idgen.Random()) -} - func NewServiceWithRuntime( - clock clock.Clock, - idGen idgen.Generator, -) *Service { - return NewServiceWithRepository(NewMemoryRepository(), clock, idGen) -} - -func NewServiceWithRepository( repository Repository, clock clock.Clock, idGen idgen.Generator, diff --git a/internal/app/volume/volume_test.go b/internal/app/volume/volume_test.go index 5b7e68e..d60a3af 100644 --- a/internal/app/volume/volume_test.go +++ b/internal/app/volume/volume_test.go @@ -7,6 +7,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/app/volume" "github.com/JSYoo5B/SandStack/internal/platform/clock" "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storevolume "github.com/JSYoo5B/SandStack/internal/store/volume" "github.com/stretchr/testify/suite" ) @@ -20,7 +21,11 @@ func TestVolumeSuite(t *testing.T) { func (s *VolumeSuite) TestCreateVolumeUsesInjectedClock() { now := time.Date(2026, 6, 23, 8, 30, 0, 123456000, time.UTC) - service := volume.NewServiceWithClock(clock.Fixed(now)) + service := volume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), + clock.Fixed(now), + idgen.Random(), + ) created := service.Create(volume.CreateVolume{ Size: 1, @@ -33,6 +38,7 @@ func (s *VolumeSuite) TestCreateVolumeUsesInjectedClock() { func (s *VolumeSuite) TestCreateVolumeUsesInjectedIDGenerator() { service := volume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), clock.Fixed(time.Time{}), idgen.Fixed("volume-id"), ) @@ -48,6 +54,7 @@ func (s *VolumeSuite) TestCreateVolumeUsesInjectedIDGenerator() { func (s *VolumeSuite) TestGetVolumeMakesCreatedVolumeAvailable() { now := time.Date(2026, 6, 23, 8, 30, 0, 123456000, time.UTC) service := volume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), clock.Fixed(now), idgen.Fixed("volume-id"), ) @@ -66,6 +73,7 @@ func (s *VolumeSuite) TestGetVolumeMakesCreatedVolumeAvailable() { func (s *VolumeSuite) TestResetClearsVolumes() { service := volume.NewServiceWithRuntime( + storevolume.NewMemoryRepository(), clock.Fixed(time.Time{}), idgen.Fixed("volume-id"), ) diff --git a/internal/app/volume/memory_repository.go b/internal/store/volume/memory_repository.go similarity index 60% rename from internal/app/volume/memory_repository.go rename to internal/store/volume/memory_repository.go index feb1aa8..e9f0c42 100644 --- a/internal/app/volume/memory_repository.go +++ b/internal/store/volume/memory_repository.go @@ -1,21 +1,25 @@ package volume -import "sync" +import ( + "sync" + + appvolume "github.com/JSYoo5B/SandStack/internal/app/volume" +) type MemoryRepository struct { mu sync.RWMutex ids []string - volumes map[string]Volume + volumes map[string]appvolume.Volume } func NewMemoryRepository() *MemoryRepository { return &MemoryRepository{ ids: []string{}, - volumes: map[string]Volume{}, + volumes: map[string]appvolume.Volume{}, } } -func (r *MemoryRepository) Create(volume Volume) Volume { +func (r *MemoryRepository) Create(volume appvolume.Volume) appvolume.Volume { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemoryRepository) Create(volume Volume) Volume { return volume } -func (r *MemoryRepository) List() []Volume { +func (r *MemoryRepository) List() []appvolume.Volume { r.mu.RLock() defer r.mu.RUnlock() - volumes := make([]Volume, 0, len(r.ids)) + volumes := make([]appvolume.Volume, 0, len(r.ids)) for _, id := range r.ids { volumes = append(volumes, r.volumes[id]) } @@ -37,24 +41,26 @@ func (r *MemoryRepository) List() []Volume { return volumes } -func (r *MemoryRepository) Get(id string) (Volume, error) { +func (r *MemoryRepository) Get(id string) (appvolume.Volume, error) { r.mu.RLock() defer r.mu.RUnlock() volume, ok := r.volumes[id] if !ok { - return Volume{}, ErrVolumeNotFound + return appvolume.Volume{}, appvolume.ErrVolumeNotFound } return volume, nil } -func (r *MemoryRepository) Update(volume Volume) (Volume, error) { +func (r *MemoryRepository) Update( + volume appvolume.Volume, +) (appvolume.Volume, error) { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.volumes[volume.ID]; !ok { - return Volume{}, ErrVolumeNotFound + return appvolume.Volume{}, appvolume.ErrVolumeNotFound } r.volumes[volume.ID] = volume @@ -67,7 +73,7 @@ func (r *MemoryRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.volumes[id]; !ok { - return ErrVolumeNotFound + return appvolume.ErrVolumeNotFound } delete(r.volumes, id) @@ -86,5 +92,5 @@ func (r *MemoryRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.volumes = map[string]Volume{} + r.volumes = map[string]appvolume.Volume{} } diff --git a/internal/app/volume/sqlite_repository.go b/internal/store/volume/sqlite_repository.go similarity index 84% rename from internal/app/volume/sqlite_repository.go rename to internal/store/volume/sqlite_repository.go index 8c33726..9ddcea0 100644 --- a/internal/app/volume/sqlite_repository.go +++ b/internal/store/volume/sqlite_repository.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" + appvolume "github.com/JSYoo5B/SandStack/internal/app/volume" _ "modernc.org/sqlite" ) @@ -53,7 +54,7 @@ func (r *SQLiteRepository) Close() error { return r.db.Close() } -func (r *SQLiteRepository) Create(volume Volume) Volume { +func (r *SQLiteRepository) Create(volume appvolume.Volume) appvolume.Volume { metadataJSON := marshalStringMap(volume.Metadata) _, err := r.db.Exec( `INSERT INTO volumes ( @@ -90,7 +91,7 @@ func (r *SQLiteRepository) Create(volume Volume) Volume { return volume } -func (r *SQLiteRepository) List() []Volume { +func (r *SQLiteRepository) List() []appvolume.Volume { rows, err := r.db.Query(` SELECT id, @@ -112,7 +113,7 @@ ORDER BY sequence`) } defer rows.Close() - volumes := []Volume{} + volumes := []appvolume.Volume{} for rows.Next() { volume, err := scanVolume(rows) if err != nil { @@ -127,7 +128,7 @@ ORDER BY sequence`) return volumes } -func (r *SQLiteRepository) Get(id string) (Volume, error) { +func (r *SQLiteRepository) Get(id string) (appvolume.Volume, error) { row := r.db.QueryRow(` SELECT id, @@ -147,16 +148,16 @@ WHERE id = ?`, id) volume, err := scanVolume(row) if errors.Is(err, sql.ErrNoRows) { - return Volume{}, ErrVolumeNotFound + return appvolume.Volume{}, appvolume.ErrVolumeNotFound } if err != nil { - return Volume{}, err + return appvolume.Volume{}, err } return volume, nil } -func (r *SQLiteRepository) Update(volume Volume) (Volume, error) { +func (r *SQLiteRepository) Update(volume appvolume.Volume) (appvolume.Volume, error) { metadataJSON := marshalStringMap(volume.Metadata) result, err := r.db.Exec( `UPDATE volumes @@ -186,15 +187,15 @@ func (r *SQLiteRepository) Update(volume Volume) (Volume, error) { volume.ID, ) if err != nil { - return Volume{}, err + return appvolume.Volume{}, err } rowsAffected, err := result.RowsAffected() if err != nil { - return Volume{}, err + return appvolume.Volume{}, err } if rowsAffected == 0 { - return Volume{}, ErrVolumeNotFound + return appvolume.Volume{}, appvolume.ErrVolumeNotFound } return volume, nil @@ -211,7 +212,7 @@ func (r *SQLiteRepository) Delete(id string) error { return err } if rowsAffected == 0 { - return ErrVolumeNotFound + return appvolume.ErrVolumeNotFound } return nil @@ -227,8 +228,8 @@ type volumeScanner interface { Scan(dest ...any) error } -func scanVolume(scanner volumeScanner) (Volume, error) { - var volume Volume +func scanVolume(scanner volumeScanner) (appvolume.Volume, error) { + var volume appvolume.Volume var metadataJSON string var encrypted int var multiattach int @@ -247,11 +248,11 @@ func scanVolume(scanner volumeScanner) (Volume, error) { &encrypted, &multiattach, ); err != nil { - return Volume{}, err + return appvolume.Volume{}, err } if err := json.Unmarshal([]byte(metadataJSON), &volume.Metadata); err != nil { - return Volume{}, err + return appvolume.Volume{}, err } volume.Encrypted = encrypted != 0 volume.Multiattach = multiattach != 0 diff --git a/internal/app/volume/sqlite_repository_test.go b/internal/store/volume/sqlite_repository_test.go similarity index 81% rename from internal/app/volume/sqlite_repository_test.go rename to internal/store/volume/sqlite_repository_test.go index b736443..7c5b0f5 100644 --- a/internal/app/volume/sqlite_repository_test.go +++ b/internal/store/volume/sqlite_repository_test.go @@ -4,13 +4,14 @@ import ( "path/filepath" "testing" - "github.com/JSYoo5B/SandStack/internal/app/volume" + appvolume "github.com/JSYoo5B/SandStack/internal/app/volume" + storevolume "github.com/JSYoo5B/SandStack/internal/store/volume" "github.com/stretchr/testify/suite" ) type SQLiteRepositorySuite struct { suite.Suite - repository *volume.SQLiteRepository + repository *storevolume.SQLiteRepository } func TestSQLiteRepositorySuite(t *testing.T) { @@ -18,7 +19,7 @@ func TestSQLiteRepositorySuite(t *testing.T) { } func (s *SQLiteRepositorySuite) SetupTest() { - repository, err := volume.OpenSQLiteRepository(":memory:") + repository, err := storevolume.OpenSQLiteRepository(":memory:") s.Require().NoError(err) s.repository = repository @@ -50,7 +51,7 @@ func (s *SQLiteRepositorySuite) TestDeleteVolume() { s.Require().NoError(err) _, err = s.repository.Get(created.ID) - s.Require().ErrorIs(err, volume.ErrVolumeNotFound) + s.Require().ErrorIs(err, appvolume.ErrVolumeNotFound) s.Assert().Empty(s.repository.List()) } @@ -64,13 +65,13 @@ func (s *SQLiteRepositorySuite) TestResetClearsVolumes() { func (s *SQLiteRepositorySuite) TestFileBackedDatabasePersistsVolumes() { path := filepath.Join(s.T().TempDir(), "sandstack.db") - repository, err := volume.OpenSQLiteRepository(path) + repository, err := storevolume.OpenSQLiteRepository(path) s.Require().NoError(err) created := repository.Create(volumeFixture("vol-1")) s.Require().NoError(repository.Close()) - reopened, err := volume.OpenSQLiteRepository(path) + reopened, err := storevolume.OpenSQLiteRepository(path) s.Require().NoError(err) defer reopened.Close() @@ -80,8 +81,8 @@ func (s *SQLiteRepositorySuite) TestFileBackedDatabasePersistsVolumes() { s.Assert().Equal(created, found) } -func volumeFixture(id string) volume.Volume { - return volume.Volume{ +func volumeFixture(id string) appvolume.Volume { + return appvolume.Volume{ ID: id, Status: "creating", Size: 1, From 45e072e672240ae481e09913f24a91612e0bf556 Mon Sep 17 00:00:00 2001 From: JaeSang Yoo Date: Fri, 3 Jul 2026 03:19:16 +0900 Subject: [PATCH 7/7] refactor(network): move repositories to store layer --- internal/api/network/router.go | 12 +++++++- internal/api/router.go | 8 +++++- internal/app/network/service.go | 13 --------- internal/app/network/service_test.go | 18 +++++++++--- .../network/memory_network_repository.go | 28 +++++++++++-------- .../network/memory_port_repository.go | 24 +++++++++------- .../network/memory_subnet_repository.go | 24 +++++++++------- 7 files changed, 76 insertions(+), 51 deletions(-) rename internal/{app => store}/network/memory_network_repository.go (59%) rename internal/{app => store}/network/memory_port_repository.go (62%) rename internal/{app => store}/network/memory_subnet_repository.go (62%) diff --git a/internal/api/network/router.go b/internal/api/network/router.go index 92ffdf7..e340b0a 100644 --- a/internal/api/network/router.go +++ b/internal/api/network/router.go @@ -5,6 +5,8 @@ import ( appnetwork "github.com/JSYoo5B/SandStack/internal/app/network" "github.com/JSYoo5B/SandStack/internal/platform/config" + "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storenetwork "github.com/JSYoo5B/SandStack/internal/store/network" "github.com/go-chi/chi/v5" ) @@ -25,7 +27,15 @@ func NewRouterWithService( } func NewHandler(cfg config.Config) Handler { - return NewHandlerWithService(cfg, appnetwork.NewService()) + return NewHandlerWithService( + cfg, + appnetwork.NewServiceWithRepositories( + storenetwork.NewMemoryNetworkRepository(), + storenetwork.NewMemorySubnetRepository(), + storenetwork.NewMemoryPortRepository(), + idgen.Random(), + ), + ) } func NewHandlerWithService( diff --git a/internal/api/router.go b/internal/api/router.go index 6e4db08..567e1e2 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -20,6 +20,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/platform/idgen" storecompute "github.com/JSYoo5B/SandStack/internal/store/compute" storeimage "github.com/JSYoo5B/SandStack/internal/store/image" + storenetwork "github.com/JSYoo5B/SandStack/internal/store/network" storevolume "github.com/JSYoo5B/SandStack/internal/store/volume" "github.com/go-chi/chi/v5" ) @@ -40,7 +41,12 @@ func NewRouter(cfg config.Config) http.Handler { clock.Wall(), idgen.Random(), ) - networkService := appnetwork.NewService() + networkService := appnetwork.NewServiceWithRepositories( + storenetwork.NewMemoryNetworkRepository(), + storenetwork.NewMemorySubnetRepository(), + storenetwork.NewMemoryPortRepository(), + idgen.Random(), + ) volumeService := appvolume.NewServiceWithRuntime( storevolume.NewMemoryRepository(), clock.Wall(), diff --git a/internal/app/network/service.go b/internal/app/network/service.go index 1093ccd..c739476 100644 --- a/internal/app/network/service.go +++ b/internal/app/network/service.go @@ -14,19 +14,6 @@ type Service struct { idGen idgen.Generator } -func NewService() *Service { - return NewServiceWithIDGenerator(idgen.Random()) -} - -func NewServiceWithIDGenerator(idGen idgen.Generator) *Service { - return NewServiceWithRepositories( - NewMemoryNetworkRepository(), - NewMemorySubnetRepository(), - NewMemoryPortRepository(), - idGen, - ) -} - func NewServiceWithRepositories( networkRepository NetworkRepository, subnetRepository SubnetRepository, diff --git a/internal/app/network/service_test.go b/internal/app/network/service_test.go index c140583..69bce13 100644 --- a/internal/app/network/service_test.go +++ b/internal/app/network/service_test.go @@ -5,6 +5,7 @@ import ( "github.com/JSYoo5B/SandStack/internal/app/network" "github.com/JSYoo5B/SandStack/internal/platform/idgen" + storenetwork "github.com/JSYoo5B/SandStack/internal/store/network" "github.com/stretchr/testify/suite" ) @@ -17,7 +18,7 @@ func TestServiceSuite(t *testing.T) { } func (s *ServiceSuite) TestCreateNetworkUsesInjectedIDGenerator() { - service := network.NewServiceWithIDGenerator(idgen.Fixed("network-id")) + service := newService(idgen.Fixed("network-id")) created := service.Create(network.CreateNetwork{Name: "private"}) @@ -25,7 +26,7 @@ func (s *ServiceSuite) TestCreateNetworkUsesInjectedIDGenerator() { } func (s *ServiceSuite) TestCreateSubnetUsesInjectedIDGenerator() { - service := network.NewServiceWithIDGenerator(idgen.Fixed("subnet-id")) + service := newService(idgen.Fixed("subnet-id")) created := service.CreateSubnet(network.CreateSubnet{ NetworkID: "net-1", @@ -36,7 +37,7 @@ func (s *ServiceSuite) TestCreateSubnetUsesInjectedIDGenerator() { } func (s *ServiceSuite) TestCreatePortUsesInjectedIDGenerator() { - service := network.NewServiceWithIDGenerator(idgen.Fixed("port-id")) + service := newService(idgen.Fixed("port-id")) created := service.CreatePort(network.CreatePort{ NetworkID: "net-1", @@ -48,7 +49,7 @@ func (s *ServiceSuite) TestCreatePortUsesInjectedIDGenerator() { } func (s *ServiceSuite) TestResetClearsNetworkResources() { - service := network.NewServiceWithIDGenerator(idgen.Fixed("network-id")) + service := newService(idgen.Fixed("network-id")) created := service.Create(network.CreateNetwork{Name: "private"}) service.CreateSubnet(network.CreateSubnet{NetworkID: created.ID}) service.CreatePort(network.CreatePort{NetworkID: created.ID}) @@ -59,3 +60,12 @@ func (s *ServiceSuite) TestResetClearsNetworkResources() { s.Assert().Empty(service.ListSubnets()) s.Assert().Empty(service.ListPorts()) } + +func newService(idGen idgen.Generator) *network.Service { + return network.NewServiceWithRepositories( + storenetwork.NewMemoryNetworkRepository(), + storenetwork.NewMemorySubnetRepository(), + storenetwork.NewMemoryPortRepository(), + idGen, + ) +} diff --git a/internal/app/network/memory_network_repository.go b/internal/store/network/memory_network_repository.go similarity index 59% rename from internal/app/network/memory_network_repository.go rename to internal/store/network/memory_network_repository.go index 7f03c92..5525005 100644 --- a/internal/app/network/memory_network_repository.go +++ b/internal/store/network/memory_network_repository.go @@ -1,21 +1,25 @@ package network -import "sync" +import ( + "sync" + + appnetwork "github.com/JSYoo5B/SandStack/internal/app/network" +) type MemoryNetworkRepository struct { mu sync.RWMutex ids []string - networks map[string]Network + networks map[string]appnetwork.Network } func NewMemoryNetworkRepository() *MemoryNetworkRepository { return &MemoryNetworkRepository{ ids: []string{}, - networks: map[string]Network{}, + networks: map[string]appnetwork.Network{}, } } -func (r *MemoryNetworkRepository) Create(network Network) Network { +func (r *MemoryNetworkRepository) Create(network appnetwork.Network) appnetwork.Network { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemoryNetworkRepository) Create(network Network) Network { return network } -func (r *MemoryNetworkRepository) List() []Network { +func (r *MemoryNetworkRepository) List() []appnetwork.Network { r.mu.RLock() defer r.mu.RUnlock() - networks := make([]Network, 0, len(r.ids)) + networks := make([]appnetwork.Network, 0, len(r.ids)) for _, id := range r.ids { networks = append(networks, r.networks[id]) } @@ -37,24 +41,24 @@ func (r *MemoryNetworkRepository) List() []Network { return networks } -func (r *MemoryNetworkRepository) Get(id string) (Network, error) { +func (r *MemoryNetworkRepository) Get(id string) (appnetwork.Network, error) { r.mu.RLock() defer r.mu.RUnlock() network, ok := r.networks[id] if !ok { - return Network{}, ErrNetworkNotFound + return appnetwork.Network{}, appnetwork.ErrNetworkNotFound } return network, nil } -func (r *MemoryNetworkRepository) Update(network Network) (Network, error) { +func (r *MemoryNetworkRepository) Update(network appnetwork.Network) (appnetwork.Network, error) { r.mu.Lock() defer r.mu.Unlock() if _, ok := r.networks[network.ID]; !ok { - return Network{}, ErrNetworkNotFound + return appnetwork.Network{}, appnetwork.ErrNetworkNotFound } r.networks[network.ID] = network @@ -67,7 +71,7 @@ func (r *MemoryNetworkRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.networks[id]; !ok { - return ErrNetworkNotFound + return appnetwork.ErrNetworkNotFound } delete(r.networks, id) @@ -86,5 +90,5 @@ func (r *MemoryNetworkRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.networks = map[string]Network{} + r.networks = map[string]appnetwork.Network{} } diff --git a/internal/app/network/memory_port_repository.go b/internal/store/network/memory_port_repository.go similarity index 62% rename from internal/app/network/memory_port_repository.go rename to internal/store/network/memory_port_repository.go index a89b346..bb7fd42 100644 --- a/internal/app/network/memory_port_repository.go +++ b/internal/store/network/memory_port_repository.go @@ -1,21 +1,25 @@ package network -import "sync" +import ( + "sync" + + appnetwork "github.com/JSYoo5B/SandStack/internal/app/network" +) type MemoryPortRepository struct { mu sync.RWMutex ids []string - ports map[string]Port + ports map[string]appnetwork.Port } func NewMemoryPortRepository() *MemoryPortRepository { return &MemoryPortRepository{ ids: []string{}, - ports: map[string]Port{}, + ports: map[string]appnetwork.Port{}, } } -func (r *MemoryPortRepository) Create(port Port) Port { +func (r *MemoryPortRepository) Create(port appnetwork.Port) appnetwork.Port { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemoryPortRepository) Create(port Port) Port { return port } -func (r *MemoryPortRepository) List() []Port { +func (r *MemoryPortRepository) List() []appnetwork.Port { r.mu.RLock() defer r.mu.RUnlock() - ports := make([]Port, 0, len(r.ids)) + ports := make([]appnetwork.Port, 0, len(r.ids)) for _, id := range r.ids { ports = append(ports, r.ports[id]) } @@ -37,13 +41,13 @@ func (r *MemoryPortRepository) List() []Port { return ports } -func (r *MemoryPortRepository) Get(id string) (Port, error) { +func (r *MemoryPortRepository) Get(id string) (appnetwork.Port, error) { r.mu.RLock() defer r.mu.RUnlock() port, ok := r.ports[id] if !ok { - return Port{}, ErrPortNotFound + return appnetwork.Port{}, appnetwork.ErrPortNotFound } return port, nil @@ -54,7 +58,7 @@ func (r *MemoryPortRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.ports[id]; !ok { - return ErrPortNotFound + return appnetwork.ErrPortNotFound } delete(r.ports, id) @@ -73,5 +77,5 @@ func (r *MemoryPortRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.ports = map[string]Port{} + r.ports = map[string]appnetwork.Port{} } diff --git a/internal/app/network/memory_subnet_repository.go b/internal/store/network/memory_subnet_repository.go similarity index 62% rename from internal/app/network/memory_subnet_repository.go rename to internal/store/network/memory_subnet_repository.go index 148fc6f..ffb5dd5 100644 --- a/internal/app/network/memory_subnet_repository.go +++ b/internal/store/network/memory_subnet_repository.go @@ -1,21 +1,25 @@ package network -import "sync" +import ( + "sync" + + appnetwork "github.com/JSYoo5B/SandStack/internal/app/network" +) type MemorySubnetRepository struct { mu sync.RWMutex ids []string - subnets map[string]Subnet + subnets map[string]appnetwork.Subnet } func NewMemorySubnetRepository() *MemorySubnetRepository { return &MemorySubnetRepository{ ids: []string{}, - subnets: map[string]Subnet{}, + subnets: map[string]appnetwork.Subnet{}, } } -func (r *MemorySubnetRepository) Create(subnet Subnet) Subnet { +func (r *MemorySubnetRepository) Create(subnet appnetwork.Subnet) appnetwork.Subnet { r.mu.Lock() defer r.mu.Unlock() @@ -25,11 +29,11 @@ func (r *MemorySubnetRepository) Create(subnet Subnet) Subnet { return subnet } -func (r *MemorySubnetRepository) List() []Subnet { +func (r *MemorySubnetRepository) List() []appnetwork.Subnet { r.mu.RLock() defer r.mu.RUnlock() - subnets := make([]Subnet, 0, len(r.ids)) + subnets := make([]appnetwork.Subnet, 0, len(r.ids)) for _, id := range r.ids { subnets = append(subnets, r.subnets[id]) } @@ -37,13 +41,13 @@ func (r *MemorySubnetRepository) List() []Subnet { return subnets } -func (r *MemorySubnetRepository) Get(id string) (Subnet, error) { +func (r *MemorySubnetRepository) Get(id string) (appnetwork.Subnet, error) { r.mu.RLock() defer r.mu.RUnlock() subnet, ok := r.subnets[id] if !ok { - return Subnet{}, ErrSubnetNotFound + return appnetwork.Subnet{}, appnetwork.ErrSubnetNotFound } return subnet, nil @@ -54,7 +58,7 @@ func (r *MemorySubnetRepository) Delete(id string) error { defer r.mu.Unlock() if _, ok := r.subnets[id]; !ok { - return ErrSubnetNotFound + return appnetwork.ErrSubnetNotFound } delete(r.subnets, id) @@ -73,5 +77,5 @@ func (r *MemorySubnetRepository) Reset() { defer r.mu.Unlock() r.ids = []string{} - r.subnets = map[string]Subnet{} + r.subnets = map[string]appnetwork.Subnet{} }