mirror of
https://github.com/navidrome/navidrome.git
synced 2026-04-03 06:41:01 +00:00
feat(plugins UI): add plugin management routes and middleware
Signed-off-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
52c3985508
commit
9c626183d0
@ -72,6 +72,7 @@ func (api *Router) routes() http.Handler {
|
||||
api.addInspectRoute(r)
|
||||
api.addConfigRoute(r)
|
||||
api.addUserLibraryRoute(r)
|
||||
api.addPluginRoute(r)
|
||||
api.RX(r, "/library", api.libs.NewRepository, true)
|
||||
})
|
||||
})
|
||||
|
||||
112
server/nativeapi/plugin.go
Normal file
112
server/nativeapi/plugin.go
Normal file
@ -0,0 +1,112 @@
|
||||
package nativeapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
)
|
||||
|
||||
func (api *Router) addPluginRoute(r chi.Router) {
|
||||
constructor := func(ctx context.Context) rest.Repository {
|
||||
return api.ds.Plugin(ctx)
|
||||
}
|
||||
|
||||
r.Route("/plugin", func(r chi.Router) {
|
||||
r.Use(pluginsEnabledMiddleware)
|
||||
r.Get("/", rest.GetAll(constructor))
|
||||
r.Route("/{id}", func(r chi.Router) {
|
||||
r.Use(server.URLParamsMiddleware)
|
||||
r.Get("/", rest.Get(constructor))
|
||||
r.Put("/", api.updatePlugin)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Middleware to check if plugins feature is enabled
|
||||
func pluginsEnabledMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !conf.Server.Plugins.Enabled {
|
||||
http.Error(w, "Not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// PluginUpdateRequest represents the fields that can be updated via the API
|
||||
type PluginUpdateRequest struct {
|
||||
Enabled *bool `json:"enabled,omitempty"`
|
||||
Config *string `json:"config,omitempty"`
|
||||
}
|
||||
|
||||
func (api *Router) updatePlugin(w http.ResponseWriter, r *http.Request) {
|
||||
id := chi.URLParam(r, "id")
|
||||
repo := api.ds.Plugin(r.Context())
|
||||
|
||||
// Get existing plugin
|
||||
plugin, err := repo.Get(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, rest.ErrPermissionDenied) {
|
||||
http.Error(w, "Access denied: admin privileges required", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
http.Error(w, "Plugin not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
log.Error(r.Context(), "Error getting plugin", "id", id, err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse update request
|
||||
var req PluginUpdateRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
log.Error(r.Context(), "Error decoding request", err)
|
||||
http.Error(w, "Invalid request body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Apply updates
|
||||
if req.Enabled != nil {
|
||||
plugin.Enabled = *req.Enabled
|
||||
}
|
||||
if req.Config != nil {
|
||||
// Validate JSON if not empty
|
||||
if *req.Config != "" && !isValidJSON(*req.Config) {
|
||||
http.Error(w, "Invalid JSON in config field", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
plugin.Config = *req.Config
|
||||
}
|
||||
|
||||
// Save
|
||||
if err := repo.Put(plugin); err != nil {
|
||||
if errors.Is(err, rest.ErrPermissionDenied) {
|
||||
http.Error(w, "Access denied: admin privileges required", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
log.Error(r.Context(), "Error updating plugin", "id", id, err)
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(plugin); err != nil {
|
||||
log.Error(r.Context(), "Error encoding plugin response", err)
|
||||
}
|
||||
}
|
||||
|
||||
// isValidJSON checks if a string is valid JSON
|
||||
func isValidJSON(s string) bool {
|
||||
var js json.RawMessage
|
||||
return json.Unmarshal([]byte(s), &js) == nil
|
||||
}
|
||||
298
server/nativeapi/plugin_test.go
Normal file
298
server/nativeapi/plugin_test.go
Normal file
@ -0,0 +1,298 @@
|
||||
package nativeapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/conf/configtest"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/core"
|
||||
"github.com/navidrome/navidrome/core/auth"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/navidrome/navidrome/model/request"
|
||||
"github.com/navidrome/navidrome/server"
|
||||
"github.com/navidrome/navidrome/tests"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Plugin API", func() {
|
||||
var ds *tests.MockDataStore
|
||||
var router http.Handler
|
||||
var adminUser, regularUser model.User
|
||||
var testPlugin1, testPlugin2 model.Plugin
|
||||
|
||||
BeforeEach(func() {
|
||||
DeferCleanup(configtest.SetupConfig())
|
||||
conf.Server.Plugins.Enabled = true
|
||||
ds = &tests.MockDataStore{}
|
||||
auth.Init(ds)
|
||||
nativeRouter := New(ds, nil, nil, nil, core.NewMockLibraryService(), nil)
|
||||
router = server.JWTVerifier(nativeRouter)
|
||||
|
||||
// Create test users
|
||||
adminUser = model.User{
|
||||
ID: "admin-1",
|
||||
UserName: "admin",
|
||||
Name: "Admin User",
|
||||
IsAdmin: true,
|
||||
NewPassword: "adminpass",
|
||||
}
|
||||
regularUser = model.User{
|
||||
ID: "user-1",
|
||||
UserName: "regular",
|
||||
Name: "Regular User",
|
||||
IsAdmin: false,
|
||||
NewPassword: "userpass",
|
||||
}
|
||||
|
||||
// Create test plugins
|
||||
testPlugin1 = model.Plugin{
|
||||
ID: "test-plugin-1",
|
||||
Path: "/plugins/test1.wasm",
|
||||
Manifest: `{"name":"Test Plugin 1","version":"1.0.0"}`,
|
||||
SHA256: "abc123",
|
||||
Enabled: false,
|
||||
}
|
||||
testPlugin2 = model.Plugin{
|
||||
ID: "test-plugin-2",
|
||||
Path: "/plugins/test2.wasm",
|
||||
Manifest: `{"name":"Test Plugin 2","version":"2.0.0"}`,
|
||||
Config: `{"setting":"value"}`,
|
||||
SHA256: "def456",
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// Store users in mock datastore
|
||||
Expect(ds.User(GinkgoT().Context()).Put(&adminUser)).To(Succeed())
|
||||
Expect(ds.User(GinkgoT().Context()).Put(®ularUser)).To(Succeed())
|
||||
})
|
||||
|
||||
Context("when plugins are disabled", func() {
|
||||
BeforeEach(func() {
|
||||
conf.Server.Plugins.Enabled = false
|
||||
})
|
||||
|
||||
It("returns 404 for all plugin endpoints", func() {
|
||||
adminToken, err := auth.CreateToken(&adminUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
req := httptest.NewRequest("GET", "/plugin", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when plugins are enabled", func() {
|
||||
Describe("as admin user", func() {
|
||||
var adminToken string
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
adminToken, err = auth.CreateToken(&adminUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
// Store test plugins as admin
|
||||
ctx := GinkgoT().Context()
|
||||
adminCtx := request.WithUser(ctx, adminUser)
|
||||
Expect(ds.Plugin(adminCtx).Put(&testPlugin1)).To(Succeed())
|
||||
Expect(ds.Plugin(adminCtx).Put(&testPlugin2)).To(Succeed())
|
||||
})
|
||||
|
||||
Describe("GET /api/plugin", func() {
|
||||
It("returns all plugins", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
|
||||
var plugins []model.Plugin
|
||||
err := json.Unmarshal(w.Body.Bytes(), &plugins)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(plugins).To(HaveLen(2))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("GET /api/plugin/{id}", func() {
|
||||
It("returns a specific plugin", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin/test-plugin-1", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
|
||||
var plugin model.Plugin
|
||||
err := json.Unmarshal(w.Body.Bytes(), &plugin)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(plugin.ID).To(Equal("test-plugin-1"))
|
||||
Expect(plugin.Path).To(Equal("/plugins/test1.wasm"))
|
||||
})
|
||||
|
||||
It("returns 404 for non-existent plugin", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin/non-existent", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("PUT /api/plugin/{id}", func() {
|
||||
It("updates plugin enabled state", func() {
|
||||
body := bytes.NewBufferString(`{"enabled":true}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
|
||||
var plugin model.Plugin
|
||||
err := json.Unmarshal(w.Body.Bytes(), &plugin)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(plugin.Enabled).To(BeTrue())
|
||||
})
|
||||
|
||||
It("updates plugin config with valid JSON", func() {
|
||||
body := bytes.NewBufferString(`{"config":"{\"key\":\"value\"}"}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
|
||||
var plugin model.Plugin
|
||||
err := json.Unmarshal(w.Body.Bytes(), &plugin)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(plugin.Config).To(Equal(`{"key":"value"}`))
|
||||
})
|
||||
|
||||
It("rejects invalid JSON in config field", func() {
|
||||
body := bytes.NewBufferString(`{"config":"not valid json"}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
Expect(w.Body.String()).To(ContainSubstring("Invalid JSON"))
|
||||
})
|
||||
|
||||
It("allows empty config", func() {
|
||||
body := bytes.NewBufferString(`{"config":""}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusOK))
|
||||
|
||||
var plugin model.Plugin
|
||||
err := json.Unmarshal(w.Body.Bytes(), &plugin)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(plugin.Config).To(Equal(""))
|
||||
})
|
||||
|
||||
It("returns 404 for non-existent plugin", func() {
|
||||
body := bytes.NewBufferString(`{"enabled":true}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/non-existent", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusNotFound))
|
||||
})
|
||||
|
||||
It("returns 400 for invalid request body", func() {
|
||||
body := bytes.NewBufferString(`not json`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+adminToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusBadRequest))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("as regular user", func() {
|
||||
var userToken string
|
||||
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
userToken, err = auth.CreateToken(®ularUser)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
})
|
||||
|
||||
It("denies access to GET /api/plugin", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+userToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusForbidden))
|
||||
})
|
||||
|
||||
It("denies access to GET /api/plugin/{id}", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin/test-plugin-1", nil)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+userToken)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusForbidden))
|
||||
})
|
||||
|
||||
It("denies access to PUT /api/plugin/{id}", func() {
|
||||
body := bytes.NewBufferString(`{"enabled":true}`)
|
||||
req := httptest.NewRequest("PUT", "/plugin/test-plugin-1", body)
|
||||
req.Header.Set(consts.UIAuthorizationHeader, "Bearer "+userToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusForbidden))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("without authentication", func() {
|
||||
It("denies access to plugin endpoints", func() {
|
||||
req := httptest.NewRequest("GET", "/plugin", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
router.ServeHTTP(w, req)
|
||||
|
||||
Expect(w.Code).To(Equal(http.StatusUnauthorized))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@ -243,7 +243,7 @@ func (db *MockDataStore) Plugin(ctx context.Context) model.PluginRepository {
|
||||
if db.RealDS != nil {
|
||||
db.MockedPlugin = db.RealDS.Plugin(ctx)
|
||||
} else {
|
||||
db.MockedPlugin = struct{ model.PluginRepository }{}
|
||||
db.MockedPlugin = CreateMockPluginRepo()
|
||||
}
|
||||
}
|
||||
return db.MockedPlugin
|
||||
|
||||
174
tests/mock_plugin_repo.go
Normal file
174
tests/mock_plugin_repo.go
Normal file
@ -0,0 +1,174 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/deluan/rest"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
)
|
||||
|
||||
func CreateMockPluginRepo() *MockPluginRepo {
|
||||
return &MockPluginRepo{
|
||||
Data: make(map[string]*model.Plugin),
|
||||
IsAdmin: true, // Default to admin access
|
||||
Permitted: true,
|
||||
}
|
||||
}
|
||||
|
||||
type MockPluginRepo struct {
|
||||
Data map[string]*model.Plugin
|
||||
All model.Plugins
|
||||
Err bool
|
||||
Options model.QueryOptions
|
||||
IsAdmin bool
|
||||
Permitted bool
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) SetError(err bool) {
|
||||
m.Err = err
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) SetData(plugins model.Plugins) {
|
||||
m.Data = make(map[string]*model.Plugin, len(plugins))
|
||||
m.All = plugins
|
||||
for i, p := range m.All {
|
||||
m.Data[p.ID] = &m.All[i]
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) SetPermitted(permitted bool) {
|
||||
m.Permitted = permitted
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Get(id string) (*model.Plugin, error) {
|
||||
if !m.Permitted {
|
||||
return nil, rest.ErrPermissionDenied
|
||||
}
|
||||
if m.Err {
|
||||
return nil, errors.New("unexpected error")
|
||||
}
|
||||
if d, ok := m.Data[id]; ok {
|
||||
return d, nil
|
||||
}
|
||||
return nil, model.ErrNotFound
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Read(id string) (interface{}, error) {
|
||||
p, err := m.Get(id)
|
||||
if errors.Is(err, model.ErrNotFound) {
|
||||
return nil, rest.ErrNotFound
|
||||
}
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Put(p *model.Plugin) error {
|
||||
if !m.Permitted {
|
||||
return rest.ErrPermissionDenied
|
||||
}
|
||||
if m.Err {
|
||||
return errors.New("unexpected error")
|
||||
}
|
||||
if p.ID == "" {
|
||||
return errors.New("plugin ID cannot be empty")
|
||||
}
|
||||
now := time.Now()
|
||||
if existing, ok := m.Data[p.ID]; ok {
|
||||
p.CreatedAt = existing.CreatedAt
|
||||
} else {
|
||||
p.CreatedAt = now
|
||||
}
|
||||
p.UpdatedAt = now
|
||||
m.Data[p.ID] = p
|
||||
// Update All slice
|
||||
found := false
|
||||
for i, existing := range m.All {
|
||||
if existing.ID == p.ID {
|
||||
m.All[i] = *p
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
m.All = append(m.All, *p)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Delete(id string) error {
|
||||
if !m.Permitted {
|
||||
return rest.ErrPermissionDenied
|
||||
}
|
||||
if m.Err {
|
||||
return errors.New("unexpected error")
|
||||
}
|
||||
delete(m.Data, id)
|
||||
// Update All slice
|
||||
for i, p := range m.All {
|
||||
if p.ID == id {
|
||||
m.All = append(m.All[:i], m.All[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) GetAll(qo ...model.QueryOptions) (model.Plugins, error) {
|
||||
if len(qo) > 0 {
|
||||
m.Options = qo[0]
|
||||
}
|
||||
if !m.Permitted {
|
||||
return nil, rest.ErrPermissionDenied
|
||||
}
|
||||
if m.Err {
|
||||
return nil, errors.New("unexpected error")
|
||||
}
|
||||
return m.All, nil
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) CountAll(qo ...model.QueryOptions) (int64, error) {
|
||||
if len(qo) > 0 {
|
||||
m.Options = qo[0]
|
||||
}
|
||||
if !m.Permitted {
|
||||
return 0, rest.ErrPermissionDenied
|
||||
}
|
||||
if m.Err {
|
||||
return 0, errors.New("unexpected error")
|
||||
}
|
||||
return int64(len(m.All)), nil
|
||||
}
|
||||
|
||||
// rest.Repository interface methods
|
||||
func (m *MockPluginRepo) Count(options ...rest.QueryOptions) (int64, error) {
|
||||
if !m.Permitted {
|
||||
return 0, rest.ErrPermissionDenied
|
||||
}
|
||||
return int64(len(m.All)), nil
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) EntityName() string {
|
||||
return "plugin"
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) NewInstance() interface{} {
|
||||
return &model.Plugin{}
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) ReadAll(options ...rest.QueryOptions) (interface{}, error) {
|
||||
return m.GetAll()
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Save(entity interface{}) (string, error) {
|
||||
p := entity.(*model.Plugin)
|
||||
err := m.Put(p)
|
||||
return p.ID, err
|
||||
}
|
||||
|
||||
func (m *MockPluginRepo) Update(id string, entity interface{}, cols ...string) error {
|
||||
p := entity.(*model.Plugin)
|
||||
p.ID = id
|
||||
return m.Put(p)
|
||||
}
|
||||
|
||||
var _ model.PluginRepository = (*MockPluginRepo)(nil)
|
||||
Loading…
x
Reference in New Issue
Block a user