feat: add farm api for farm setup

This commit is contained in:
Natthapol SERMSARAN 2025-02-14 03:05:24 +07:00
parent d6b73c9bb1
commit f3ded0f687
7 changed files with 187 additions and 22 deletions

View File

@ -25,6 +25,7 @@ require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mfridman/interpolate v0.0.2 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect

View File

@ -46,6 +46,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=

View File

@ -23,6 +23,7 @@ type api struct {
httpClient *http.Client
userRepo domain.UserRepository
farmRepo domain.FarmRepository
}
func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool) *api {
@ -30,12 +31,14 @@ func NewAPI(ctx context.Context, logger *slog.Logger, pool *pgxpool.Pool) *api {
client := &http.Client{}
userRepository := repository.NewPostgresUser(pool)
farmRepository := repository.NewPostgresFarm(pool)
return &api{
logger: logger,
httpClient: client,
userRepo: userRepository,
farmRepo: farmRepository,
}
}
@ -69,7 +72,8 @@ func (a *api) Routes() *chi.Mux {
router.Group(func(r chi.Router) {
api.UseMiddleware(m.AuthMiddleware(api))
a.registerHelloRoutes(r, api)
a.registerHelloRoutes(r, api)
a.registerFarmRoutes(r, api)
})
return router

View File

@ -0,0 +1,138 @@
package api
import (
"context"
"net/http"
"github.com/danielgtaylor/huma/v2"
"github.com/forfarm/backend/internal/domain"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
)
func (a *api) registerFarmRoutes(_ chi.Router, api huma.API) {
tags := []string{"farm"}
prefix := "/farm"
huma.Register(api, huma.Operation{
OperationID: "createFarm",
Method: http.MethodPost,
Path: prefix,
Tags: tags,
}, a.createFarmHandler)
huma.Register(api, huma.Operation{
OperationID: "getFarmsByOwner",
Method: http.MethodGet,
Path: prefix + "/owner/{owner_id}",
Tags: tags,
}, a.getFarmsByOwnerHandler)
huma.Register(api, huma.Operation{
OperationID: "getFarmByID",
Method: http.MethodGet,
Path: prefix + "/{farm_id}",
Tags: tags,
}, a.getFarmByIDHandler)
huma.Register(api, huma.Operation{
OperationID: "deleteFarm",
Method: http.MethodDelete,
Path: prefix + "/{farm_id}",
Tags: tags,
}, a.deleteFarmHandler)
}
type CreateFarmInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
Body struct {
Name string `json:"name"`
Lat []float64 `json:"lat"`
Lon []float64 `json:"lon"`
OwnerID string `json:"owner_id"`
PlantTypes []uuid.UUID `json:"plant_types"`
}
}
type CreateFarmOutput struct {
Body struct {
UUID string `json:"uuid"`
}
}
func (a *api) createFarmHandler(ctx context.Context, input *CreateFarmInput) (*CreateFarmOutput, error) {
farm := &domain.Farm{
Name: input.Body.Name,
Lat: input.Body.Lat,
Lon: input.Body.Lon,
OwnerID: input.Body.OwnerID,
PlantTypes: input.Body.PlantTypes,
}
err := a.farmRepo.CreateOrUpdate(ctx, farm)
if err != nil {
return nil, err
}
return &CreateFarmOutput{Body: struct {
UUID string `json:"uuid"`
}{UUID: farm.UUID}}, nil
}
type GetFarmsByOwnerInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
OwnerID string `path:"owner_id"`
}
type GetFarmsByOwnerOutput struct {
Body []domain.Farm
}
func (a *api) getFarmsByOwnerHandler(ctx context.Context, input *GetFarmsByOwnerInput) (*GetFarmsByOwnerOutput, error) {
farms, err := a.farmRepo.GetByOwnerID(ctx, input.OwnerID)
if err != nil {
return nil, err
}
return &GetFarmsByOwnerOutput{Body: farms}, nil
}
type GetFarmByIDInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
FarmID string `path:"farm_id"`
}
type GetFarmByIDOutput struct {
Body domain.Farm
}
func (a *api) getFarmByIDHandler(ctx context.Context, input *GetFarmByIDInput) (*GetFarmByIDOutput, error) {
farm, err := a.farmRepo.GetByID(ctx, input.FarmID)
if err != nil {
return nil, err
}
return &GetFarmByIDOutput{Body: farm}, nil
}
type DeleteFarmInput struct {
Header string `header:"Authorization" required:"true" example:"Bearer token"`
FarmID string `path:"farm_id"`
}
type DeleteFarmOutput struct {
Body struct {
Message string `json:"message"`
}
}
func (a *api) deleteFarmHandler(ctx context.Context, input *DeleteFarmInput) (*DeleteFarmOutput, error) {
err := a.farmRepo.Delete(ctx, input.FarmID)
if err != nil {
return nil, err
}
return &DeleteFarmOutput{Body: struct {
Message string `json:"message"`
}{Message: "Farm deleted successfully"}}, nil
}

View File

@ -2,19 +2,20 @@ package domain
import (
"context"
"time"
validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/google/uuid"
"time"
)
type Farm struct {
UUID string
Name string
Lat float64
Lon float64
CreatedAt time.Time
UpdatedAt time.Time
OwnerID string
UUID string
Name string
Lat []float64
Lon []float64
CreatedAt time.Time
UpdatedAt time.Time
OwnerID string
PlantTypes []uuid.UUID
}
func (f *Farm) Validate() error {
@ -28,6 +29,7 @@ func (f *Farm) Validate() error {
type FarmRepository interface {
GetByID(context.Context, string) (Farm, error)
GetByOwnerID(context.Context, string) ([]Farm, error)
CreateOrUpdate(context.Context, *Farm) error
Delete(context.Context, string) error
}

View File

@ -2,11 +2,10 @@ package repository
import (
"context"
"strings"
"github.com/google/uuid"
"github.com/forfarm/backend/internal/domain"
"github.com/google/uuid"
"github.com/lib/pq"
"strings"
)
type postgresFarmRepository struct {
@ -27,6 +26,7 @@ func (p *postgresFarmRepository) fetch(ctx context.Context, query string, args .
var farms []domain.Farm
for rows.Next() {
var f domain.Farm
var plantTypes pq.StringArray
if err := rows.Scan(
&f.UUID,
&f.Name,
@ -35,9 +35,19 @@ func (p *postgresFarmRepository) fetch(ctx context.Context, query string, args .
&f.CreatedAt,
&f.UpdatedAt,
&f.OwnerID,
&plantTypes,
); err != nil {
return nil, err
}
for _, plantTypeStr := range plantTypes {
plantTypeUUID, err := uuid.Parse(plantTypeStr)
if err != nil {
return nil, err
}
f.PlantTypes = append(f.PlantTypes, plantTypeUUID)
}
farms = append(farms, f)
}
return farms, nil
@ -45,7 +55,7 @@ func (p *postgresFarmRepository) fetch(ctx context.Context, query string, args .
func (p *postgresFarmRepository) GetByID(ctx context.Context, uuid string) (domain.Farm, error) {
query := `
SELECT uuid, name, lat, lon, created_at, updated_at, owner_id
SELECT uuid, name, lat, lon, created_at, updated_at, owner_id, plant_types
FROM farms
WHERE uuid = $1`
@ -61,7 +71,7 @@ func (p *postgresFarmRepository) GetByID(ctx context.Context, uuid string) (doma
func (p *postgresFarmRepository) GetByOwnerID(ctx context.Context, ownerID string) ([]domain.Farm, error) {
query := `
SELECT uuid, name, lat, lon, created_at, updated_at, owner_id
SELECT uuid, name, lat, lon, created_at, updated_at, owner_id, plant_types
FROM farms
WHERE owner_id = $1`
@ -73,15 +83,21 @@ func (p *postgresFarmRepository) CreateOrUpdate(ctx context.Context, f *domain.F
f.UUID = uuid.New().String()
}
plantTypes := make([]string, len(f.PlantTypes))
for i, pt := range f.PlantTypes {
plantTypes[i] = pt.String()
}
query := `
INSERT INTO farms (uuid, name, lat, lon, created_at, updated_at, owner_id)
VALUES ($1, $2, $3, $4, NOW(), NOW(), $5)
INSERT INTO farms (uuid, name, lat, lon, created_at, updated_at, owner_id, plant_types)
VALUES ($1, $2, $3, $4, NOW(), NOW(), $5, $6)
ON CONFLICT (uuid) DO UPDATE
SET name = EXCLUDED.name,
lat = EXCLUDED.lat,
lon = EXCLUDED.lon,
updated_at = NOW(),
owner_id = EXCLUDED.owner_id
owner_id = EXCLUDED.owner_id,
plant_types = EXCLUDED.plant_types
RETURNING uuid, created_at, updated_at`
return p.conn.QueryRow(
@ -92,7 +108,8 @@ func (p *postgresFarmRepository) CreateOrUpdate(ctx context.Context, f *domain.F
f.Lat,
f.Lon,
f.OwnerID,
).Scan(&f.CreatedAt, &f.UpdatedAt)
pq.StringArray(plantTypes),
).Scan(&f.UUID, &f.CreatedAt, &f.UpdatedAt)
}
func (p *postgresFarmRepository) Delete(ctx context.Context, uuid string) error {

View File

@ -43,8 +43,9 @@ CREATE TABLE plants (
CREATE TABLE farms (
uuid UUID PRIMARY KEY DEFAULT gen_random_uuid(),
name TEXT NOT NULL,
lat DOUBLE PRECISION NOT NULL,
lon DOUBLE PRECISION NOT NULL,
lat DOUBLE PRECISION[] NOT NULL,
lon DOUBLE PRECISION[] NOT NULL,
plant_types UUID[],
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
owner_id UUID NOT NULL,