diff --git a/api/handlers/api.go b/api/handlers/api.go index 31722e3..8a4f08b 100644 --- a/api/handlers/api.go +++ b/api/handlers/api.go @@ -41,6 +41,7 @@ func (a *App) New() *mux.Router { ev := EmsVehicle{DB: databases.NewEmsVehicleDatabase(a.dbHelper)} w := Warrant{DB: databases.NewWarrantDatabase(a.dbHelper)} call := Call{DB: databases.NewCallDatabase(a.dbHelper)} + s := Spotlight{DB: databases.NewSpotlightDatabase(a.dbHelper)} // healthchex r.HandleFunc("/health", healthCheckHandler) @@ -91,6 +92,8 @@ func (a *App) New() *mux.Router { apiCreate.Handle("/calls", api.Middleware(http.HandlerFunc(call.CallHandler))).Methods("GET") apiCreate.Handle("/calls/community/{community_id}", api.Middleware(http.HandlerFunc(call.CallsByCommunityIDHandler))).Methods("GET") + apiCreate.Handle("/spotlight", api.Middleware(http.HandlerFunc(s.SpotlightHandler))).Methods("GET") + // swagger docs hosted at "/" r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("./docs/")))) return r diff --git a/api/handlers/spotlight.go b/api/handlers/spotlight.go new file mode 100644 index 0000000..6c0146d --- /dev/null +++ b/api/handlers/spotlight.go @@ -0,0 +1,79 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + + "github.com/gorilla/mux" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.mongodb.org/mongo-driver/mongo/options" + "go.uber.org/zap" + + "github.com/linesmerrill/police-cad-api/config" + "github.com/linesmerrill/police-cad-api/databases" + "github.com/linesmerrill/police-cad-api/models" +) + +// Spotlight exported for testing purposes +type Spotlight struct { + DB databases.SpotlightDatabase +} + +// SpotlightHandler returns all spotlights +func (e Spotlight) SpotlightHandler(w http.ResponseWriter, r *http.Request) { + Limit, err := strconv.Atoi(r.URL.Query().Get("limit")) + if err != nil { + zap.S().Warnf(fmt.Sprintf("limit not set, using default of %v, err: %v", Limit|10, err)) + } + limit64 := int64(Limit) + Page = getPage(Page, r) + skip64 := int64(Page * Limit) + dbResp, err := e.DB.Find(context.TODO(), bson.D{}, &options.FindOptions{Limit: &limit64, Skip: &skip64}) + if err != nil { + config.ErrorStatus("failed to get spotlight", http.StatusNotFound, w, err) + return + } + // Because the frontend requires that the data elements inside models.Spotlight exist, if + // len == 0 then we will just return an empty data object + if len(dbResp) == 0 { + dbResp = []models.Spotlight{} + } + b, err := json.Marshal(dbResp) + if err != nil { + config.ErrorStatus("failed to marshal response", http.StatusInternalServerError, w, err) + return + } + w.WriteHeader(http.StatusOK) + w.Write(b) +} + +// SpotlightByIDHandler returns a spotlight by ID +func (e Spotlight) SpotlightByIDHandler(w http.ResponseWriter, r *http.Request) { + spotlightID := mux.Vars(r)["spotlight_id"] + + zap.S().Debugf("spotlight_id: %v", spotlightID) + + cID, err := primitive.ObjectIDFromHex(spotlightID) + if err != nil { + config.ErrorStatus("failed to get objectID from Hex", http.StatusBadRequest, w, err) + return + } + + dbResp, err := e.DB.FindOne(context.Background(), bson.M{"_id": cID}) + if err != nil { + config.ErrorStatus("failed to get spotlight by ID", http.StatusNotFound, w, err) + return + } + + b, err := json.Marshal(dbResp) + if err != nil { + config.ErrorStatus("failed to marshal response", http.StatusInternalServerError, w, err) + return + } + w.WriteHeader(http.StatusOK) + w.Write(b) +} diff --git a/databases/mocks/SpotlightDatabase.go b/databases/mocks/SpotlightDatabase.go new file mode 100644 index 0000000..54cfc08 --- /dev/null +++ b/databases/mocks/SpotlightDatabase.go @@ -0,0 +1,78 @@ +// Code generated by mockery v2.10.0. DO NOT EDIT. + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + + models "github.com/linesmerrill/police-cad-api/models" + + options "go.mongodb.org/mongo-driver/mongo/options" +) + +// SpotlightDatabase is an autogenerated mock type for the SpotlightDatabase type +type SpotlightDatabase struct { + mock.Mock +} + +// Find provides a mock function with given fields: _a0, _a1, _a2 +func (_m *SpotlightDatabase) Find(_a0 context.Context, _a1 interface{}, _a2 ...*options.FindOptions) ([]models.Spotlight, error) { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0, _a1) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 []models.Spotlight + if rf, ok := ret.Get(0).(func(context.Context, interface{}, ...*options.FindOptions) []models.Spotlight); ok { + r0 = rf(_a0, _a1, _a2...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.Spotlight) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, interface{}, ...*options.FindOptions) error); ok { + r1 = rf(_a0, _a1, _a2...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindOne provides a mock function with given fields: _a0, _a1, _a2 +func (_m *SpotlightDatabase) FindOne(_a0 context.Context, _a1 interface{}, _a2 ...*options.FindOneOptions) (*models.Spotlight, error) { + _va := make([]interface{}, len(_a2)) + for _i := range _a2 { + _va[_i] = _a2[_i] + } + var _ca []interface{} + _ca = append(_ca, _a0, _a1) + _ca = append(_ca, _va...) + ret := _m.Called(_ca...) + + var r0 *models.Spotlight + if rf, ok := ret.Get(0).(func(context.Context, interface{}, ...*options.FindOneOptions) *models.Spotlight); ok { + r0 = rf(_a0, _a1, _a2...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Spotlight) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(context.Context, interface{}, ...*options.FindOneOptions) error); ok { + r1 = rf(_a0, _a1, _a2...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/databases/spotlight.go b/databases/spotlight.go new file mode 100644 index 0000000..3ca2813 --- /dev/null +++ b/databases/spotlight.go @@ -0,0 +1,47 @@ +package databases + +// go generate: mockery --name SpotlightDatabase + +import ( + "context" + + "github.com/linesmerrill/police-cad-api/models" + "go.mongodb.org/mongo-driver/mongo/options" +) + +const spotlightName = "spotlight" + +// SpotlightDatabase contains the methods to use with the spotlight database +type SpotlightDatabase interface { + FindOne(context.Context, interface{}, ...*options.FindOneOptions) (*models.Spotlight, error) + Find(context.Context, interface{}, ...*options.FindOptions) ([]models.Spotlight, error) +} + +type spotlightDatabase struct { + db DatabaseHelper +} + +// NewSpotlightDatabase initializes a new instance of user database with the provided db connection +func NewSpotlightDatabase(db DatabaseHelper) SpotlightDatabase { + return &spotlightDatabase{ + db: db, + } +} + +func (c *spotlightDatabase) FindOne(ctx context.Context, filter interface{}, opts ...*options.FindOneOptions) (*models.Spotlight, error) { + spotlight := &models.Spotlight{} + err := c.db.Collection(spotlightName).FindOne(ctx, filter, opts...).Decode(&spotlight) + if err != nil { + return nil, err + } + return spotlight, nil +} + +func (c *spotlightDatabase) Find(ctx context.Context, filter interface{}, opts ...*options.FindOptions) ([]models.Spotlight, error) { + var spotlight []models.Spotlight + err := c.db.Collection(spotlightName).Find(ctx, filter, opts...).Decode(&spotlight) + if err != nil { + return nil, err + } + return spotlight, nil +} diff --git a/models/spotlight.go b/models/spotlight.go new file mode 100644 index 0000000..a8c0811 --- /dev/null +++ b/models/spotlight.go @@ -0,0 +1,18 @@ +package models + +// Spotlight holds the structure for the spotlight collection in mongo +type Spotlight struct { + ID string `json:"_id" bson:"_id"` + Details SpotlightDetails `json:"spotlight" bson:"spotlight"` + Version int32 `json:"__v" bson:"__v"` +} + +// SpotlightDetails holds the structure for the inner user structure as +// defined in the spotlight collection in mongo +type SpotlightDetails struct { + Image string `json:"image" bson:"image"` + Title string `json:"title" bson:"title"` + Time string `json:"time" bson:"time"` + CreatedAt interface{} `json:"createdAt" bson:"createdAt"` + UpdatedAt interface{} `json:"updatedAt" bson:"updatedAt"` +}