--- /dev/null
+FROM golang:1.24.6
+
+WORKDIR /app
+
+COPY . .
+
+RUN ls
+
+CMD ["go", "run", "cmd/api/main.go"]
--- /dev/null
+package main
+
+import (
+ "jsdaj.tq/pf/persistence/inmemory"
+ "jsdaj.tq/pf/pkg/http"
+)
+
+const PORT int = 8080
+
+func main() {
+ persistence := inmemory.NewPersistence()
+
+ serv := http.InitServer(PORT, &persistence)
+ serv.Start()
+}
--- /dev/null
+services:
+ back:
+ build: .
+ ports:
+ - "8080:8080"
+ develop:
+ watch:
+ - action: rebuild
+ path: .
--- /dev/null
+module jsdaj.tq/pf
+
+go 1.24.5
--- /dev/null
+package persistence
+
+import (
+ "jsdaj.tq/pf/pkg/core"
+)
+
+type IngredientRepository interface {
+ IngredientCreate(core.Ingredient) (core.Ingredient, error)
+ IngredientUpdate(core.Ingredient) error
+ IngredientDeactivate(int) error
+ IngredientGet(int) (*core.Ingredient, error)
+ // TODO: add search params
+ IngredientList() ([]core.Ingredient, error)
+}
--- /dev/null
+package inmemory
+
+import (
+ "errors"
+ "time"
+
+ "jsdaj.tq/pf/pkg/core"
+)
+
+func (p *InmemoryPersistence) IngredientCreate(in core.Ingredient) (core.Ingredient, error) {
+ id := len(p.ingredients) + 1
+
+ createdAt := time.Now()
+
+ in.Id = id
+ in.CreatedAt = &createdAt
+ p.ingredients = append(p.ingredients, in)
+
+ return in, nil
+}
+
+func (p *InmemoryPersistence) IngredientUpdate(in core.Ingredient) error {
+ return nil
+}
+
+func (p *InmemoryPersistence) IngredientDeactivate(id int) error {
+ return nil
+}
+
+func (p *InmemoryPersistence) IngredientGet(id int) (*core.Ingredient, error) {
+ if id < 0 || id > len(p.ingredients) {
+ return nil, errors.New("not_found")
+ }
+ return &p.ingredients[id-1], nil
+}
+
+func (p *InmemoryPersistence) IngredientList() ([]core.Ingredient, error) {
+ return p.ingredients, nil
+}
--- /dev/null
+package inmemory
+
+import (
+ "jsdaj.tq/pf/pkg/core"
+)
+
+type InmemoryPersistence struct {
+ ingredients []core.Ingredient
+}
+
+func NewPersistence() InmemoryPersistence {
+ return InmemoryPersistence{
+ ingredients: []core.Ingredient{},
+ }
+}
+
--- /dev/null
+package persistence
+
+type Persistence interface {
+ IngredientRepository
+}
+
--- /dev/null
+package core
+
+import "time"
+
+type Ingredient struct {
+ Id int `json:"id,omitempty"`
+ Name string `json:"name"`
+ Quantity int `json:"quantity"`
+ Description string `json:"description"`
+ CreatedAt *time.Time `json:"createdAt,omitempty"`
+}
+
--- /dev/null
+package http
+
+import (
+ shttp "net/http"
+)
+
+func dishHandleCreate(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func dishHandleUpdate(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func dishHandleDelete(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func dishHandleGet(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func dishHandleList(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func dishHandler() shttp.Handler {
+ mux := shttp.NewServeMux()
+
+ mux.HandleFunc("POST /dish", dishHandleCreate)
+ mux.HandleFunc("PUT /dish/{id}", dishHandleUpdate)
+ mux.HandleFunc("DELETE /dish/{id}", dishHandleDelete)
+ mux.HandleFunc("GET /dish/{id}", dishHandleGet)
+ mux.HandleFunc("GET /dish", dishHandleList)
+
+ return mux
+}
--- /dev/null
+package http
+
+import (
+ "encoding/json"
+ shttp "net/http"
+
+ "jsdaj.tq/pf/pkg/services"
+)
+
+type httpError struct {
+ Status int `json:"status"`
+ Message string `json:"message"`
+}
+
+func handleError(w shttp.ResponseWriter, status int, message string) {
+ response := httpError{
+ Status: status,
+ Message: message,
+ }
+
+ body, err := json.Marshal(response)
+ if err != nil {
+ body = []byte("Something went wrong")
+ }
+
+ w.WriteHeader(status)
+ w.Write(body)
+}
+
+func handleErrorByCode(w shttp.ResponseWriter, status int) {
+ switch status {
+ case shttp.StatusBadRequest:
+ handleError(w, status, "Bad body")
+ default:
+ handleError(w, shttp.StatusInternalServerError, "Something went wrong")
+ }
+}
+
+func handleServiceError(w shttp.ResponseWriter, err services.Error) {
+ status := err.Code.MapToHTTP()
+ handleError(w, status, err.Message)
+}
--- /dev/null
+package http
+
+import (
+ "fmt"
+ "log"
+ shttp "net/http"
+
+ "jsdaj.tq/pf/persistence"
+ "jsdaj.tq/pf/pkg/services"
+)
+
+type Server struct {
+ port int
+
+ serv *shttp.Server
+ mux *shttp.ServeMux
+
+ ingredients services.Ingredient
+}
+
+func (s *Server) Start() {
+ s.initRoutes()
+
+ s.serv.Handler = middlewares(s.mux)
+
+ log.Printf(">> Starting server at :%d\n", s.port)
+ s.serv.ListenAndServe()
+}
+
+func InitServer(port int, persist persistence.Persistence) Server {
+ var mux *shttp.ServeMux = nil
+
+ serv := &shttp.Server{Addr: fmt.Sprintf(":%d", port)}
+
+ ingredientService := services.InitIngredientService(persist)
+
+ return Server{
+ port,
+ serv,
+ mux,
+ ingredientService,
+ }
+}
+
+func (s *Server) initRoutes() {
+ s.mux = shttp.NewServeMux()
+
+ s.mux.Handle("/ingredient", s.ingredientHandler())
+ s.mux.Handle("/dish", dishHandler())
+ s.mux.Handle("/menu", menuHandler())
+}
+
--- /dev/null
+package http
+
+import (
+ "encoding/json"
+ "log"
+ shttp "net/http"
+
+ "jsdaj.tq/pf/pkg/core"
+)
+
+func (s *Server) ingredientHandleCreate(w shttp.ResponseWriter, r *shttp.Request) {
+ var input core.Ingredient
+
+ err := json.NewDecoder(r.Body).Decode(&input)
+ if err != nil {
+ log.Printf("!! %s\n", err)
+ handleErrorByCode(w, shttp.StatusBadRequest)
+ return
+ }
+
+ ingredient, err := s.ingredients.Create(input)
+ if err != nil {
+ handleError(w, shttp.StatusInternalServerError, err.Error())
+ return
+ }
+
+ response, err := json.Marshal(ingredient)
+ if err != nil {
+ handleErrorByCode(w, shttp.StatusInternalServerError)
+ return
+ }
+
+ w.Write(response)
+}
+
+func (s *Server) ingredientHandleUpdate(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func (s *Server) ingredientHandleDelete(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func (s *Server) ingredientHandleGet(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func (s *Server) ingredientHandleList(w shttp.ResponseWriter, r *shttp.Request) {
+ ingredients, err := s.ingredients.List()
+ if err != nil {
+ handleError(w, shttp.StatusInternalServerError, err.Error())
+ return
+ }
+
+ response, err := json.Marshal(ingredients)
+ if err != nil {
+ handleErrorByCode(w, shttp.StatusInternalServerError)
+ return
+ }
+
+ w.Write(response)
+}
+
+func (s *Server) ingredientHandler() shttp.Handler {
+ mux := shttp.NewServeMux()
+
+ mux.HandleFunc("POST /ingredient", s.ingredientHandleCreate)
+ mux.HandleFunc("PUT /ingredient/{id}", s.ingredientHandleUpdate)
+ mux.HandleFunc("DELETE /ingredient/{id}", s.ingredientHandleDelete)
+ mux.HandleFunc("GET /ingredient/{id}", s.ingredientHandleGet)
+ mux.HandleFunc("GET /ingredient", s.ingredientHandleList)
+
+ return mux
+}
--- /dev/null
+package http
+
+import (
+ shttp "net/http"
+)
+
+func menuHandleCreate(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func menuHandleUpdate(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func menuHandleDelete(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func menuHandleGet(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func menuHandleList(w shttp.ResponseWriter, r *shttp.Request) {}
+
+func menuHandler() shttp.Handler {
+ mux := shttp.NewServeMux()
+
+ mux.HandleFunc("POST /menu", menuHandleCreate)
+ mux.HandleFunc("PUT /menu/{id}", menuHandleUpdate)
+ mux.HandleFunc("DELETE /menu/{id}", menuHandleDelete)
+ mux.HandleFunc("GET /menu/{id}", menuHandleGet)
+ mux.HandleFunc("GET /menu", menuHandleList)
+
+ return mux
+}
--- /dev/null
+package http
+
+import (
+ "log"
+ shttp "net/http"
+)
+
+func middlewareLog(next shttp.Handler) shttp.Handler {
+ return shttp.HandlerFunc(func(w shttp.ResponseWriter, r *shttp.Request) {
+ log.Printf("New request :: %s :: %s\n", r.Method, r.URL)
+ next.ServeHTTP(w, r)
+ })
+}
+
+func middlewareJson(next shttp.Handler) shttp.Handler {
+ return shttp.HandlerFunc(func(w shttp.ResponseWriter, r *shttp.Request) {
+ w.Header().Set("content-type", "application/json")
+ next.ServeHTTP(w, r)
+ })
+}
+
+func middlewares(next shttp.Handler) shttp.Handler {
+ return middlewareLog(middlewareJson(next))
+}
--- /dev/null
+package services
+
+import "fmt"
+
+type ErrorCode int
+
+const (
+ DefaultError ErrorCode = iota
+ BadInputError
+)
+
+func (c ErrorCode) MapToHTTP() int {
+ switch c {
+ case BadInputError:
+ return 400
+ default:
+ return 500
+ }
+}
+
+type Error struct {
+ Code ErrorCode
+ Message string
+}
+
+func (e Error) Error() string {
+ return fmt.Sprintf("%d :: %s", e.Code, e.Message)
+}
--- /dev/null
+package services
+
+import (
+ "jsdaj.tq/pf/persistence"
+ "jsdaj.tq/pf/pkg/core"
+)
+
+type Ingredient struct {
+ repository persistence.IngredientRepository
+}
+
+func (s *Ingredient) Create(ingredient core.Ingredient) (*core.Ingredient, error) {
+ ingredient, err := s.repository.IngredientCreate(ingredient)
+ if err != nil {
+ return nil, err
+ }
+ return &ingredient, nil
+}
+
+func (s *Ingredient) List() ([]core.Ingredient, error) {
+ ingredients, err := s.repository.IngredientList()
+ if err != nil {
+ return []core.Ingredient{}, err
+ }
+ return ingredients, nil
+}
+
+func InitIngredientService(repository persistence.IngredientRepository) Ingredient {
+ return Ingredient{
+ repository,
+ }
+}
+