mirror of
https://github.com/kataras/iris.git
synced 2025-12-18 02:17:05 +00:00
reorganization of _examples and add some new examples such as iris+groupcache+mysql+docker
Former-commit-id: ed635ee95de7160cde11eaabc0c1dcb0e460a620
This commit is contained in:
2
_examples/database/mongodb/.env
Normal file
2
_examples/database/mongodb/.env
Normal file
@@ -0,0 +1,2 @@
|
||||
PORT=8080
|
||||
DSN=mongodb://localhost:27017
|
||||
BIN
_examples/database/mongodb/0_create_movie.png
Normal file
BIN
_examples/database/mongodb/0_create_movie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
BIN
_examples/database/mongodb/1_update_movie.png
Normal file
BIN
_examples/database/mongodb/1_update_movie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 69 KiB |
BIN
_examples/database/mongodb/2_get_all_movies.png
Normal file
BIN
_examples/database/mongodb/2_get_all_movies.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
BIN
_examples/database/mongodb/3_get_movie.png
Normal file
BIN
_examples/database/mongodb/3_get_movie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 83 KiB |
BIN
_examples/database/mongodb/4_delete_movie.png
Normal file
BIN
_examples/database/mongodb/4_delete_movie.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
17
_examples/database/mongodb/Dockerfile
Normal file
17
_examples/database/mongodb/Dockerfile
Normal file
@@ -0,0 +1,17 @@
|
||||
# docker build -t myapp .
|
||||
# docker run --rm -it -p 8080:8080 myapp:latest
|
||||
FROM golang:latest AS builder
|
||||
RUN apt-get update
|
||||
ENV GO111MODULE=on \
|
||||
CGO_ENABLED=0 \
|
||||
GOOS=linux \
|
||||
GOARCH=amd64
|
||||
WORKDIR /go/src/app
|
||||
COPY go.mod .
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go install
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /go/bin/myapp .
|
||||
ENTRYPOINT ["./myapp"]
|
||||
64
_examples/database/mongodb/README.md
Normal file
64
_examples/database/mongodb/README.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Build RESTful API with the official MongoDB Go Driver and Iris
|
||||
|
||||
Article is coming soon, follow and stay tuned
|
||||
|
||||
- <https://medium.com/@kataras>
|
||||
- <https://dev.to/kataras>
|
||||
|
||||
Read [the fully functional example](main.go).
|
||||
|
||||
## Run
|
||||
|
||||
### Docker
|
||||
|
||||
Install [Docker](https://www.docker.com/) and execute the command below
|
||||
|
||||
```sh
|
||||
$ docker-compose up
|
||||
```
|
||||
|
||||
### Manually
|
||||
|
||||
```sh
|
||||
# .env file contents
|
||||
PORT=8080
|
||||
DSN=mongodb://localhost:27017
|
||||
```
|
||||
|
||||
```sh
|
||||
$ go run main.go
|
||||
> 2019/01/28 05:17:59 Loading environment variables from file: .env
|
||||
> 2019/01/28 05:17:59 ◽ Port=8080
|
||||
> 2019/01/28 05:17:59 ◽ DSN=mongodb://localhost:27017
|
||||
> Now listening on: http://localhost:8080
|
||||
```
|
||||
|
||||
```sh
|
||||
GET : http://localhost:8080/api/store/movies
|
||||
POST : http://localhost:8080/api/store/movies
|
||||
GET : http://localhost:8080/api/store/movies/{id}
|
||||
PUT : http://localhost:8080/api/store/movies/{id}
|
||||
DELETE : http://localhost:8080/api/store/movies/{id}
|
||||
```
|
||||
|
||||
## Screens
|
||||
|
||||
### Add a Movie
|
||||

|
||||
|
||||
### Update a Movie
|
||||
|
||||

|
||||
|
||||
### Get all Movies
|
||||
|
||||

|
||||
|
||||
### Get a Movie by its ID
|
||||
|
||||

|
||||
|
||||
### Delete a Movie by its ID
|
||||
|
||||

|
||||
|
||||
101
_examples/database/mongodb/api/store/movie.go
Normal file
101
_examples/database/mongodb/api/store/movie.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package storeapi
|
||||
|
||||
import (
|
||||
"myapp/httputil"
|
||||
"myapp/store"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
type MovieHandler struct {
|
||||
service store.MovieService
|
||||
}
|
||||
|
||||
func NewMovieHandler(service store.MovieService) *MovieHandler {
|
||||
return &MovieHandler{service: service}
|
||||
}
|
||||
|
||||
func (h *MovieHandler) GetAll(ctx iris.Context) {
|
||||
movies, err := h.service.GetAll(nil)
|
||||
if err != nil {
|
||||
httputil.InternalServerErrorJSON(ctx, err, "Server was unable to retrieve all movies")
|
||||
return
|
||||
}
|
||||
|
||||
if movies == nil {
|
||||
// will return "null" if empty, with this "trick" we return "[]" json.
|
||||
movies = make([]store.Movie, 0)
|
||||
}
|
||||
|
||||
ctx.JSON(movies)
|
||||
}
|
||||
|
||||
func (h *MovieHandler) Get(ctx iris.Context) {
|
||||
id := ctx.Params().Get("id")
|
||||
|
||||
m, err := h.service.GetByID(nil, id)
|
||||
if err != nil {
|
||||
if err == store.ErrNotFound {
|
||||
ctx.NotFound()
|
||||
} else {
|
||||
httputil.InternalServerErrorJSON(ctx, err, "Server was unable to retrieve movie [%s]", id)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
ctx.JSON(m)
|
||||
}
|
||||
|
||||
func (h *MovieHandler) Add(ctx iris.Context) {
|
||||
m := new(store.Movie)
|
||||
|
||||
err := ctx.ReadJSON(m)
|
||||
if err != nil {
|
||||
httputil.FailJSON(ctx, iris.StatusBadRequest, err, "Malformed request payload")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.service.Create(nil, m)
|
||||
if err != nil {
|
||||
httputil.InternalServerErrorJSON(ctx, err, "Server was unable to create a movie")
|
||||
return
|
||||
}
|
||||
|
||||
ctx.StatusCode(iris.StatusCreated)
|
||||
ctx.JSON(m)
|
||||
}
|
||||
|
||||
func (h *MovieHandler) Update(ctx iris.Context) {
|
||||
id := ctx.Params().Get("id")
|
||||
|
||||
var m store.Movie
|
||||
err := ctx.ReadJSON(&m)
|
||||
if err != nil {
|
||||
httputil.FailJSON(ctx, iris.StatusBadRequest, err, "Malformed request payload")
|
||||
return
|
||||
}
|
||||
|
||||
err = h.service.Update(nil, id, m)
|
||||
if err != nil {
|
||||
if err == store.ErrNotFound {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
httputil.InternalServerErrorJSON(ctx, err, "Server was unable to update movie [%s]", id)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (h *MovieHandler) Delete(ctx iris.Context) {
|
||||
id := ctx.Params().Get("id")
|
||||
|
||||
err := h.service.Delete(nil, id)
|
||||
if err != nil {
|
||||
if err == store.ErrNotFound {
|
||||
ctx.NotFound()
|
||||
return
|
||||
}
|
||||
httputil.InternalServerErrorJSON(ctx, err, "Server was unable to delete movie [%s]", id)
|
||||
return
|
||||
}
|
||||
}
|
||||
18
_examples/database/mongodb/docker-compose.yml
Normal file
18
_examples/database/mongodb/docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: "3.1"
|
||||
|
||||
services:
|
||||
app:
|
||||
build: .
|
||||
environment:
|
||||
Port: 8080
|
||||
DSN: db:27017
|
||||
ports:
|
||||
- 8080:8080
|
||||
depends_on:
|
||||
- db
|
||||
db:
|
||||
image: mongo
|
||||
environment:
|
||||
MONGO_INITDB_DATABASE: store
|
||||
ports:
|
||||
- 27017:27017
|
||||
85
_examples/database/mongodb/env/env.go
vendored
Normal file
85
_examples/database/mongodb/env/env.go
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
package env
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
var (
|
||||
// Port is the PORT environment variable or 8080 if missing.
|
||||
// Used to open the tcp listener for our web server.
|
||||
Port string
|
||||
// DSN is the DSN environment variable or mongodb://localhost:27017 if missing.
|
||||
// Used to connect to the mongodb.
|
||||
DSN string
|
||||
)
|
||||
|
||||
func parse() {
|
||||
Port = getDefault("PORT", "8080")
|
||||
DSN = getDefault("DSN", "mongodb://localhost:27017")
|
||||
|
||||
log.Printf("• Port=%s\n", Port)
|
||||
log.Printf("• DSN=%s\n", DSN)
|
||||
}
|
||||
|
||||
// Load loads environment variables that are being used across the whole app.
|
||||
// Loading from file(s), i.e .env or dev.env
|
||||
//
|
||||
// Example of a 'dev.env':
|
||||
// PORT=8080
|
||||
// DSN=mongodb://localhost:27017
|
||||
//
|
||||
// After `Load` the callers can get an environment variable via `os.Getenv`.
|
||||
func Load(envFileName string) {
|
||||
if args := os.Args; len(args) > 1 && args[1] == "help" {
|
||||
fmt.Fprintln(os.Stderr, "https://github.com/kataras/iris/blob/master/_examples/database/mongodb/README.md")
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
||||
// If more than one filename passed with comma separated then load from all
|
||||
// of these, a env file can be a partial too.
|
||||
envFiles := strings.Split(envFileName, ",")
|
||||
for _, envFile := range envFiles {
|
||||
if filepath.Ext(envFile) == "" {
|
||||
envFile += ".env"
|
||||
}
|
||||
|
||||
if fileExists(envFile) {
|
||||
log.Printf("Loading environment variables from file: %s\n", envFile)
|
||||
|
||||
if err := godotenv.Load(envFile); err != nil {
|
||||
panic(fmt.Sprintf("error loading environment variables from [%s]: %v", envFile, err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// envMap, _ := godotenv.Read(envFiles...)
|
||||
// for k, v := range envMap {
|
||||
// log.Printf("◽ %s=%s\n", k, v)
|
||||
// }
|
||||
|
||||
parse()
|
||||
}
|
||||
|
||||
func getDefault(key string, def string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
os.Setenv(key, def)
|
||||
value = def
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
func fileExists(filename string) bool {
|
||||
info, err := os.Stat(filename)
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return !info.IsDir()
|
||||
}
|
||||
9
_examples/database/mongodb/go.mod
Normal file
9
_examples/database/mongodb/go.mod
Normal file
@@ -0,0 +1,9 @@
|
||||
module myapp
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/kataras/iris/v12 v12.2.0
|
||||
go.mongodb.org/mongo-driver v1.3.4
|
||||
)
|
||||
130
_examples/database/mongodb/httputil/error.go
Normal file
130
_examples/database/mongodb/httputil/error.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"runtime"
|
||||
"runtime/debug"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
)
|
||||
|
||||
var validStackFuncs = []func(string) bool{
|
||||
func(file string) bool {
|
||||
return strings.Contains(file, "/mongodb/api/")
|
||||
},
|
||||
}
|
||||
|
||||
// RuntimeCallerStack returns the app's `file:line` stacktrace
|
||||
// to give more information about an error cause.
|
||||
func RuntimeCallerStack() (s string) {
|
||||
var pcs [10]uintptr
|
||||
n := runtime.Callers(1, pcs[:])
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
for _, fn := range validStackFuncs {
|
||||
if fn(frame.File) {
|
||||
s += fmt.Sprintf("\n\t\t\t%s:%d", frame.File, frame.Line)
|
||||
}
|
||||
}
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// HTTPError describes an HTTP error.
|
||||
type HTTPError struct {
|
||||
error
|
||||
Stack string `json:"-"` // the whole stacktrace.
|
||||
CallerStack string `json:"-"` // the caller, file:lineNumber
|
||||
When time.Time `json:"-"` // the time that the error occurred.
|
||||
// ErrorCode int: maybe a collection of known error codes.
|
||||
StatusCode int `json:"statusCode"`
|
||||
// could be named as "reason" as well
|
||||
// it's the message of the error.
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
func newError(statusCode int, err error, format string, args ...interface{}) HTTPError {
|
||||
if format == "" {
|
||||
format = http.StatusText(statusCode)
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf(format, args...)
|
||||
if err == nil {
|
||||
err = errors.New(desc)
|
||||
}
|
||||
|
||||
return HTTPError{
|
||||
err,
|
||||
string(debug.Stack()),
|
||||
RuntimeCallerStack(),
|
||||
time.Now(),
|
||||
statusCode,
|
||||
desc,
|
||||
}
|
||||
}
|
||||
|
||||
func (err HTTPError) writeHeaders(ctx iris.Context) {
|
||||
ctx.StatusCode(err.StatusCode)
|
||||
ctx.Header("X-Content-Type-Options", "nosniff")
|
||||
}
|
||||
|
||||
// LogFailure will print out the failure to the "logger".
|
||||
func LogFailure(logger io.Writer, ctx iris.Context, err HTTPError) {
|
||||
timeFmt := err.When.Format("2006/01/02 15:04:05")
|
||||
firstLine := fmt.Sprintf("%s %s: %s", timeFmt, http.StatusText(err.StatusCode), err.Error())
|
||||
whitespace := strings.Repeat(" ", len(timeFmt)+1)
|
||||
fmt.Fprintf(logger, "%s\n%sIP: %s\n%sURL: %s\n%sSource: %s\n",
|
||||
firstLine, whitespace, ctx.RemoteAddr(), whitespace, ctx.FullRequestURI(), whitespace, err.CallerStack)
|
||||
}
|
||||
|
||||
// Fail will send the status code, write the error's reason
|
||||
// and return the HTTPError for further use, i.e logging, see `InternalServerError`.
|
||||
func Fail(ctx iris.Context, statusCode int, err error, format string, args ...interface{}) HTTPError {
|
||||
httpErr := newError(statusCode, err, format, args...)
|
||||
httpErr.writeHeaders(ctx)
|
||||
|
||||
ctx.WriteString(httpErr.Description)
|
||||
return httpErr
|
||||
}
|
||||
|
||||
// FailJSON will send to the client the error data as JSON.
|
||||
// Useful for APIs.
|
||||
func FailJSON(ctx iris.Context, statusCode int, err error, format string, args ...interface{}) HTTPError {
|
||||
httpErr := newError(statusCode, err, format, args...)
|
||||
httpErr.writeHeaders(ctx)
|
||||
|
||||
ctx.JSON(httpErr)
|
||||
|
||||
return httpErr
|
||||
}
|
||||
|
||||
// InternalServerError logs to the server's terminal
|
||||
// and dispatches to the client the 500 Internal Server Error.
|
||||
// Internal Server errors are critical, so we log them to the `os.Stderr`.
|
||||
func InternalServerError(ctx iris.Context, err error, format string, args ...interface{}) {
|
||||
LogFailure(os.Stderr, ctx, Fail(ctx, iris.StatusInternalServerError, err, format, args...))
|
||||
}
|
||||
|
||||
// InternalServerErrorJSON acts exactly like `InternalServerError` but instead it sends the data as JSON.
|
||||
// Useful for APIs.
|
||||
func InternalServerErrorJSON(ctx iris.Context, err error, format string, args ...interface{}) {
|
||||
LogFailure(os.Stderr, ctx, FailJSON(ctx, iris.StatusInternalServerError, err, format, args...))
|
||||
}
|
||||
|
||||
// UnauthorizedJSON sends JSON format of StatusUnauthorized(401) HTTPError value.
|
||||
func UnauthorizedJSON(ctx iris.Context, err error, format string, args ...interface{}) HTTPError {
|
||||
return FailJSON(ctx, iris.StatusUnauthorized, err, format, args...)
|
||||
}
|
||||
83
_examples/database/mongodb/main.go
Normal file
83
_examples/database/mongodb/main.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
// go get -u go.mongodb.org/mongo-driver
|
||||
// go get -u github.com/joho/godotenv
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
// APIs
|
||||
storeapi "myapp/api/store"
|
||||
|
||||
//
|
||||
"myapp/env"
|
||||
"myapp/store"
|
||||
|
||||
"github.com/kataras/iris/v12"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
const version = "0.0.1"
|
||||
|
||||
func init() {
|
||||
envFileName := ".env"
|
||||
|
||||
flagset := flag.CommandLine
|
||||
flagset.StringVar(&envFileName, "env", envFileName, "the env file which web app will use to extract its environment variables")
|
||||
flagset.Parse(os.Args[1:])
|
||||
|
||||
env.Load(envFileName)
|
||||
}
|
||||
|
||||
func main() {
|
||||
clientOptions := options.Client().SetHosts([]string{env.DSN})
|
||||
client, err := mongo.Connect(context.Background(), clientOptions)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = client.Ping(context.Background(), nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer client.Disconnect(context.TODO())
|
||||
|
||||
db := client.Database("store")
|
||||
|
||||
var (
|
||||
// Collections.
|
||||
moviesCollection = db.Collection("movies")
|
||||
|
||||
// Services.
|
||||
movieService = store.NewMovieService(moviesCollection)
|
||||
)
|
||||
|
||||
app := iris.New()
|
||||
app.Use(func(ctx iris.Context) {
|
||||
ctx.Header("Server", "Iris MongoDB/"+version)
|
||||
ctx.Next()
|
||||
})
|
||||
|
||||
storeAPI := app.Party("/api/store")
|
||||
{
|
||||
movieHandler := storeapi.NewMovieHandler(movieService)
|
||||
storeAPI.Get("/movies", movieHandler.GetAll)
|
||||
storeAPI.Post("/movies", movieHandler.Add)
|
||||
storeAPI.Get("/movies/{id}", movieHandler.Get)
|
||||
storeAPI.Put("/movies/{id}", movieHandler.Update)
|
||||
storeAPI.Delete("/movies/{id}", movieHandler.Delete)
|
||||
}
|
||||
|
||||
// GET: http://localhost:8080/api/store/movies
|
||||
// POST: http://localhost:8080/api/store/movies
|
||||
// GET: http://localhost:8080/api/store/movies/{id}
|
||||
// PUT: http://localhost:8080/api/store/movies/{id}
|
||||
// DELETE: http://localhost:8080/api/store/movies/{id}
|
||||
app.Listen(fmt.Sprintf(":%s", env.Port), iris.WithOptimizations)
|
||||
}
|
||||
180
_examples/database/mongodb/store/movie.go
Normal file
180
_examples/database/mongodb/store/movie.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
// up to you:
|
||||
// "go.mongodb.org/mongo-driver/mongo/options"
|
||||
)
|
||||
|
||||
type Movie struct {
|
||||
ID primitive.ObjectID `json:"_id" bson:"_id"` /* you need the bson:"_id" to be able to retrieve with ID filled */
|
||||
Name string `json:"name"`
|
||||
Cover string `json:"cover"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type MovieService interface {
|
||||
GetAll(ctx context.Context) ([]Movie, error)
|
||||
GetByID(ctx context.Context, id string) (Movie, error)
|
||||
Create(ctx context.Context, m *Movie) error
|
||||
Update(ctx context.Context, id string, m Movie) error
|
||||
Delete(ctx context.Context, id string) error
|
||||
}
|
||||
|
||||
type movieService struct {
|
||||
C *mongo.Collection
|
||||
}
|
||||
|
||||
var _ MovieService = (*movieService)(nil)
|
||||
|
||||
func NewMovieService(collection *mongo.Collection) MovieService {
|
||||
// up to you:
|
||||
// indexOpts := new(options.IndexOptions)
|
||||
// indexOpts.SetName("movieIndex").
|
||||
// SetUnique(true).
|
||||
// SetBackground(true).
|
||||
// SetSparse(true)
|
||||
|
||||
// collection.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||
// Keys: []string{"_id", "name"},
|
||||
// Options: indexOpts,
|
||||
// })
|
||||
|
||||
return &movieService{C: collection}
|
||||
}
|
||||
|
||||
func (s *movieService) GetAll(ctx context.Context) ([]Movie, error) {
|
||||
// Note:
|
||||
// The mongodb's go-driver's docs says that you can pass `nil` to "find all" but this gives NilDocument error,
|
||||
// probably it's a bug or a documentation's mistake, you have to pass `bson.D{}` instead.
|
||||
cur, err := s.C.Find(ctx, bson.D{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer cur.Close(ctx)
|
||||
|
||||
var results []Movie
|
||||
|
||||
for cur.Next(ctx) {
|
||||
if err = cur.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// elem := bson.D{}
|
||||
var elem Movie
|
||||
err = cur.Decode(&elem)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// results = append(results, Movie{ID: elem[0].Value.(primitive.ObjectID)})
|
||||
|
||||
results = append(results, elem)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func matchID(id string) (bson.D, error) {
|
||||
objectID, err := primitive.ObjectIDFromHex(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
filter := bson.D{{Key: "_id", Value: objectID}}
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
var ErrNotFound = errors.New("not found")
|
||||
|
||||
func (s *movieService) GetByID(ctx context.Context, id string) (Movie, error) {
|
||||
var movie Movie
|
||||
filter, err := matchID(id)
|
||||
if err != nil {
|
||||
return movie, err
|
||||
}
|
||||
|
||||
err = s.C.FindOne(ctx, filter).Decode(&movie)
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return movie, ErrNotFound
|
||||
}
|
||||
return movie, err
|
||||
}
|
||||
|
||||
func (s *movieService) Create(ctx context.Context, m *Movie) error {
|
||||
if m.ID.IsZero() {
|
||||
m.ID = primitive.NewObjectID()
|
||||
}
|
||||
|
||||
_, err := s.C.InsertOne(ctx, m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The following doesn't work if you have the `bson:"_id` on Movie.ID field,
|
||||
// therefore we manually generate a new ID (look above).
|
||||
// res, err := ...InsertOne
|
||||
// objectID := res.InsertedID.(primitive.ObjectID)
|
||||
// m.ID = objectID
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *movieService) Update(ctx context.Context, id string, m Movie) error {
|
||||
filter, err := matchID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// update := bson.D{
|
||||
// {Key: "$set", Value: m},
|
||||
// }
|
||||
// ^ this will override all fields, you can do that, depending on your design. but let's check each field:
|
||||
elem := bson.D{}
|
||||
|
||||
if m.Name != "" {
|
||||
elem = append(elem, bson.E{Key: "name", Value: m.Name})
|
||||
}
|
||||
|
||||
if m.Description != "" {
|
||||
elem = append(elem, bson.E{Key: "description", Value: m.Description})
|
||||
}
|
||||
|
||||
if m.Cover != "" {
|
||||
elem = append(elem, bson.E{Key: "cover", Value: m.Cover})
|
||||
}
|
||||
|
||||
update := bson.D{
|
||||
{Key: "$set", Value: elem},
|
||||
}
|
||||
|
||||
_, err = s.C.UpdateOne(ctx, filter, update)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *movieService) Delete(ctx context.Context, id string) error {
|
||||
filter, err := matchID(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = s.C.DeleteOne(ctx, filter)
|
||||
if err != nil {
|
||||
if err == mongo.ErrNoDocuments {
|
||||
return ErrNotFound
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user