🏡 index : quine-global/quintodrome.git

package persistence

import (
	"context"
	"errors"
	"time"

	. "github.com/Masterminds/squirrel"
	"github.com/deluan/rest"
	"github.com/navidrome/navidrome/model"
	"github.com/pocketbase/dbx"
)

type pluginRepository struct {
	sqlRepository
}

func NewPluginRepository(db dbx.Builder) model.PluginRepository {
	r := &pluginRepository{}
	r.db = db
	r.registerModel(&model.Plugin{}, map[string]filterFunc{
		"id":      idFilter("plugin"),
		"enabled": booleanFilter,
	})
	return r
}

func (r *pluginRepository) isPermitted(ctx context.Context) bool {
	user := loggedUser(ctx)
	return user.IsAdmin
}

func (r *pluginRepository) ClearErrors(ctx context.Context) error {
	if !r.isPermitted(ctx) {
		return rest.ErrPermissionDenied
	}
	// An UPDATE takes the write lock even when nothing matches, so only run it when there is an error to clear
	var hasErrors bool
	if err := r.db.NewQuery("SELECT EXISTS (SELECT 1 FROM plugin WHERE last_error != '')").Row(&hasErrors); err != nil {
		return err
	}
	if !hasErrors {
		return nil
	}
	_, err := r.db.NewQuery("UPDATE plugin SET last_error = '' WHERE last_error != ''").Execute()
	return err
}

func (r *pluginRepository) CountAll(ctx context.Context, options ...model.QueryOptions) (int64, error) {
	if !r.isPermitted(ctx) {
		return 0, rest.ErrPermissionDenied
	}
	sql := r.newSelect(ctx)
	return r.count(ctx, sql, options...)
}

func (r *pluginRepository) Delete(ctx context.Context, id string) error {
	if !r.isPermitted(ctx) {
		return rest.ErrPermissionDenied
	}
	return r.delete(ctx, Eq{"id": id})
}

func (r *pluginRepository) Get(ctx context.Context, id string) (*model.Plugin, error) {
	if !r.isPermitted(ctx) {
		return nil, rest.ErrPermissionDenied
	}
	sel := r.newSelect(ctx).Where(Eq{"id": id}).Columns("*")
	res := model.Plugin{}
	err := r.queryOne(ctx, sel, &res)
	return &res, err
}

func (r *pluginRepository) GetAll(ctx context.Context, options ...model.QueryOptions) (model.Plugins, error) {
	if !r.isPermitted(ctx) {
		return nil, rest.ErrPermissionDenied
	}
	sel := r.newSelect(ctx, options...).Columns("*")
	res := model.Plugins{}
	err := r.queryAll(ctx, sel, &res)
	return res, err
}

func (r *pluginRepository) Put(ctx context.Context, plugin *model.Plugin) error {
	if !r.isPermitted(ctx) {
		return rest.ErrPermissionDenied
	}

	plugin.UpdatedAt = time.Now()

	if plugin.ID == "" {
		return errors.New("plugin ID cannot be empty")
	}

	// Upsert using INSERT ... ON CONFLICT for atomic operation
	_, err := r.db.NewQuery(`
		INSERT INTO plugin (id, path, manifest, config, users, all_users, libraries, all_libraries, allow_write_access, enabled, last_error, sha256, created_at, updated_at)
		VALUES ({:id}, {:path}, {:manifest}, {:config}, {:users}, {:all_users}, {:libraries}, {:all_libraries}, {:allow_write_access}, {:enabled}, {:last_error}, {:sha256}, {:created_at}, {:updated_at})
		ON CONFLICT(id) DO UPDATE SET
			path = excluded.path,
			manifest = excluded.manifest,
			config = excluded.config,
			users = excluded.users,
			all_users = excluded.all_users,
			libraries = excluded.libraries,
			all_libraries = excluded.all_libraries,
			allow_write_access = excluded.allow_write_access,
			enabled = excluded.enabled,
			last_error = excluded.last_error,
			sha256 = excluded.sha256,
			updated_at = excluded.updated_at
	`).Bind(dbx.Params{
		"id":                 plugin.ID,
		"path":               plugin.Path,
		"manifest":           plugin.Manifest,
		"config":             plugin.Config,
		"users":              plugin.Users,
		"all_users":          plugin.AllUsers,
		"libraries":          plugin.Libraries,
		"all_libraries":      plugin.AllLibraries,
		"allow_write_access": plugin.AllowWriteAccess,
		"enabled":            plugin.Enabled,
		"last_error":         plugin.LastError,
		"sha256":             plugin.SHA256,
		"created_at":         time.Now(),
		"updated_at":         plugin.UpdatedAt,
	}).Execute()
	return err
}

func (r *pluginRepository) Count(ctx context.Context, options ...rest.QueryOptions) (int64, error) {
	return r.CountAll(ctx, r.parseRestOptions(ctx, options...))
}

func (r *pluginRepository) Read(ctx context.Context, id string) (*model.Plugin, error) {
	return r.Get(ctx, id)
}

func (r *pluginRepository) ReadAll(ctx context.Context, options ...rest.QueryOptions) ([]model.Plugin, error) {
	return r.GetAll(ctx, r.parseRestOptions(ctx, options...))
}

var _ model.PluginRepository = (*pluginRepository)(nil)
var _ rest.Repository[model.Plugin] = (*pluginRepository)(nil)