diff --git a/CHANGELOG.md b/CHANGELOG.md index abca080..84eec40 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +## [1.14.0] - 2024-10-08 +### Added +- Meta_data dependancies [#125](https://github.com/rokwire/content-building-block/issues/125) + ## [1.13.1] - 2024-10-07 ### Fixed - Fixed environment variable [#127](https://github.com/rokwire/content-building-block/issues/127) diff --git a/SECURITY.md b/SECURITY.md index e9bf6fa..5049831 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,8 +6,8 @@ Patches for **Content Building Block** in this repository will only be applied t | Version | Supported | |----------| ------------------ | -| 1.13.1 | :white_check_mark: | -| < 1.13.1 | :x: | +| 1.14.0 | :white_check_mark: | +| < 1.14.0 | :x: | ## Reporting a Bug or Vulnerability diff --git a/core/interfaces/core.go b/core/interfaces/core.go index c8e66d5..58ea8c6 100644 --- a/core/interfaces/core.go +++ b/core/interfaces/core.go @@ -59,6 +59,9 @@ type Services interface { UpdateDataContentItem(claims *tokenauth.Claims, item *model.DataContentItem) (*model.DataContentItem, error) DeleteDataContentItem(claims *tokenauth.Claims, key string) error GetDataContentItems(claims *tokenauth.Claims, category string) ([]*model.DataContentItem, error) + CreateOrUpdateMetaData(key string, value map[string]interface{}) (*model.MetaData, error) + GetMetaData(key *string) (*model.MetaData, error) + DeleteMetaData(key string) error CreateCategory(claims *tokenauth.Claims, item *model.Category) (*model.Category, error) GetCategory(claims *tokenauth.Claims, name string) (*model.Category, error) diff --git a/core/interfaces/driven.go b/core/interfaces/driven.go index 1dd4f1b..474d063 100644 --- a/core/interfaces/driven.go +++ b/core/interfaces/driven.go @@ -57,6 +57,11 @@ type Storage interface { FindCategory(appID *string, orgID string, name string) (*model.Category, error) UpdateCategory(appID *string, orgID string, item *model.Category) (*model.Category, error) DeleteCategory(appID *string, orgID string, key string) error + + CreateMetaData(key string, value map[string]interface{}) (*model.MetaData, error) + FindMetaData(key *string) (*model.MetaData, error) + UpdateMetaData(item *model.MetaData, value map[string]interface{}) (*model.MetaData, error) + DeleteMetaData(key string) error } // Core BB interface diff --git a/core/model/content.go b/core/model/content.go index 1ac8785..ad9c853 100644 --- a/core/model/content.go +++ b/core/model/content.go @@ -38,3 +38,12 @@ type Category struct { DateUpdated *time.Time `json:"date_updated,omitempty" bson:"date_updated,omitempty"` Permissions []string `json:"permissions" bson:"permissions"` } // @name Category + +// MetaData defines a meta_data object +type MetaData struct { + ID string `json:"id" bson:"_id"` + Key string `json:"key" bson:"key"` + Value map[string]interface{} `json:"value" bson:"value"` + DateCreated time.Time `json:"date_created" bson:"date_created"` + DateUpdated *time.Time `json:"date_updated,omitempty" bson:"date_updated,omitempty"` +} // @name MetaData diff --git a/core/services.go b/core/services.go index 4bada5a..4ab2679 100644 --- a/core/services.go +++ b/core/services.go @@ -430,6 +430,45 @@ func (s *servicesImpl) GetDataContentItems(claims *tokenauth.Claims, category st return item, nil } +func (s *servicesImpl) CreateOrUpdateMetaData(key string, value map[string]interface{}) (*model.MetaData, error) { + var metaData *model.MetaData + + findMetaData, err := s.app.storage.FindMetaData(&key) + if err != nil { + return nil, err + } + if findMetaData == nil { + metaData, err = s.app.storage.CreateMetaData(key, value) + if err != nil { + return nil, err + } + } else { + metaData, err = s.app.storage.UpdateMetaData(findMetaData, value) + if err != nil { + return nil, err + } + } + + return metaData, nil +} + +func (s *servicesImpl) GetMetaData(key *string) (*model.MetaData, error) { + item, err := s.app.storage.FindMetaData(key) + if err != nil { + return nil, err + } + return item, nil +} + +func (s *servicesImpl) DeleteMetaData(key string) error { + err := s.app.storage.DeleteMetaData(key) + if err != nil { + return err + } + + return nil +} + func (s *servicesImpl) CreateDataContentItem(claims *tokenauth.Claims, item *model.DataContentItem) (*model.DataContentItem, error) { category, err := s.app.storage.FindCategory(&claims.AppID, claims.OrgID, item.Category) diff --git a/driven/storage/adapter.go b/driven/storage/adapter.go index 9f9d4ab..22e76d9 100644 --- a/driven/storage/adapter.go +++ b/driven/storage/adapter.go @@ -656,6 +656,74 @@ func (sa *Adapter) StoreMultiTenancyData(appID string, orgID string) error { return nil } +// CreateMetaData creates meta_data object +func (sa *Adapter) CreateMetaData(key string, value map[string]interface{}) (*model.MetaData, error) { + now := time.Now() + id, _ := uuid.NewUUID() + item := model.MetaData{ + ID: id.String(), + Key: key, + Value: value, + DateCreated: now, + } + + _, err := sa.db.metaData.InsertOne(sa.context, &item) + if err != nil { + return nil, err + } + + return &item, nil +} + +// FindMetaData find meta_data object +func (sa *Adapter) FindMetaData(key *string) (*model.MetaData, error) { + filter := bson.D{primitive.E{Key: "key", Value: key}} + + var result *model.MetaData + err := sa.db.metaData.FindOne(sa.context, filter, &result, nil) + if err != nil { + return nil, nil + } + return result, nil +} + +// DeleteMetaData deletes meta_data object +func (sa *Adapter) DeleteMetaData(key string) error { + filter := bson.D{primitive.E{Key: "key", Value: key}} + + result, err := sa.db.metaData.DeleteOne(sa.context, filter, nil) + if err != nil { + return err + } + if result == nil { + return fmt.Errorf("result is nil for meta_data with key %s", key) + } + deletedCount := result.DeletedCount + if deletedCount != 1 { + return fmt.Errorf("error occured while deleting a meta_data with key %s", key) + } + return nil +} + +// UpdateMetaData updates a metaData +func (sa *Adapter) UpdateMetaData(item *model.MetaData, value map[string]interface{}) (*model.MetaData, error) { + filter := bson.D{ + primitive.E{Key: "key", Value: item.Key}} + update := bson.D{ + primitive.E{Key: "$set", Value: bson.D{ + primitive.E{Key: "value", Value: value}, + primitive.E{Key: "date_updated", Value: time.Now().UTC()}, + }}, + } + _, err := sa.db.metaData.UpdateOne(sa.context, filter, update, nil) + if err != nil { + log.Printf("error updating category: %s", err) + return nil, err + } + + return item, nil +} + func (sa *Adapter) abortTransaction(sessionContext mongo.SessionContext) { err := sessionContext.AbortTransaction(sessionContext) if err != nil { diff --git a/driven/storage/database.go b/driven/storage/database.go index 16a941c..c909080 100644 --- a/driven/storage/database.go +++ b/driven/storage/database.go @@ -40,6 +40,7 @@ type database struct { contentItems *collectionWrapper dataContentItems *collectionWrapper categories *collectionWrapper + metaData *collectionWrapper logger *logs.Logger } @@ -98,6 +99,12 @@ func (m *database) start() error { return err } + metaData := &collectionWrapper{database: m, coll: db.Collection("meta_data")} + err = m.applyMetaDataChecks(metaData) + if err != nil { + return err + } + //asign the db, db client and the collections m.db = db m.dbClient = client @@ -107,6 +114,7 @@ func (m *database) start() error { m.contentItems = contentItems m.dataContentItems = dataContentItems m.categories = categories + m.metaData = metaData return nil } @@ -191,6 +199,19 @@ func (m *database) applyCategoriesChecks(categories *collectionWrapper) error { return nil } +func (m *database) applyMetaDataChecks(metaData *collectionWrapper) error { + log.Println("apply meta_data checks.....") + + // Add key index + err := metaData.AddIndex(bson.D{primitive.E{Key: "key", Value: 1}}, false) + if err != nil { + return err + } + + log.Println("meta_data checks passed") + return nil +} + // Event func (m *database) onDataChanged(changeDoc map[string]interface{}) { diff --git a/driver/web/adapter.go b/driver/web/adapter.go index 5c2c4ee..5c92224 100644 --- a/driver/web/adapter.go +++ b/driver/web/adapter.go @@ -116,6 +116,9 @@ func (we Adapter) Start() { //TODO: add /files/upload/multipart for large file uploads contentRouter.HandleFunc("/files/download", we.coreAuthWrapFunc(we.apisHandler.GetFileContentDownloadURLs, we.auth.coreAuth.standardAuth)).Methods("GET") contentRouter.HandleFunc("/data", we.coreAuthWrapFunc(we.apisHandler.GetDataContentItems, we.auth.coreAuth.standardAuth)).Methods("GET") + contentRouter.HandleFunc("/meta-data", we.coreAuthWrapFunc(we.apisHandler.CreateOrUpdateMetaData, we.auth.coreAuth.standardAuth)).Methods("POST") + contentRouter.HandleFunc("/meta-data/{key}", we.coreAuthWrapFunc(we.apisHandler.GetMetaData, we.auth.coreAuth.standardAuth)).Methods("GET") + contentRouter.HandleFunc("/meta-data/{key}", we.coreAuthWrapFunc(we.apisHandler.DeleteMetaData, we.auth.coreAuth.standardAuth)).Methods("DELETE") // handle student guide admin apis adminSubRouter := contentRouter.PathPrefix("/admin").Subrouter() diff --git a/driver/web/docs/gen/def.yaml b/driver/web/docs/gen/def.yaml index 9add459..fa8719a 100644 --- a/driver/web/docs/gen/def.yaml +++ b/driver/web/docs/gen/def.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Rokwire Content Building Block API description: Rokwire Content Block API Documentation - version: 1.13.1 + version: 1.14.0 servers: - url: 'https://api.rokwire.illinois.edu/content' description: Production server @@ -2891,6 +2891,99 @@ paths: description: Unauthorized '500': description: Internal error + /meta_data: + post: + tags: + - Client + summary: Create meta data + description: | + Create meta data + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + required: + - key + - data + type: object + properties: + key: + type: string + value: + type: object + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/MetaData' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + '/meta_data/{key}': + get: + tags: + - Client + summary: Retrieves a health location by id + description: | + Retrieves a health location by id + security: + - bearerAuth: [] + parameters: + - name: key + in: path + description: the key of the meta_data + required: false + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/MetaData' + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error + delete: + tags: + - Client + summary: Delete meta data object + description: | + Delete meta data object + security: + - bearerAuth: [] + parameters: + - name: key + in: path + description: id + required: true + style: simple + explode: false + schema: + type: string + responses: + '200': + description: Success + '400': + description: Bad request + '401': + description: Unauthorized + '500': + description: Internal error /files/upload: get: tags: @@ -3155,6 +3248,22 @@ components: type: string app_id: type: string + MetaData: + required: + - key + - data + type: object + properties: + id: + type: string + key: + type: string + value: + type: object + date_created: + type: string + date_updated: + type: string DataContentItem: type: object properties: diff --git a/driver/web/docs/index.yaml b/driver/web/docs/index.yaml index 71fbbb6..4612f87 100644 --- a/driver/web/docs/index.yaml +++ b/driver/web/docs/index.yaml @@ -2,7 +2,7 @@ openapi: 3.0.3 info: title: Rokwire Content Building Block API description: Rokwire Content Block API Documentation - version: 1.13.1 + version: 1.14.0 servers: - url: 'https://api.rokwire.illinois.edu/content' description: Production server @@ -110,7 +110,11 @@ paths: /data/{key}: $ref: "./resources/client/data-content-itemsids.yaml" /files: - $ref: "./resources/client/file-content-items.yaml" + $ref: "./resources/client/file-content-items.yaml" + /meta_data: + $ref: "./resources/client/meta-data.yaml" + /meta_data/{key}: + $ref: "./resources/client/meta-data-id.yaml" /files/upload: $ref: "./resources/client/file-content-upload.yaml" /files/download: diff --git a/driver/web/docs/resources/client/meta-data-id.yaml b/driver/web/docs/resources/client/meta-data-id.yaml new file mode 100644 index 0000000..9049a9d --- /dev/null +++ b/driver/web/docs/resources/client/meta-data-id.yaml @@ -0,0 +1,56 @@ +get: + tags: + - Client + summary: Retrieves a health location by id + description: | + Retrieves a health location by id + security: + - bearerAuth: [] + parameters: + - name: key + in: path + description: the key of the meta_data + required: false + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/MetaData.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error +delete: + tags: + - Client + summary: Delete meta data object + description: | + Delete meta data object + security: + - bearerAuth: [] + parameters: + - name: key + in: path + description: id + required: true + style: simple + explode: false + schema: + type: string + responses: + 200: + description: Success + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error \ No newline at end of file diff --git a/driver/web/docs/resources/client/meta-data.yaml b/driver/web/docs/resources/client/meta-data.yaml new file mode 100644 index 0000000..3080cbc --- /dev/null +++ b/driver/web/docs/resources/client/meta-data.yaml @@ -0,0 +1,27 @@ +post: + tags: + - Client + summary: Create meta data + description: | + Create meta data + security: + - bearerAuth: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: "../../schemas/apis/client/meta-data/request/Request.yaml" + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: "../../schemas/application/MetaData.yaml" + 400: + description: Bad request + 401: + description: Unauthorized + 500: + description: Internal error diff --git a/driver/web/docs/schemas/apis/client/meta-data/request/Request.yaml b/driver/web/docs/schemas/apis/client/meta-data/request/Request.yaml new file mode 100644 index 0000000..1f39b01 --- /dev/null +++ b/driver/web/docs/schemas/apis/client/meta-data/request/Request.yaml @@ -0,0 +1,9 @@ +required: + - key + - data +type: object +properties: + key: + type: string + value: + type: object \ No newline at end of file diff --git a/driver/web/docs/schemas/application/MetaData.yaml b/driver/web/docs/schemas/application/MetaData.yaml new file mode 100644 index 0000000..604f261 --- /dev/null +++ b/driver/web/docs/schemas/application/MetaData.yaml @@ -0,0 +1,15 @@ +required: + - key + - data +type: object +properties: + id: + type: string + key: + type: string + value: + type: object + date_created: + type: string + date_updated: + type: string \ No newline at end of file diff --git a/driver/web/docs/schemas/index.yaml b/driver/web/docs/schemas/index.yaml index 7f66f64..60f762e 100644 --- a/driver/web/docs/schemas/index.yaml +++ b/driver/web/docs/schemas/index.yaml @@ -1,9 +1,11 @@ # application ContentItem: $ref: "./application/ContentItem.yaml" +MetaData: + $ref: "./application/MetaData.yaml" DataContentItem: $ref: "./application/DataContentItem.yaml" FileContentItemRef: $ref: "./application/FileContentItemRef.yaml" ImageSpec: - $ref: "./application/ImageSpec.yaml" \ No newline at end of file + $ref: "./application/ImageSpec.yaml" diff --git a/driver/web/rest/apis.go b/driver/web/rest/apis.go index 9f07127..d5e6e15 100644 --- a/driver/web/rest/apis.go +++ b/driver/web/rest/apis.go @@ -1006,6 +1006,87 @@ func (h ApisHandler) GetDataContentItems(claims *tokenauth.Claims, w http.Respon w.Write(data) } +type createOrUpdateMetaDataRequestBody struct { + Key string `json:"key"` + Value map[string]interface{} `json:"value"` +} // @name createMetaDataRequestBody + +// CreateOrUpdateMetaData creates or updates meta data object +func (h ApisHandler) CreateOrUpdateMetaData(claims *tokenauth.Claims, w http.ResponseWriter, r *http.Request) { + var body createOrUpdateMetaDataRequestBody + bodyData, _ := ioutil.ReadAll(r.Body) + if len(bodyData) > 0 { + bodyErr := json.Unmarshal(bodyData, &body) + if bodyErr != nil { + log.Printf("Warning: bad createMetaDataRequestBody request: %s", bodyErr) + } + } + + resData, err := h.app.Services.CreateOrUpdateMetaData(body.Key, body.Value) + if err != nil { + log.Printf("Error on creating meta- data content items with category") + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data, err := json.Marshal(resData) + if err != nil { + log.Println("Error on marshal of data content type") + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(data) +} + +// GetMetaData Gets meta data +func (h ApisHandler) GetMetaData(claims *tokenauth.Claims, w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + key := vars["key"] + var keyPtr *string + if key, ok := vars["key"]; ok { + keyPtr = &key + } + + resData, err := h.app.Services.GetMetaData(keyPtr) + if err != nil { + log.Printf("Error on getting data content type with key - %s\n %s", key, err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + data, err := json.Marshal(resData) + if err != nil { + log.Println("Error on marshal of data content type") + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json; charset=utf-8") + w.WriteHeader(http.StatusOK) + w.Write(data) +} + +// DeleteMetaData deletes meta data by key +func (h ApisHandler) DeleteMetaData(claims *tokenauth.Claims, w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + key := vars["key"] + + err := h.app.Services.DeleteMetaData(key) + if err != nil { + if err != nil { + log.Printf("error on delete meta data: %s", err) + } + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusOK) + w.Write([]byte("Success")) +} + func intPostValueFromString(stringValue string) int { var value int if len(stringValue) > 0 {