Refactored settings
This commit is contained in:
1673
api/system/spec.json
1673
api/system/spec.json
File diff suppressed because it is too large
Load Diff
100
api/system/spec/settings.json
Normal file
100
api/system/spec/settings.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"Title": "Settings",
|
||||
"Interface": "Settings",
|
||||
"Struct": [
|
||||
{
|
||||
"imports": [
|
||||
"sqlxTypes github.com/jmoiron/sqlx/types"
|
||||
]
|
||||
}
|
||||
],
|
||||
"Parameters": null,
|
||||
"Protocol": "",
|
||||
"Authentication": [],
|
||||
"Path": "/settings",
|
||||
"APIs": [
|
||||
{
|
||||
"Name": "list",
|
||||
"Method": "GET",
|
||||
"Title": "List settings",
|
||||
"Path": "/",
|
||||
"Parameters": {
|
||||
"get": [
|
||||
{
|
||||
"name": "prefix",
|
||||
"title": "Key prefix",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "update",
|
||||
"Method": "PATCH",
|
||||
"Title": "Update settings",
|
||||
"Path": "/",
|
||||
"Parameters": {
|
||||
"post": [
|
||||
{
|
||||
"name": "values",
|
||||
"required": true,
|
||||
"title": "Array of new settings: `[{ name: ..., value: ... }]`. Omit value to remove setting",
|
||||
"type": "sqlxTypes.JSONText"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "get",
|
||||
"Method": "GET",
|
||||
"Title": "Check JWT token",
|
||||
"Path": "/{key}",
|
||||
"Parameters": {
|
||||
"get": [
|
||||
{
|
||||
"name": "ownerID",
|
||||
"title": "Owner ID",
|
||||
"type": "uint64"
|
||||
}
|
||||
],
|
||||
"path": [
|
||||
{
|
||||
"name": "key",
|
||||
"required": true,
|
||||
"title": "Setting key",
|
||||
"type": "string"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Name": "set",
|
||||
"Method": "PUT",
|
||||
"Title": "Set a value for a key",
|
||||
"Path": "/{key}",
|
||||
"Parameters": {
|
||||
"path": [
|
||||
{
|
||||
"name": "key",
|
||||
"required": true,
|
||||
"title": "Setting key",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"post": [
|
||||
{
|
||||
"name": "ownerID",
|
||||
"title": "Owner",
|
||||
"type": "uint64"
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"required": true,
|
||||
"title": "Setting value",
|
||||
"type": "sqlxTypes.JSONText"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -50,6 +50,8 @@ function types {
|
||||
./build/gen-type-set --types Organisation --output system/types/organisation.gen.go
|
||||
./build/gen-type-set --types Credentials --output system/types/credentials.gen.go
|
||||
|
||||
./build/gen-type-set --types Value --output internal/settings/types.gen.go --with-primary-key=false --package settings
|
||||
|
||||
green "OK"
|
||||
}
|
||||
|
||||
|
||||
16
crm/db/mysql/statik.go
Normal file
16
crm/db/mysql/statik.go
Normal file
File diff suppressed because one or more lines are too long
@@ -515,6 +515,79 @@ An organisation may have many roles. Roles may have many channels available. Acc
|
||||
|
||||
|
||||
|
||||
# Settings
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
| ------ | -------- | ------- |
|
||||
| `GET` | `/settings/` | List settings |
|
||||
| `PATCH` | `/settings/` | Update settings |
|
||||
| `GET` | `/settings/{key}` | Check JWT token |
|
||||
| `PUT` | `/settings/{key}` | Set a value for a key |
|
||||
|
||||
## List settings
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/settings/` | HTTP/S | GET | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| prefix | string | GET | Key prefix | N/A | NO |
|
||||
|
||||
## Update settings
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/settings/` | HTTP/S | PATCH | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| values | sqlxTypes.JSONText | POST | Array of new settings: `[{ name: ..., value: ... }]`. Omit value to remove setting | N/A | YES |
|
||||
|
||||
## Check JWT token
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/settings/{key}` | HTTP/S | GET | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| ownerID | uint64 | GET | Owner ID | N/A | NO |
|
||||
| key | string | PATH | Setting key | N/A | YES |
|
||||
|
||||
## Set a value for a key
|
||||
|
||||
#### Method
|
||||
|
||||
| URI | Protocol | Method | Authentication |
|
||||
| --- | -------- | ------ | -------------- |
|
||||
| `/settings/{key}` | HTTP/S | PUT | |
|
||||
|
||||
#### Request parameters
|
||||
|
||||
| Parameter | Type | Method | Description | Default | Required? |
|
||||
| --------- | ---- | ------ | ----------- | ------- | --------- |
|
||||
| key | string | PATH | Setting key | N/A | YES |
|
||||
| ownerID | uint64 | POST | Owner | N/A | NO |
|
||||
| value | sqlxTypes.JSONText | POST | Setting value | N/A | YES |
|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
|
||||
# Users
|
||||
|
||||
| Method | Endpoint | Purpose |
|
||||
|
||||
132
internal/settings/repository.go
Normal file
132
internal/settings/repository.go
Normal file
@@ -0,0 +1,132 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
"gopkg.in/Masterminds/squirrel.v1"
|
||||
)
|
||||
|
||||
type (
|
||||
repository struct {
|
||||
dbh *factory.DB
|
||||
|
||||
// sql table reference
|
||||
dbTable string
|
||||
}
|
||||
|
||||
Repository interface {
|
||||
With(ctx context.Context) Repository
|
||||
|
||||
Find(filter Filter) (ss ValueSet, err error)
|
||||
|
||||
Get(name string, ownedBy uint64) (value *Value, err error)
|
||||
Set(value *Value) error
|
||||
BulkSet(vv ValueSet) error
|
||||
Delete(name string, ownedBy uint64) (err error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewRepository(db *factory.DB, table string) Repository {
|
||||
return &repository{
|
||||
dbTable: table,
|
||||
dbh: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repository) db() *factory.DB {
|
||||
return r.dbh
|
||||
}
|
||||
|
||||
func (r repository) columns() []string {
|
||||
return []string{
|
||||
"name",
|
||||
"value",
|
||||
"rel_owner",
|
||||
"updated_at",
|
||||
"updated_by",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repository) With(ctx context.Context) Repository {
|
||||
return &repository{
|
||||
dbTable: r.dbTable,
|
||||
dbh: r.db().With(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (r *repository) Find(f Filter) (ss ValueSet, err error) {
|
||||
f.Normalize()
|
||||
lookup := squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.dbTable).
|
||||
// Always filter by owner
|
||||
Where("rel_owner = ?", f.OwnedBy)
|
||||
|
||||
if len(f.Prefix) > 0 {
|
||||
lookup = lookup.Where("name LIKE ?", f.Prefix+"%")
|
||||
}
|
||||
|
||||
if f.Page > 0 {
|
||||
lookup = lookup.Offset(f.PerPage * f.Page)
|
||||
}
|
||||
|
||||
if f.PerPage > 0 {
|
||||
lookup = lookup.Limit(f.PerPage)
|
||||
}
|
||||
|
||||
if query, args, err := lookup.ToSql(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not build lookup query for settings")
|
||||
} else if err = r.db().Select(&ss, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "could not find settings")
|
||||
} else {
|
||||
return ss, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (r repository) BulkSet(vv ValueSet) error {
|
||||
// Save all inside a db transaction
|
||||
return r.db().Transaction(func() (err error) {
|
||||
return vv.Walk(func(v *Value) error {
|
||||
return r.Set(v)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (r *repository) Set(value *Value) error {
|
||||
value.UpdatedAt = time.Now()
|
||||
return r.db().Replace(r.dbTable, value)
|
||||
}
|
||||
|
||||
func (r *repository) Delete(name string, ownedBy uint64) error {
|
||||
_, err := r.db().Exec(
|
||||
fmt.Sprintf("DELETE FROM %s WHERE name = ? AND rel_owner = ? ", r.dbTable),
|
||||
name,
|
||||
ownedBy,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *repository) Get(name string, ownedBy uint64) (value *Value, err error) {
|
||||
lookup := squirrel.
|
||||
Select(r.columns()...).
|
||||
From(r.dbTable).
|
||||
Where("rel_owner = ?", ownedBy).
|
||||
Where("name = ?", name)
|
||||
|
||||
value = &Value{}
|
||||
|
||||
if query, args, err := lookup.ToSql(); err != nil {
|
||||
return nil, errors.Wrap(err, "could not build lookup query for settings")
|
||||
} else if err = r.db().Get(value, query, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "could not get settings")
|
||||
} else if value.Name == "" {
|
||||
return nil, nil
|
||||
} else {
|
||||
return value, nil
|
||||
}
|
||||
|
||||
}
|
||||
83
internal/settings/settings.go
Normal file
83
internal/settings/settings.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type (
|
||||
service struct {
|
||||
repository Repository
|
||||
}
|
||||
|
||||
Service interface {
|
||||
With(ctx context.Context) Service
|
||||
|
||||
FindByPrefix(prefix string) (vv ValueSet, err error)
|
||||
BulkSet(vv ValueSet) (err error)
|
||||
Set(v *Value) (err error)
|
||||
Get(name string, ownedBy uint64) (out *Value, err error)
|
||||
Delete(name string, ownedBy uint64) error
|
||||
GetGlobalString(name string) (out string, err error)
|
||||
GetGlobalBool(name string) (out bool, err error)
|
||||
}
|
||||
)
|
||||
|
||||
func NewService(r Repository) Service {
|
||||
svc := &service{
|
||||
repository: r,
|
||||
}
|
||||
|
||||
return svc
|
||||
}
|
||||
|
||||
func (s service) With(ctx context.Context) Service {
|
||||
return &service{
|
||||
repository: s.repository.With(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (s service) FindByPrefix(prefix string) (ValueSet, error) {
|
||||
if vv, err := s.repository.Find(Filter{Prefix: prefix}); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return vv, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s service) Set(v *Value) (err error) {
|
||||
return s.repository.Set(v)
|
||||
}
|
||||
|
||||
func (s service) BulkSet(vv ValueSet) (err error) {
|
||||
return s.repository.BulkSet(vv)
|
||||
}
|
||||
|
||||
func (s service) Get(name string, ownedBy uint64) (out *Value, err error) {
|
||||
return s.repository.Get(name, ownedBy)
|
||||
}
|
||||
|
||||
func (s service) Delete(name string, ownedBy uint64) error {
|
||||
return s.repository.Delete(name, ownedBy)
|
||||
}
|
||||
|
||||
func (s service) GetGlobalString(name string) (out string, err error) {
|
||||
const global = 0
|
||||
var v *Value
|
||||
|
||||
if v, err = s.repository.Get(name, global); err == nil {
|
||||
err = v.Value.Unmarshal(&out)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s service) GetGlobalBool(name string) (out bool, err error) {
|
||||
const global = 0
|
||||
var v *Value
|
||||
|
||||
if v, err = s.repository.Get(name, global); err == nil {
|
||||
err = v.Value.Unmarshal(&out)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
41
internal/settings/types.gen.go
Normal file
41
internal/settings/types.gen.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package settings
|
||||
|
||||
// Hello! This file is auto-generated.
|
||||
|
||||
type (
|
||||
|
||||
// ValueSet slice of Value
|
||||
//
|
||||
// This type is auto-generated.
|
||||
ValueSet []*Value
|
||||
)
|
||||
|
||||
// Walk iterates through every slice item and calls w(Value) err
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set ValueSet) Walk(w func(*Value) error) (err error) {
|
||||
for i := range set {
|
||||
if err = w(set[i]); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Filter iterates through every slice item, calls f(Value) (bool, err) and return filtered slice
|
||||
//
|
||||
// This function is auto-generated.
|
||||
func (set ValueSet) Filter(f func(*Value) (bool, error)) (out ValueSet, err error) {
|
||||
var ok bool
|
||||
out = ValueSet{}
|
||||
for i := range set {
|
||||
if ok, err = f(set[i]); err != nil {
|
||||
return
|
||||
} else if ok {
|
||||
out = append(out, set[i])
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
104
internal/settings/types.go
Normal file
104
internal/settings/types.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
)
|
||||
|
||||
type (
|
||||
Value struct {
|
||||
Name string `json:"name" db:"name"`
|
||||
Value types.JSONText `json:"value" db:"value"`
|
||||
|
||||
// Setting owner, 0 for global settings
|
||||
OwnedBy uint64 `json:"-" db:"rel_owner"`
|
||||
|
||||
// Who updated & when
|
||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||
UpdatedBy uint64 `json:"updatedBy" db:"updated_by"`
|
||||
}
|
||||
|
||||
Filter struct {
|
||||
Prefix string `json:"prefix"`
|
||||
OwnedBy uint64 `json:"ownedBy"`
|
||||
Page uint64 `json:"page"`
|
||||
PerPage uint64 `json:"perPage"`
|
||||
}
|
||||
|
||||
KV map[string]types.JSONText
|
||||
)
|
||||
|
||||
const (
|
||||
settingsFilterPerPageMax = 100
|
||||
)
|
||||
|
||||
func (f *Filter) Normalize() {
|
||||
f.Prefix = strings.TrimSpace(f.Prefix)
|
||||
if f.PerPage > settingsFilterPerPageMax {
|
||||
f.PerPage = settingsFilterPerPageMax
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Value) SetValueAsString(str string) error {
|
||||
var dummy interface{}
|
||||
// Test input to be sure we can save it...
|
||||
if err := json.Unmarshal([]byte(str), &dummy); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.Value = types.JSONText(str)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Value) SetValue(value interface{}) (err error) {
|
||||
v.Value, err = json.Marshal(value)
|
||||
return
|
||||
}
|
||||
|
||||
func (ss ValueSet) KV() KV {
|
||||
m := KV{}
|
||||
|
||||
_ = ss.Walk(func(v *Value) error {
|
||||
m[v.Name] = v.Value
|
||||
return nil
|
||||
})
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (kv KV) Has(k string) (ok bool) {
|
||||
_, ok = kv[k]
|
||||
return
|
||||
}
|
||||
|
||||
func (kv KV) Bool(k string) (out bool) {
|
||||
out = false
|
||||
if v, ok := kv[k]; ok {
|
||||
v.Unmarshal(&out)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (kv KV) String(k string) (out string) {
|
||||
out = ""
|
||||
if v, ok := kv[k]; ok {
|
||||
v.Unmarshal(&out)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (kv KV) Filter(prefix string) KV {
|
||||
var out = KV{}
|
||||
for k, v := range kv {
|
||||
if strings.Index(k, prefix) == 0 {
|
||||
out[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
62
internal/settings/types_test.go
Normal file
62
internal/settings/types_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package settings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx/types"
|
||||
|
||||
"github.com/crusttech/crust/internal/test"
|
||||
)
|
||||
|
||||
func TestKV_Bool(t *testing.T) {
|
||||
type args struct {
|
||||
k string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
kv KV
|
||||
args args
|
||||
wantV bool
|
||||
}{
|
||||
{
|
||||
name: "True value should return true",
|
||||
kv: KV{"true-value": types.JSONText(`true`)},
|
||||
args: args{k: "true-value"},
|
||||
wantV: true,
|
||||
},
|
||||
{
|
||||
name: "Null value should return false",
|
||||
kv: KV{"null-value": types.JSONText(`null`)},
|
||||
args: args{k: "null-value"},
|
||||
wantV: false,
|
||||
},
|
||||
{
|
||||
name: "Unexisting value should return false",
|
||||
kv: KV{},
|
||||
args: args{k: "unexisting"},
|
||||
wantV: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid KV should return false",
|
||||
kv: nil,
|
||||
args: args{k: "invalid-kv"},
|
||||
wantV: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if gotV := tt.kv.Bool(tt.args.k); gotV != tt.wantV {
|
||||
t.Errorf("KV.Bool() = %v, want %v", gotV, tt.wantV)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingValueAsString(t *testing.T) {
|
||||
test.NoError(t, (&Value{}).SetValueAsString(`"string"`), "unable to set value as string")
|
||||
test.NoError(t, (&Value{}).SetValueAsString(`false`), "unable to set value as string")
|
||||
test.NoError(t, (&Value{}).SetValueAsString(`null`), "unable to set value as string")
|
||||
test.NoError(t, (&Value{}).SetValueAsString(`42`), "unable to set value as string")
|
||||
test.NoError(t, (&Value{}).SetValueAsString(`3.14`), "unable to set value as string")
|
||||
test.Error(t, (&Value{}).SetValueAsString(`error`), "expecting error when not setting JSON")
|
||||
}
|
||||
16
messaging/db/mysql/statik.go
Normal file
16
messaging/db/mysql/statik.go
Normal file
File diff suppressed because one or more lines are too long
88
system/cli/settings.go
Normal file
88
system/cli/settings.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/crusttech/crust/internal/settings"
|
||||
)
|
||||
|
||||
func Settings(rootCmd *cobra.Command, service settings.Service) {
|
||||
exit := func(err error) {
|
||||
if err != nil {
|
||||
rootCmd.Printf("Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
} else {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
settingsCmd := &cobra.Command{
|
||||
Use: "settings",
|
||||
Short: "Settings management",
|
||||
}
|
||||
|
||||
list := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List all",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
prefix := cmd.Flags().Lookup("prefix").Value.String()
|
||||
if kv, err := service.FindByPrefix(prefix); err != nil {
|
||||
exit(err)
|
||||
} else {
|
||||
for _, v := range kv {
|
||||
cmd.Printf("%s\t%v\n", v.Name, v.Value)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
list.Flags().String("prefix", "", "Filter settings by prefix")
|
||||
|
||||
get := &cobra.Command{
|
||||
Use: "get [key to get]",
|
||||
|
||||
Short: "Get value (raw JSON) for a specific key",
|
||||
Args: cobra.ExactArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if v, err := service.Get(args[0], 0); err != nil {
|
||||
exit(err)
|
||||
} else if v != nil {
|
||||
cmd.Printf("%v\n", v.Value)
|
||||
}
|
||||
exit(nil)
|
||||
},
|
||||
}
|
||||
|
||||
set := &cobra.Command{
|
||||
Use: "set [key to set] [value",
|
||||
Short: "Set value (raw JSON) for a specific key",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
value := args[1]
|
||||
v := &settings.Value{
|
||||
Name: args[0],
|
||||
}
|
||||
|
||||
if err := v.SetValueAsString(value); err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
exit(service.Set(v))
|
||||
},
|
||||
}
|
||||
|
||||
del := &cobra.Command{
|
||||
Use: "delete [key to remove]",
|
||||
Short: "Set value (raw JSON) for a specific key",
|
||||
Args: cobra.MinimumNArgs(1),
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
exit(service.Delete(args[0], 0))
|
||||
},
|
||||
}
|
||||
|
||||
settingsCmd.AddCommand(list, get, set, del)
|
||||
|
||||
rootCmd.AddCommand(settingsCmd)
|
||||
}
|
||||
16
system/db/mysql/statik.go
Normal file
16
system/db/mysql/statik.go
Normal file
File diff suppressed because one or more lines are too long
12
system/db/schema/mysql/20190326122000.settings.up.sql
Normal file
12
system/db/schema/mysql/20190326122000.settings.up.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
DROP TABLE `settings`;
|
||||
|
||||
CREATE TABLE `sys_settings` (
|
||||
rel_owner BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Value owner, 0 for global settings',
|
||||
name VARCHAR(200) NOT NULL COMMENT 'Unique set of setting keys',
|
||||
value JSON COMMENT 'Setting value',
|
||||
|
||||
updated_at DATETIME NOT NULL DEFAULT NOW() COMMENT 'When was the value updated',
|
||||
updated_by BIGINT UNSIGNED NOT NULL DEFAULT 0 COMMENT 'Who created/updated the value',
|
||||
|
||||
PRIMARY KEY (name, rel_owner)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
|
||||
@@ -188,6 +188,8 @@ func (r *role) Reset() error {
|
||||
(2, 'compose:page:*', 'delete', 2),
|
||||
(2, 'system', 'access', 2),
|
||||
(2, 'system', 'grant', 2),
|
||||
(2, 'system', 'settings.read', 2),
|
||||
(2, 'system', 'settings.manage', 2),
|
||||
(2, 'system', 'organisation.create', 2),
|
||||
(2, 'system', 'user.create', 2),
|
||||
(2, 'system', 'role.create', 2),
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/titpetric/factory"
|
||||
)
|
||||
|
||||
type (
|
||||
settings struct {
|
||||
*repository
|
||||
|
||||
// sql table reference
|
||||
settings string
|
||||
}
|
||||
|
||||
Settings interface {
|
||||
With(ctx context.Context, db *factory.DB) Settings
|
||||
|
||||
Get(name string, value interface{}) (bool, error)
|
||||
Set(name string, value interface{}) error
|
||||
}
|
||||
)
|
||||
|
||||
func NewSettings(ctx context.Context, db *factory.DB) Settings {
|
||||
return (&settings{}).With(ctx, db)
|
||||
}
|
||||
|
||||
func (r *settings) With(ctx context.Context, db *factory.DB) Settings {
|
||||
return &settings{
|
||||
repository: r.repository.With(ctx, db),
|
||||
settings: "settings",
|
||||
}
|
||||
}
|
||||
|
||||
func (r *settings) Set(name string, value interface{}) error {
|
||||
if jsonValue, err := json.Marshal(value); err != nil {
|
||||
return errors.Wrap(err, "Error marshaling settings value")
|
||||
} else {
|
||||
return r.db().Replace(r.settings, struct {
|
||||
Key string `db:"name"`
|
||||
Val json.RawMessage `db:"value"`
|
||||
}{name, jsonValue})
|
||||
}
|
||||
}
|
||||
|
||||
func (r *settings) Get(name string, value interface{}) (bool, error) {
|
||||
sql := "SELECT value FROM " + r.settings + " WHERE name = ?"
|
||||
|
||||
var stored json.RawMessage
|
||||
|
||||
if err := r.db().Get(&stored, sql, name); err != nil {
|
||||
return false, errors.Wrap(err, "Error reading settings from the database")
|
||||
} else if stored == nil {
|
||||
return false, nil
|
||||
} else if err := json.Unmarshal(stored, value); err != nil {
|
||||
return false, errors.Wrap(err, "Error unmarshaling settings value")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
@@ -25,6 +25,9 @@ type (
|
||||
|
||||
Effective() (ee []effectivePermission, err error)
|
||||
|
||||
CanReadSettings() bool
|
||||
CanManageSettings() bool
|
||||
|
||||
CanCreateOrganisation() bool
|
||||
CanCreateUser() bool
|
||||
CanCreateRole() bool
|
||||
@@ -76,6 +79,8 @@ func (p *permissions) Effective() (ee []effectivePermission, err error) {
|
||||
}
|
||||
|
||||
ee = append(ee, ep("system", "access", p.CanAccess()))
|
||||
ee = append(ee, ep("system", "settings.read", p.CanReadSettings()))
|
||||
ee = append(ee, ep("system", "settings.manage", p.CanManageSettings()))
|
||||
ee = append(ee, ep("system", "application.create", p.CanCreateApplication()))
|
||||
ee = append(ee, ep("system", "role.create", p.CanCreateRole()))
|
||||
ee = append(ee, ep("system", "organisation.create", p.CanCreateOrganisation()))
|
||||
@@ -88,6 +93,14 @@ func (p *permissions) CanAccess() bool {
|
||||
return p.checkAccess(types.PermissionResource, "access")
|
||||
}
|
||||
|
||||
func (p *permissions) CanReadSettings() bool {
|
||||
return p.checkAccess(types.PermissionResource, "settings.read")
|
||||
}
|
||||
|
||||
func (p *permissions) CanManageSettings() bool {
|
||||
return p.checkAccess(types.PermissionResource, "settings.manage")
|
||||
}
|
||||
|
||||
func (p *permissions) CanCreateOrganisation() bool {
|
||||
return p.checkAccess(types.PermissionResource, "organisation.create")
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultSettings SettingsService
|
||||
DefaultAuth AuthService
|
||||
DefaultUser UserService
|
||||
DefaultRole RoleService
|
||||
@@ -22,6 +23,7 @@ var (
|
||||
|
||||
func Init() error {
|
||||
ctx := context.Background()
|
||||
DefaultSettings = Settings(ctx)
|
||||
DefaultRules = Rules(ctx)
|
||||
DefaultPermissions = Permissions(ctx)
|
||||
DefaultAuth = Auth(ctx)
|
||||
|
||||
73
system/internal/service/settings.go
Normal file
73
system/internal/service/settings.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
internalSettings "github.com/crusttech/crust/internal/settings"
|
||||
"github.com/crusttech/crust/system/internal/repository"
|
||||
)
|
||||
|
||||
type (
|
||||
// Wrapper service for system around internal settings service
|
||||
settings struct {
|
||||
db db
|
||||
ctx context.Context
|
||||
|
||||
prm PermissionsService
|
||||
internalSettings internalSettings.Service
|
||||
}
|
||||
|
||||
SettingsService interface {
|
||||
With(ctx context.Context) SettingsService
|
||||
FindByPrefix(prefix string) (vv internalSettings.ValueSet, err error)
|
||||
Set(v *internalSettings.Value) (err error)
|
||||
BulkSet(vv internalSettings.ValueSet) (err error)
|
||||
Get(name string, ownedBy uint64) (out *internalSettings.Value, err error)
|
||||
}
|
||||
)
|
||||
|
||||
func Settings(ctx context.Context) SettingsService {
|
||||
return (&settings{}).With(ctx)
|
||||
}
|
||||
|
||||
func (svc settings) With(ctx context.Context) SettingsService {
|
||||
db := repository.DB(ctx)
|
||||
return &settings{
|
||||
ctx: ctx,
|
||||
prm: Permissions(ctx),
|
||||
internalSettings: internalSettings.NewService(internalSettings.NewRepository(db, "sys_settings")).With(ctx),
|
||||
}
|
||||
}
|
||||
|
||||
func (svc settings) FindByPrefix(prefix string) (vv internalSettings.ValueSet, err error) {
|
||||
if !svc.prm.CanReadSettings() {
|
||||
return nil, errors.New("not allowed to read settings")
|
||||
}
|
||||
|
||||
return svc.internalSettings.FindByPrefix(prefix)
|
||||
}
|
||||
|
||||
func (svc settings) Set(v *internalSettings.Value) (err error) {
|
||||
if !svc.prm.CanManageSettings() {
|
||||
return errors.New("not allowed to manage settings")
|
||||
}
|
||||
|
||||
return svc.internalSettings.Set(v)
|
||||
}
|
||||
|
||||
func (svc settings) BulkSet(vv internalSettings.ValueSet) (err error) {
|
||||
if !svc.prm.CanManageSettings() {
|
||||
return errors.New("not allowed to manage settings")
|
||||
}
|
||||
|
||||
return svc.internalSettings.BulkSet(vv)
|
||||
}
|
||||
|
||||
func (svc settings) Get(name string, ownedBy uint64) (out *internalSettings.Value, err error) {
|
||||
if !svc.prm.CanReadSettings() {
|
||||
return nil, errors.New("not allowed to read settings")
|
||||
}
|
||||
|
||||
return svc.internalSettings.Get(name, ownedBy)
|
||||
}
|
||||
@@ -11,6 +11,8 @@ var (
|
||||
"system": map[string]bool{
|
||||
"access": true,
|
||||
"grant": true,
|
||||
"settings.read": true,
|
||||
"settings.manage": true,
|
||||
"organisation.create": true,
|
||||
"role.create": true,
|
||||
"application.create": true,
|
||||
|
||||
86
system/rest/handlers/settings.go
Normal file
86
system/rest/handlers/settings.go
Normal file
@@ -0,0 +1,86 @@
|
||||
package handlers
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `settings.go`, `settings.util.go` or `settings_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `settings.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/titpetric/factory/resputil"
|
||||
|
||||
"github.com/crusttech/crust/system/rest/request"
|
||||
)
|
||||
|
||||
// Internal API interface
|
||||
type SettingsAPI interface {
|
||||
List(context.Context, *request.SettingsList) (interface{}, error)
|
||||
Update(context.Context, *request.SettingsUpdate) (interface{}, error)
|
||||
Get(context.Context, *request.SettingsGet) (interface{}, error)
|
||||
Set(context.Context, *request.SettingsSet) (interface{}, error)
|
||||
}
|
||||
|
||||
// HTTP API interface
|
||||
type Settings struct {
|
||||
List func(http.ResponseWriter, *http.Request)
|
||||
Update func(http.ResponseWriter, *http.Request)
|
||||
Get func(http.ResponseWriter, *http.Request)
|
||||
Set func(http.ResponseWriter, *http.Request)
|
||||
}
|
||||
|
||||
func NewSettings(sh SettingsAPI) *Settings {
|
||||
return &Settings{
|
||||
List: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSettingsList()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return sh.List(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Update: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSettingsUpdate()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return sh.Update(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Get: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSettingsGet()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return sh.Get(r.Context(), params)
|
||||
})
|
||||
},
|
||||
Set: func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
params := request.NewSettingsSet()
|
||||
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
|
||||
return sh.Set(r.Context(), params)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *Settings) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(middlewares...)
|
||||
r.Get("/settings/", sh.List)
|
||||
r.Patch("/settings/", sh.Update)
|
||||
r.Get("/settings/{key}", sh.Get)
|
||||
r.Put("/settings/{key}", sh.Set)
|
||||
})
|
||||
}
|
||||
230
system/rest/request/settings.go
Normal file
230
system/rest/request/settings.go
Normal file
@@ -0,0 +1,230 @@
|
||||
package request
|
||||
|
||||
/*
|
||||
Hello! This file is auto-generated from `docs/src/spec.json`.
|
||||
|
||||
For development:
|
||||
In order to update the generated files, edit this file under the location,
|
||||
add your struct fields, imports, API definitions and whatever you want, and:
|
||||
|
||||
1. run [spec](https://github.com/titpetric/spec) in the same folder,
|
||||
2. run `./_gen.php` in this folder.
|
||||
|
||||
You may edit `settings.go`, `settings.util.go` or `settings_test.go` to
|
||||
implement your API calls, helper functions and tests. The file `settings.go`
|
||||
is only generated the first time, and will not be overwritten if it exists.
|
||||
*/
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"encoding/json"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
sqlxTypes "github.com/jmoiron/sqlx/types"
|
||||
)
|
||||
|
||||
var _ = chi.URLParam
|
||||
var _ = multipart.FileHeader{}
|
||||
|
||||
// Settings list request parameters
|
||||
type SettingsList struct {
|
||||
Prefix string
|
||||
}
|
||||
|
||||
func NewSettingsList() *SettingsList {
|
||||
return &SettingsList{}
|
||||
}
|
||||
|
||||
func (seReq *SettingsList) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(seReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["prefix"]; ok {
|
||||
|
||||
seReq.Prefix = val
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewSettingsList()
|
||||
|
||||
// Settings update request parameters
|
||||
type SettingsUpdate struct {
|
||||
Values sqlxTypes.JSONText
|
||||
}
|
||||
|
||||
func NewSettingsUpdate() *SettingsUpdate {
|
||||
return &SettingsUpdate{}
|
||||
}
|
||||
|
||||
func (seReq *SettingsUpdate) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(seReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := post["values"]; ok {
|
||||
|
||||
if seReq.Values, err = parseJSONTextWithErr(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewSettingsUpdate()
|
||||
|
||||
// Settings get request parameters
|
||||
type SettingsGet struct {
|
||||
OwnerID uint64 `json:",string"`
|
||||
Key string
|
||||
}
|
||||
|
||||
func NewSettingsGet() *SettingsGet {
|
||||
return &SettingsGet{}
|
||||
}
|
||||
|
||||
func (seReq *SettingsGet) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(seReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
if val, ok := get["ownerID"]; ok {
|
||||
|
||||
seReq.OwnerID = parseUInt64(val)
|
||||
}
|
||||
seReq.Key = chi.URLParam(r, "key")
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewSettingsGet()
|
||||
|
||||
// Settings set request parameters
|
||||
type SettingsSet struct {
|
||||
Key string
|
||||
OwnerID uint64 `json:",string"`
|
||||
Value sqlxTypes.JSONText
|
||||
}
|
||||
|
||||
func NewSettingsSet() *SettingsSet {
|
||||
return &SettingsSet{}
|
||||
}
|
||||
|
||||
func (seReq *SettingsSet) Fill(r *http.Request) (err error) {
|
||||
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
|
||||
err = json.NewDecoder(r.Body).Decode(seReq)
|
||||
|
||||
switch {
|
||||
case err == io.EOF:
|
||||
err = nil
|
||||
case err != nil:
|
||||
return errors.Wrap(err, "error parsing http request body")
|
||||
}
|
||||
}
|
||||
|
||||
if err = r.ParseForm(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
get := map[string]string{}
|
||||
post := map[string]string{}
|
||||
urlQuery := r.URL.Query()
|
||||
for name, param := range urlQuery {
|
||||
get[name] = string(param[0])
|
||||
}
|
||||
postVars := r.Form
|
||||
for name, param := range postVars {
|
||||
post[name] = string(param[0])
|
||||
}
|
||||
|
||||
seReq.Key = chi.URLParam(r, "key")
|
||||
if val, ok := post["ownerID"]; ok {
|
||||
|
||||
seReq.OwnerID = parseUInt64(val)
|
||||
}
|
||||
if val, ok := post["value"]; ok {
|
||||
|
||||
if seReq.Value, err = parseJSONTextWithErr(val); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
var _ RequestFiller = NewSettingsSet()
|
||||
@@ -51,6 +51,7 @@ func MountRoutes(oidcConfig *config.OIDC, socialConfig *config.Social, jwtEncode
|
||||
handlers.NewOrganisation(Organisation{}.New()).MountRoutes(r)
|
||||
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
|
||||
handlers.NewApplication(Application{}.New()).MountRoutes(r)
|
||||
handlers.NewSettings(Settings{}.New()).MountRoutes(r)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
60
system/rest/settings.go
Normal file
60
system/rest/settings.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package rest
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/crusttech/crust/internal/settings"
|
||||
"github.com/crusttech/crust/system/internal/service"
|
||||
"github.com/crusttech/crust/system/rest/request"
|
||||
)
|
||||
|
||||
var _ = errors.Wrap
|
||||
|
||||
type (
|
||||
Settings struct {
|
||||
svc struct {
|
||||
settings service.SettingsService
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func (Settings) New() *Settings {
|
||||
ctrl := &Settings{}
|
||||
ctrl.svc.settings = service.DefaultSettings
|
||||
|
||||
return ctrl
|
||||
}
|
||||
|
||||
func (ctrl *Settings) List(ctx context.Context, r *request.SettingsList) (interface{}, error) {
|
||||
if vv, err := ctrl.svc.settings.With(ctx).FindByPrefix(r.Prefix); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return vv, err
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Settings) Update(ctx context.Context, r *request.SettingsUpdate) (interface{}, error) {
|
||||
values := settings.ValueSet{}
|
||||
|
||||
if err := r.Values.Unmarshal(&values); err != nil {
|
||||
return nil, err
|
||||
} else if err := ctrl.svc.settings.With(ctx).BulkSet(values); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Settings) Get(ctx context.Context, r *request.SettingsGet) (interface{}, error) {
|
||||
if v, err := ctrl.svc.settings.With(ctx).Get(r.Key, r.OwnerID); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctrl *Settings) Set(ctx context.Context, r *request.SettingsSet) (interface{}, error) {
|
||||
return nil, errors.New("Not implemented: Settings.set")
|
||||
}
|
||||
@@ -14,7 +14,9 @@ import (
|
||||
"github.com/crusttech/crust/internal/db"
|
||||
"github.com/crusttech/crust/internal/mail"
|
||||
"github.com/crusttech/crust/internal/metrics"
|
||||
"github.com/crusttech/crust/internal/settings"
|
||||
migrate "github.com/crusttech/crust/system/db"
|
||||
"github.com/crusttech/crust/system/internal/repository"
|
||||
"github.com/crusttech/crust/system/service"
|
||||
)
|
||||
|
||||
@@ -44,6 +46,17 @@ func Init(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Load settings from the database,
|
||||
// for now, only at start-up time.
|
||||
ctx := context.Background()
|
||||
// settingsRepository := internalRepository.NewSettings(repository.DB(ctx), "sys_settings")
|
||||
// if ss, err := settingsRepository.Find(types.SettingsFilter{}); err != nil {
|
||||
// panic(err)
|
||||
// } else {
|
||||
// spew.Dump(ss.KV())
|
||||
// }
|
||||
settingService := settings.NewService(settings.NewRepository(repository.DB(ctx), "sys_settings"))
|
||||
|
||||
// configure resputil options
|
||||
resputil.SetConfig(resputil.Options{
|
||||
Pretty: flags.http.Pretty,
|
||||
|
||||
Reference in New Issue
Block a user