diff --git a/example/cmd/app/main.go b/example/cmd/app/main.go new file mode 100644 index 0000000..cff5783 --- /dev/null +++ b/example/cmd/app/main.go @@ -0,0 +1,58 @@ +package main + +import ( + "context" + "flag" + "log" + "net/http" + "os" + "os/signal" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" + + "github.com/neonxp/geezer" + "github.com/neonxp/geezer/services/mongodb" + + "github.com/neonxp/geezer/example/services/hello" +) + +var ( + listen string + mongoURI string + mongoDB string +) + +func main() { + flag.StringVar(&listen, "listen", ":3000", "Host and port to listen (ex: '0.0.0.0:3000')") + flag.StringVar(&mongoURI, "mongo", "mongodb://localhost:27017/", "MongoDB connection uri (ex: 'mongodb://user:pass@sample.host:27017/')") + flag.StringVar(&mongoDB, "mongo_db", "geezer", "Database name") + flag.Parse() + ctx, cancel := signal.NotifyContext(context.Background(), os.Kill, os.Interrupt) + defer cancel() + + // MongoDB connection + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(mongoURI)) + if err != nil { + log.Fatalln(err) + } + defer client.Disconnect(context.Background()) + db := client.Database(mongoDB) + + app := geezer.NewHttpKernel() + + hello.RegisterHooks(app) // Register hooks + _ = app.Register(hello.ServiceName, &hello.Service{}) // Register service as external handler + + _ = app.Register("test", mongodb.New[Product](db.Collection("test"))) // Register mongodb crud service + + log.Printf("Started on %s\n", listen) + srv := http.Server{Addr: listen, Handler: app} + go func() { + <-ctx.Done() + srv.Close() + }() + if err := srv.ListenAndServe(); err != http.ErrServerClosed { + log.Fatalln(err) + } +} diff --git a/example/cmd/app/product.go b/example/cmd/app/product.go new file mode 100644 index 0000000..9e99829 --- /dev/null +++ b/example/cmd/app/product.go @@ -0,0 +1,11 @@ +package main + +import "go.mongodb.org/mongo-driver/bson/primitive" + +type Product struct { + ID primitive.ObjectID `json:"-" bson:"_id,omitempty"` + ProductID int `json:"productId"` + ProductName string `json:"productName"` + Price int `json:"price"` + Tags []string `json:"tags"` +} diff --git a/example/services/hello/hooks.go b/example/services/hello/hooks.go new file mode 100644 index 0000000..66da96d --- /dev/null +++ b/example/services/hello/hooks.go @@ -0,0 +1,18 @@ +package hello + +import ( + "log" + + "github.com/neonxp/geezer" +) + +func RegisterHooks(app geezer.AppKernel) { + app.Hook(ServiceName, geezer.HookBefore, geezer.HookFind, func(ctx *geezer.HookContext) error { + log.Printf("Hook before find") + return nil + }) + app.Hook(ServiceName, geezer.HookAfter, geezer.HookFind, func(ctx *geezer.HookContext) error { + log.Printf("Hook after find") + return nil + }) +} diff --git a/example/services/hello/service.go b/example/services/hello/service.go new file mode 100644 index 0000000..81db4fc --- /dev/null +++ b/example/services/hello/service.go @@ -0,0 +1,56 @@ +package hello + +import ( + "context" + "log" + + "github.com/neonxp/geezer" + "github.com/neonxp/geezer/render" +) + +const ServiceName = "hello" + +type Service struct { +} + +func (s Service) Find(ctx context.Context, params geezer.Params) (render.Renderer, error) { + //TODO implement me + log.Printf("Find params=%+v", params) + return render.Text("text/html", "Hello, world!"), nil +} + +func (s Service) Get(ctx context.Context, id string, params geezer.Params) (render.Renderer, error) { + //TODO implement me + log.Printf("Get id=%s params=%+v", id, params) + return nil, geezer.ErrMethodNotFound +} + +func (s Service) Create(ctx context.Context, data geezer.Data, params geezer.Params) (render.Renderer, error) { + //TODO implement me + log.Printf("Create data=%s params=%+v", data, params) + return nil, geezer.ErrMethodNotFound +} + +func (s Service) Update(ctx context.Context, id string, data geezer.Data, params geezer.Params) (render.Renderer, error) { + //TODO implement me + log.Printf("Update id=%s data=%s params=%+v", id, data, params) + return nil, geezer.ErrMethodNotFound +} + +func (s Service) Patch(ctx context.Context, id string, data geezer.Data, params geezer.Params) (render.Renderer, error) { + //TODO implement me + log.Printf("Patch id=%s data=%s params=%+v", id, data, params) + return nil, geezer.ErrMethodNotFound +} + +func (s Service) Remove(ctx context.Context, id string, params geezer.Params) error { + //TODO implement me + log.Printf("Remove id=%s params=%+v", id, params) + return geezer.ErrMethodNotFound +} + +func (s Service) Setup(app geezer.AppKernel, path string) error { + //TODO implement me + log.Printf("Setup path=%s", path) + return nil +} diff --git a/example/test.http b/example/test.http new file mode 100644 index 0000000..8b89020 --- /dev/null +++ b/example/test.http @@ -0,0 +1,19 @@ +POST http://localhost:3000/test +Content-Type: application/json + +{ + "productId": 123, + "productName": "test", + "price": 123, + "tags": [ + "one", "two", "three" + ] +} + +### + +GET http://localhost:3000/test/62017d3fbfd5a64b46077ece + +### + +GET http://localhost:3000/test?price=123 \ No newline at end of file diff --git a/go.mod b/go.mod index 1eb4bb5..697d217 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,24 @@ module github.com/neonxp/geezer go 1.18 + +require ( + github.com/xeipuuv/gojsonschema v1.2.0 + go.mongodb.org/mongo-driver v1.8.3 +) + +require ( + github.com/go-stack/stack v1.8.0 // indirect + github.com/golang/snappy v0.0.1 // indirect + github.com/klauspost/compress v1.13.6 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.0.2 // indirect + github.com/xdg-go/stringprep v1.0.2 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect + github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect + github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f // indirect + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect + golang.org/x/text v0.3.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..76d9116 --- /dev/null +++ b/go.sum @@ -0,0 +1,63 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/stringprep v1.0.2 h1:6iq84/ryjjeRmMJwxutI51F2GIPlP5BfTvXHeYjyhBc= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +go.mongodb.org/mongo-driver v1.8.3 h1:TDKlTkGDKm9kkJVUOAXDK5/fkqKHJVwYQSpoRfB43R4= +go.mongodb.org/mongo-driver v1.8.3/go.mod h1:0sQWfOeY63QTntERDJJ/0SuKK0T1uVSgKCuAROlKEPY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f h1:aZp0e2vLN4MToVqnjNEYEtrEA8RH8U8FN1CU7JgqsPU= +golang.org/x/crypto v0.0.0-20201216223049-8b5274cf687f/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190531172133-b3315ee88b7d/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/hook.go b/hook.go index 724b99d..97028fd 100644 --- a/hook.go +++ b/hook.go @@ -33,7 +33,7 @@ var hookTypeFromMethod = map[Method]HookType{ } type HookContext struct { - App Kernel + App AppKernel Path []string Method Method Type HookLifecycle diff --git a/httpKernel.go b/httpKernel.go index 8058bfa..9cc1264 100644 --- a/httpKernel.go +++ b/httpKernel.go @@ -8,12 +8,12 @@ import ( ) type HttpKernel struct { - Kernel + AppKernel } func NewHttpKernel() *HttpKernel { return &HttpKernel{ - Kernel: newKernel(), + AppKernel: newKernel(), } } @@ -57,7 +57,6 @@ func (s *HttpKernel) ServeHTTP(w http.ResponseWriter, r *http.Request) { } params := Params{ - Ctx: ctx, Path: parts, Query: Values(u.Query()), Headers: Values(r.Header), @@ -72,7 +71,7 @@ func (s *HttpKernel) ServeHTTP(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() data := Data(b) - result, err := s.Call(method, name, id, data, params) + result, err := s.Call(ctx, method, name, id, data, params) if err != nil { if err == ErrMethodNotFound { w.WriteHeader(http.StatusNotFound) diff --git a/kernel.go b/kernel.go index 0f0356e..0180036 100644 --- a/kernel.go +++ b/kernel.go @@ -1,6 +1,7 @@ package geezer import ( + "context" "errors" "strings" @@ -12,19 +13,19 @@ var ( ErrMethodNotFound = errors.New("method not found") ) -type defaultKernel struct { +type kernel struct { routes map[string]Service hooks map[string]map[HookLifecycle]map[HookType][]Hook } -func newKernel() *defaultKernel { - return &defaultKernel{ +func newKernel() *kernel { + return &kernel{ routes: map[string]Service{}, hooks: map[string]map[HookLifecycle]map[HookType][]Hook{}, } } -func (s *defaultKernel) Register(name string, service Service) error { +func (s *kernel) Register(name string, service Service) error { name = strings.ToLower(name) s.routes[name] = service if _, exist := s.hooks[name]; !exist { @@ -36,7 +37,7 @@ func (s *defaultKernel) Register(name string, service Service) error { return nil } -func (s *defaultKernel) Hook(service string, lifecycle HookLifecycle, hookType HookType, hook Hook) { +func (s *kernel) Hook(service string, lifecycle HookLifecycle, hookType HookType, hook Hook) { service = strings.ToLower(service) if _, exist := s.hooks[service]; !exist { s.hooks[service] = map[HookLifecycle]map[HookType][]Hook{} @@ -50,14 +51,14 @@ func (s *defaultKernel) Hook(service string, lifecycle HookLifecycle, hookType H s.hooks[service][lifecycle][hookType] = append(s.hooks[service][lifecycle][hookType], hook) } -func (s *defaultKernel) Service(name string) Service { +func (s *kernel) Service(name string) Service { if service, exist := s.routes[name]; exist { return service } return nil } -func (s *defaultKernel) Call(method Method, name, id string, data Data, params Params) (render.Renderer, error) { +func (s *kernel) Call(ctx context.Context, method Method, name, id string, data Data, params Params) (render.Renderer, error) { name = strings.ToLower(name) service := s.Service(name) if service == nil { @@ -70,24 +71,24 @@ func (s *defaultKernel) Call(method Method, name, id string, data Data, params P } switch hookCtx.Method { case MethodFind: - result, err = service.Find(hookCtx.Params) + result, err = service.Find(ctx, hookCtx.Params) case MethodGet: - result, err = service.Get(hookCtx.ID, hookCtx.Params) + result, err = service.Get(ctx, hookCtx.ID, hookCtx.Params) case MethodCreate: - result, err = service.Create(hookCtx.Data, hookCtx.Params) + result, err = service.Create(ctx, hookCtx.Data, hookCtx.Params) case MethodUpdate: - result, err = service.Update(hookCtx.ID, hookCtx.Data, hookCtx.Params) + result, err = service.Update(ctx, hookCtx.ID, hookCtx.Data, hookCtx.Params) case MethodPatch: - result, err = service.Patch(hookCtx.ID, hookCtx.Data, hookCtx.Params) + result, err = service.Patch(ctx, hookCtx.ID, hookCtx.Data, hookCtx.Params) case MethodRemove: - err = service.Remove(hookCtx.ID, hookCtx.Params) + err = service.Remove(ctx, hookCtx.ID, hookCtx.Params) default: return nil, ErrMethodNotFound } return s.callAfterHooks(method, name, hookCtx, result, err) } -func (s *defaultKernel) callBeforeHooks(method Method, name string, id string, data Data, params Params) (*HookContext, render.Renderer, error) { +func (s *kernel) callBeforeHooks(method Method, name string, id string, data Data, params Params) (*HookContext, render.Renderer, error) { var beforeHooks []Hook if hooks, ok := s.hooks[name][HookBefore]; ok { if allHooks, ok := hooks[HookAll]; ok { @@ -117,7 +118,7 @@ func (s *defaultKernel) callBeforeHooks(method Method, name string, id string, d return hookCtx, nil, nil } -func (s *defaultKernel) callAfterHooks(method Method, name string, hookCtx *HookContext, result render.Renderer, err error) (render.Renderer, error) { +func (s *kernel) callAfterHooks(method Method, name string, hookCtx *HookContext, result render.Renderer, err error) (render.Renderer, error) { var afterHooks []Hook if hooks, ok := s.hooks[name][HookAfter]; ok { if allHooks, ok := hooks[HookAll]; ok { @@ -154,9 +155,9 @@ func (s *defaultKernel) callAfterHooks(method Method, name string, hookCtx *Hook return hookCtx.Result, hookCtx.Err } -type Kernel interface { +type AppKernel interface { Register(name string, service Service) error Hook(service string, lifecycle HookLifecycle, hookType HookType, hook Hook) Service(name string) Service - Call(method Method, name, id string, data Data, params Params) (render.Renderer, error) + Call(ctx context.Context, method Method, name, id string, data Data, params Params) (render.Renderer, error) } diff --git a/params.go b/params.go index c57df1b..4003263 100644 --- a/params.go +++ b/params.go @@ -1,9 +1,6 @@ package geezer -import "context" - type Params struct { - Ctx context.Context Path []string Query Values Headers Values diff --git a/service.go b/service.go index 2e6f4bd..07f798e 100644 --- a/service.go +++ b/service.go @@ -1,19 +1,20 @@ package geezer import ( + "context" "encoding/json" "github.com/neonxp/geezer/render" ) type Service interface { - Find(params Params) (render.Renderer, error) - Get(id string, params Params) (render.Renderer, error) - Create(data Data, params Params) (render.Renderer, error) - Update(id string, data Data, params Params) (render.Renderer, error) - Patch(id string, data Data, params Params) (render.Renderer, error) - Remove(id string, params Params) error - Setup(app Kernel, path string) error + Find(ctx context.Context, params Params) (render.Renderer, error) + Get(ctx context.Context, id string, params Params) (render.Renderer, error) + Create(ctx context.Context, data Data, params Params) (render.Renderer, error) + Update(ctx context.Context, id string, data Data, params Params) (render.Renderer, error) + Patch(ctx context.Context, id string, data Data, params Params) (render.Renderer, error) + Remove(ctx context.Context, id string, params Params) error + Setup(app AppKernel, path string) error } type Method int diff --git a/services/mongodb/models.go b/services/mongodb/models.go new file mode 100644 index 0000000..9b38d8d --- /dev/null +++ b/services/mongodb/models.go @@ -0,0 +1,6 @@ +package mongodb + +type InsertResult struct { + ID any `json:"id"` + Item any `json:"item"` +} diff --git a/services/mongodb/service.go b/services/mongodb/service.go new file mode 100644 index 0000000..c0f779f --- /dev/null +++ b/services/mongodb/service.go @@ -0,0 +1,98 @@ +package mongodb + +import ( + "context" + "encoding/json" + + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo" + + "github.com/neonxp/geezer" + "github.com/neonxp/geezer/render" +) + +type Service[T any] struct { + collection *mongo.Collection +} + +func New[T any](collection *mongo.Collection) *Service[T] { + return &Service[T]{collection: collection} +} + +func (s Service[T]) Find(ctx context.Context, params geezer.Params) (render.Renderer, error) { + var model []*T + where := bson.D{} + for k, v := range params.Query { + where = append(where, bson.E{Key: k, Value: v[0]}) + } + cursor, err := s.collection.Find(ctx, where) + if err != nil { + return nil, err + } + for cursor.Next(ctx) { + r := new(T) + if err := cursor.Decode(r); err != nil { + return nil, err + } + model = append(model, r) + } + + return render.JSON(model), nil +} + +func (s Service[T]) Get(ctx context.Context, id string, params geezer.Params) (render.Renderer, error) { + var model T + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return nil, err + } + sr := s.collection.FindOne(ctx, bson.M{"_id": oid}) + if err := sr.Err(); err != nil { + return nil, err + } + if err := sr.Decode(&model); err != nil { + return nil, err + } + + return render.JSON(model), nil +} + +func (s Service[T]) Create(ctx context.Context, data geezer.Data, params geezer.Params) (render.Renderer, error) { + var model T + if err := json.Unmarshal(data, &model); err != nil { + return nil, err + } + ir, err := s.collection.InsertOne(ctx, model) + if err != nil { + return nil, err + } + + return render.JSON(InsertResult{ + ID: ir.InsertedID, + Item: model, + }), nil +} + +func (s Service[T]) Update(ctx context.Context, id string, data geezer.Data, params geezer.Params) (render.Renderer, error) { + //TODO implement me + panic("implement me") +} + +func (s Service[T]) Patch(ctx context.Context, id string, data geezer.Data, params geezer.Params) (render.Renderer, error) { + //TODO implement me + panic("implement me") +} + +func (s Service[T]) Remove(ctx context.Context, id string, params geezer.Params) error { + oid, err := primitive.ObjectIDFromHex(id) + if err != nil { + return err + } + _, err = s.collection.DeleteOne(ctx, bson.M{"_id": oid}) + return err +} + +func (s Service[T]) Setup(app geezer.AppKernel, path string) error { + return nil +}