3
0

Add system/applications

- CRUD support for applications
 - new (application related) permissions
This commit is contained in:
Denis Arh
2019-03-08 19:51:08 +01:00
parent 7dd93af411
commit ebfab252fc
17 changed files with 1178 additions and 1 deletions
+89
View File
@@ -574,6 +574,95 @@
}
]
},
{
"title": "Applications",
"path": "/application",
"entrypoint": "application",
"authentication": [],
"struct": [
{
"imports": [
"sqlxTypes github.com/jmoiron/sqlx/types"
]
}
],
"apis": [
{
"name": "list",
"method": "GET",
"title": "List applications",
"path": "/"
},
{
"name": "create",
"method": "POST",
"title": "Create application",
"path": "/",
"parameters": {
"post": [
{ "name": "name", "type": "string", "required": true, "title": "Email"},
{ "name": "enabled", "type": "bool", "required": false, "title": "Enabled"},
{ "name": "unify", "type": "sqlxTypes.JSONText", "required": false, "title": "Unify properties"},
{ "name": "config", "type": "sqlxTypes.JSONText", "required": false, "title": "Arbitrary JSON holding application configuration"}
]
}
},
{
"name": "update",
"method": "PUT",
"title": "Update user details",
"path": "/{applicationID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "applicationID",
"required": true,
"title": "Application ID"
}
],
"post": [
{ "name": "name", "type": "string", "required": true, "title": "Email"},
{ "name": "enabled", "type": "bool", "required": false, "title": "Enabled"},
{ "name": "unify", "type": "sqlxTypes.JSONText", "required": false, "title": "Unify properties"},
{ "name": "config", "type": "sqlxTypes.JSONText", "required": false, "title": "Arbitrary JSON holding application configuration"}
]
}
},
{
"name": "read",
"method": "GET",
"title": "Read application details",
"path": "/{applicationID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "applicationID",
"required": true,
"title": "Application ID"
}
]
}
},
{
"name": "delete",
"method": "DELETE",
"title": "Remove application",
"path": "/{applicationID}",
"parameters": {
"path": [
{
"type": "uint64",
"name": "applicationID",
"required": true,
"title": "Application ID"
}
]
}
}
]
},
{
"title": "Permissions",
"parameters": {},
+132
View File
@@ -0,0 +1,132 @@
{
"Title": "Applications",
"Interface": "Application",
"Struct": [
{
"imports": [
"sqlxTypes github.com/jmoiron/sqlx/types"
]
}
],
"Parameters": null,
"Protocol": "",
"Authentication": [],
"Path": "/application",
"APIs": [
{
"Name": "list",
"Method": "GET",
"Title": "List applications",
"Path": "/",
"Parameters": null
},
{
"Name": "create",
"Method": "POST",
"Title": "Create application",
"Path": "/",
"Parameters": {
"post": [
{
"name": "name",
"required": true,
"title": "Email",
"type": "string"
},
{
"name": "enabled",
"required": false,
"title": "Enabled",
"type": "bool"
},
{
"name": "unify",
"required": false,
"title": "Unify properties",
"type": "sqlxTypes.JSONText"
},
{
"name": "config",
"required": false,
"title": "Arbitrary JSON holding application configuration",
"type": "sqlxTypes.JSONText"
}
]
}
},
{
"Name": "update",
"Method": "PUT",
"Title": "Update user details",
"Path": "/{applicationID}",
"Parameters": {
"path": [
{
"name": "applicationID",
"required": true,
"title": "Application ID",
"type": "uint64"
}
],
"post": [
{
"name": "name",
"required": true,
"title": "Email",
"type": "string"
},
{
"name": "enabled",
"required": false,
"title": "Enabled",
"type": "bool"
},
{
"name": "unify",
"required": false,
"title": "Unify properties",
"type": "sqlxTypes.JSONText"
},
{
"name": "config",
"required": false,
"title": "Arbitrary JSON holding application configuration",
"type": "sqlxTypes.JSONText"
}
]
}
},
{
"Name": "read",
"Method": "GET",
"Title": "Read application details",
"Path": "/{applicationID}",
"Parameters": {
"path": [
{
"name": "applicationID",
"required": true,
"title": "Application ID",
"type": "uint64"
}
]
}
},
{
"Name": "delete",
"Method": "DELETE",
"Title": "Remove application",
"Path": "/{applicationID}",
"Parameters": {
"path": [
{
"name": "applicationID",
"required": true,
"title": "Application ID",
"type": "uint64"
}
]
}
}
]
}
+1
View File
@@ -39,6 +39,7 @@ function types {
./build/gen-type-set --with-primary-key=false --types Unread --output messaging/types/unread.gen.go
./build/gen-type-set --types User --output system/types/user.gen.go
./build/gen-type-set --types Application --output system/types/application.gen.go
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Role --output system/types/role.gen.go
./build/gen-type-set --with-resources=true --resource-type "rules.Resource" --imports "github.com/crusttech/crust/internal/rules" --types Organisation --output system/types/organisation.gen.go
./build/gen-type-set --types Credentials --output system/types/credentials.gen.go
+81
View File
@@ -1,3 +1,84 @@
# Applications
## List applications
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/application/` | HTTP/S | GET | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
## Create application
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/application/` | HTTP/S | POST | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| name | string | POST | Email | N/A | YES |
| enabled | bool | POST | Enabled | N/A | NO |
| unify | sqlxTypes.JSONText | POST | Unify properties | N/A | NO |
| config | sqlxTypes.JSONText | POST | Arbitrary JSON holding application configuration | N/A | NO |
## Update user details
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/application/{applicationID}` | HTTP/S | PUT | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| applicationID | uint64 | PATH | Application ID | N/A | YES |
| name | string | POST | Email | N/A | YES |
| enabled | bool | POST | Enabled | N/A | NO |
| unify | sqlxTypes.JSONText | POST | Unify properties | N/A | NO |
| config | sqlxTypes.JSONText | POST | Arbitrary JSON holding application configuration | N/A | NO |
## Read application details
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/application/{applicationID}` | HTTP/S | GET | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| applicationID | uint64 | PATH | Application ID | N/A | YES |
## Remove application
#### Method
| URI | Protocol | Method | Authentication |
| --- | -------- | ------ | -------------- |
| `/application/{applicationID}` | HTTP/S | DELETE | |
#### Request parameters
| Parameter | Type | Method | Description | Default | Required? |
| --------- | ---- | ------ | ----------- | ------- | --------- |
| applicationID | uint64 | PATH | Application ID | N/A | YES |
# Authentication
## Check JWT token
File diff suppressed because one or more lines are too long
@@ -0,0 +1,40 @@
CREATE TABLE sys_application (
id BIGINT UNSIGNED NOT NULL,
rel_owner BIGINT UNSIGNED NOT NULL REFERENCES sys_users(id),
name TEXT NOT NULL COMMENT 'something we can differentiate application by',
enabled BOOL NOT NULL,
unify JSON NULL COMMENT 'unify specific settings',
created_at DATETIME NOT NULL DEFAULT NOW(),
updated_at DATETIME NULL,
deleted_at DATETIME NULL, -- user soft delete
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
REPLACE INTO `sys_application` (`id`, `name`, `enabled`, `rel_owner`, `unify`) VALUES
( 1, 'Crust Messaging', true, 0,
'{"logo": "/applications/crust.jpg", "icon": "/applications/crust_favicon.png", "url": "/messaging/", "listed": true}'
),
( 2, 'Crust CRM', true, 0,
'{"logo": "/applications/crust.jpg", "icon": "/applications/crust_favicon.png", "url": "/crm/", "listed": true}'
),
( 3, 'Crust Admin Area', true, 0,
'{"logo": "/applications/crust.jpg", "icon": "/applications/crust_favicon.png", "url": "/admin/", "listed": true}'
),
( 4, 'Corteza Jitsi Bridge', true, 0,
'{"logo": "/applications/jitsi.png", "icon": "/applications/jitsi_icon.png", "url": "/bridge/jitsi/", "listed": true}'
),
( 5, 'Google Maps', true, 0,
'{"logo": "/applications/google_maps.png", "icon": "/applications/google_maps_icon.png", "url": "/bridge/google-maps/", "listed": true}'
);
-- Allow admin access to applications
INSERT INTO `sys_rules` (`rel_role`, `resource`, `operation`, `value`) VALUES
(2, 'system', 'application.create', 2),
(2, 'application:role', 'read', 2),
(2, 'application:role', 'update', 2),
(2, 'application:role', 'delete', 2)
;
+85
View File
@@ -0,0 +1,85 @@
package repository
import (
"context"
"time"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/types"
)
type (
ApplicationRepository interface {
With(ctx context.Context, db *factory.DB) ApplicationRepository
FindByID(id uint64) (*types.Application, error)
Find() (types.ApplicationSet, error)
Create(mod *types.Application) (*types.Application, error)
Update(mod *types.Application) (*types.Application, error)
DeleteByID(id uint64) error
}
application struct {
*repository
// sql table reference
applications string
members string
}
)
const (
sqlApplicationColumns = "id, rel_owner, name, enabled, unify, created_at, updated_at, deleted_at"
sqlApplicationScope = "deleted_at IS NULL"
ErrApplicationNotFound = repositoryError("ApplicationNotFound")
)
func Application(ctx context.Context, db *factory.DB) ApplicationRepository {
return (&application{}).With(ctx, db)
}
func (r *application) With(ctx context.Context, db *factory.DB) ApplicationRepository {
return &application{
repository: r.repository.With(ctx, db),
applications: "sys_application",
}
}
func (r *application) FindByID(id uint64) (*types.Application, error) {
sql := "SELECT " + sqlApplicationColumns + " FROM " + r.applications + " WHERE id = ? AND " + sqlApplicationScope
mod := &types.Application{}
return mod, isFound(r.db().Get(mod, sql, id), mod.ID > 0, ErrApplicationNotFound)
}
func (r *application) Find() (types.ApplicationSet, error) {
rval := make([]*types.Application, 0)
params := make([]interface{}, 0)
sql := "SELECT " + sqlApplicationColumns + " FROM " + r.applications + " WHERE " + sqlApplicationScope
sql += " ORDER BY id ASC"
return rval, r.db().Select(&rval, sql, params...)
}
func (r *application) Create(mod *types.Application) (*types.Application, error) {
mod.ID = factory.Sonyflake.NextID()
mod.CreatedAt = time.Now()
return mod, r.db().Insert(r.applications, mod)
}
func (r *application) Update(mod *types.Application) (*types.Application, error) {
mod.UpdatedAt = timeNowPtr()
return mod, r.db().Replace(r.applications, mod)
}
func (r *application) DeleteByID(id uint64) error {
return r.updateColumnByID(r.applications, "deleted_at", time.Now(), id)
}
+66
View File
@@ -0,0 +1,66 @@
package repository
import (
"context"
"testing"
"github.com/titpetric/factory"
"github.com/crusttech/crust/internal/test"
"github.com/crusttech/crust/system/types"
)
func TestApplication(t *testing.T) {
if testing.Short() {
t.Skip("skipping test in short mode.")
return
}
crepo := Application(context.Background(), factory.Database.MustGet())
{
tx(t, func() (err error) {
if _, err = factory.Database.MustGet().Exec("TRUNCATE sys_application"); err != nil {
return
}
app := &types.Application{
Name: "created",
Enabled: true,
OwnerID: 1,
Unify: &types.ApplicationUnify{
Name: "created",
Listed: true,
Order: 1,
Icon: "...ico",
},
}
app, err = crepo.Create(app)
test.NoError(t, err, "Application.Create error: %+v", err)
test.Assert(t, app.Valid(), "Expecting application to be valid after creation")
test.Assert(t, app.Name == "created", "Expecting application name to be set, got %q", app.Name)
test.Assert(t, app.Enabled, "Expecting application to be enabled")
test.Assert(t, app.Unify.Name == "created", "Expecting application name to be set in unify, got %q", app.Name)
test.Assert(t, app.Unify.Listed, "Expecting application to be listed in unify")
test.Assert(t, app.Unify.Order == 1, "Expecting application name to have order val 1")
app.Name = "updated"
app.Enabled = false
app.Unify.Name = "updated"
app.Unify.Listed = false
app, err = crepo.Update(app)
test.NoError(t, err, "Application.Create error: %+v", err)
test.Assert(t, err == nil, "Application.Create error: %+v", err)
test.Assert(t, app.Name == "updated", "Expecting application name to be updated")
test.Assert(t, !app.Enabled, "Expecting application to be disabled")
test.Assert(t, app.Unify.Name == "updated", "Expecting application name to be updated in unify")
test.Assert(t, !app.Unify.Listed, "Expecting application to be unlisted in unify")
return nil
})
}
}
+71
View File
@@ -0,0 +1,71 @@
package rest
import (
"context"
"github.com/crusttech/crust/system/rest/request"
"github.com/crusttech/crust/system/service"
"github.com/crusttech/crust/system/types"
"github.com/pkg/errors"
)
var _ = errors.Wrap
type Application struct {
svc struct {
application service.ApplicationService
}
}
func (Application) New() *Application {
ctrl := &Application{}
ctrl.svc.application = service.DefaultApplication
return ctrl
}
func (ctrl *Application) List(ctx context.Context, r *request.ApplicationList) (interface{}, error) {
return ctrl.svc.application.With(ctx).Find()
}
func (ctrl *Application) Create(ctx context.Context, r *request.ApplicationCreate) (interface{}, error) {
app := &types.Application{
Name: r.Name,
Enabled: r.Enabled,
}
if r.Unify != nil {
app.Unify = &types.ApplicationUnify{}
if err := r.Unify.Unmarshal(app.Unify); err != nil {
return nil, err
}
}
return ctrl.svc.application.With(ctx).Create(app)
}
func (ctrl *Application) Update(ctx context.Context, r *request.ApplicationUpdate) (interface{}, error) {
app := &types.Application{
ID: r.ApplicationID,
Name: r.Name,
Enabled: r.Enabled,
}
if r.Unify != nil {
app.Unify = &types.ApplicationUnify{}
if err := r.Unify.Unmarshal(app.Unify); err != nil {
return nil, err
}
}
return ctrl.svc.application.With(ctx).Update(app)
}
func (ctrl *Application) Read(ctx context.Context, r *request.ApplicationRead) (interface{}, error) {
return ctrl.svc.application.With(ctx).FindByID(r.ApplicationID)
}
func (ctrl *Application) Delete(ctx context.Context, r *request.ApplicationDelete) (interface{}, error) {
return nil, ctrl.svc.application.With(ctx).DeleteByID(r.ApplicationID)
}
+97
View File
@@ -0,0 +1,97 @@
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 `application.go`, `application.util.go` or `application_test.go` to
implement your API calls, helper functions and tests. The file `application.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"context"
"github.com/go-chi/chi"
"net/http"
"github.com/titpetric/factory/resputil"
"github.com/crusttech/crust/system/rest/request"
)
// Internal API interface
type ApplicationAPI interface {
List(context.Context, *request.ApplicationList) (interface{}, error)
Create(context.Context, *request.ApplicationCreate) (interface{}, error)
Update(context.Context, *request.ApplicationUpdate) (interface{}, error)
Read(context.Context, *request.ApplicationRead) (interface{}, error)
Delete(context.Context, *request.ApplicationDelete) (interface{}, error)
}
// HTTP API interface
type Application struct {
List func(http.ResponseWriter, *http.Request)
Create func(http.ResponseWriter, *http.Request)
Update func(http.ResponseWriter, *http.Request)
Read func(http.ResponseWriter, *http.Request)
Delete func(http.ResponseWriter, *http.Request)
}
func NewApplication(ah ApplicationAPI) *Application {
return &Application{
List: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewApplicationList()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.List(r.Context(), params)
})
},
Create: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewApplicationCreate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Create(r.Context(), params)
})
},
Update: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewApplicationUpdate()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Update(r.Context(), params)
})
},
Read: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewApplicationRead()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Read(r.Context(), params)
})
},
Delete: func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
params := request.NewApplicationDelete()
resputil.JSON(w, params.Fill(r), func() (interface{}, error) {
return ah.Delete(r.Context(), params)
})
},
}
}
func (ah *Application) MountRoutes(r chi.Router, middlewares ...func(http.Handler) http.Handler) {
r.Group(func(r chi.Router) {
r.Use(middlewares...)
r.Route("/application", func(r chi.Router) {
r.Get("/", ah.List)
r.Post("/", ah.Create)
r.Put("/{applicationID}", ah.Update)
r.Get("/{applicationID}", ah.Read)
r.Delete("/{applicationID}", ah.Delete)
})
})
}
+290
View File
@@ -0,0 +1,290 @@
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 `application.go`, `application.util.go` or `application_test.go` to
implement your API calls, helper functions and tests. The file `application.go`
is only generated the first time, and will not be overwritten if it exists.
*/
import (
"encoding/json"
"io"
"mime/multipart"
"net/http"
"strings"
"github.com/go-chi/chi"
"github.com/pkg/errors"
sqlxTypes "github.com/jmoiron/sqlx/types"
)
var _ = chi.URLParam
var _ = multipart.FileHeader{}
// Application list request parameters
type ApplicationList struct {
}
func NewApplicationList() *ApplicationList {
return &ApplicationList{}
}
func (apReq *ApplicationList) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(apReq)
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])
}
return err
}
var _ RequestFiller = NewApplicationList()
// Application create request parameters
type ApplicationCreate struct {
Name string
Enabled bool
Unify sqlxTypes.JSONText
Config sqlxTypes.JSONText
}
func NewApplicationCreate() *ApplicationCreate {
return &ApplicationCreate{}
}
func (apReq *ApplicationCreate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(apReq)
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["name"]; ok {
apReq.Name = val
}
if val, ok := post["enabled"]; ok {
apReq.Enabled = parseBool(val)
}
if val, ok := post["unify"]; ok {
if apReq.Unify, err = parseJSONTextWithErr(val); err != nil {
return err
}
}
if val, ok := post["config"]; ok {
if apReq.Config, err = parseJSONTextWithErr(val); err != nil {
return err
}
}
return err
}
var _ RequestFiller = NewApplicationCreate()
// Application update request parameters
type ApplicationUpdate struct {
ApplicationID uint64 `json:",string"`
Name string
Enabled bool
Unify sqlxTypes.JSONText
Config sqlxTypes.JSONText
}
func NewApplicationUpdate() *ApplicationUpdate {
return &ApplicationUpdate{}
}
func (apReq *ApplicationUpdate) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(apReq)
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])
}
apReq.ApplicationID = parseUInt64(chi.URLParam(r, "applicationID"))
if val, ok := post["name"]; ok {
apReq.Name = val
}
if val, ok := post["enabled"]; ok {
apReq.Enabled = parseBool(val)
}
if val, ok := post["unify"]; ok {
if apReq.Unify, err = parseJSONTextWithErr(val); err != nil {
return err
}
}
if val, ok := post["config"]; ok {
if apReq.Config, err = parseJSONTextWithErr(val); err != nil {
return err
}
}
return err
}
var _ RequestFiller = NewApplicationUpdate()
// Application read request parameters
type ApplicationRead struct {
ApplicationID uint64 `json:",string"`
}
func NewApplicationRead() *ApplicationRead {
return &ApplicationRead{}
}
func (apReq *ApplicationRead) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(apReq)
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])
}
apReq.ApplicationID = parseUInt64(chi.URLParam(r, "applicationID"))
return err
}
var _ RequestFiller = NewApplicationRead()
// Application delete request parameters
type ApplicationDelete struct {
ApplicationID uint64 `json:",string"`
}
func NewApplicationDelete() *ApplicationDelete {
return &ApplicationDelete{}
}
func (apReq *ApplicationDelete) Fill(r *http.Request) (err error) {
if strings.ToLower(r.Header.Get("content-type")) == "application/json" {
err = json.NewDecoder(r.Body).Decode(apReq)
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])
}
apReq.ApplicationID = parseUInt64(chi.URLParam(r, "applicationID"))
return err
}
var _ RequestFiller = NewApplicationDelete()
+1
View File
@@ -50,6 +50,7 @@ func MountRoutes(oidcConfig *config.OIDC, socialConfig *config.Social, jwtEncode
handlers.NewRole(Role{}.New()).MountRoutes(r)
handlers.NewOrganisation(Organisation{}.New()).MountRoutes(r)
handlers.NewPermissions(Permissions{}.New()).MountRoutes(r)
handlers.NewApplication(Application{}.New()).MountRoutes(r)
})
}
}
+90
View File
@@ -0,0 +1,90 @@
package service
import (
"context"
"github.com/titpetric/factory"
"github.com/crusttech/crust/system/repository"
"github.com/crusttech/crust/system/types"
)
type (
application struct {
db *factory.DB
ctx context.Context
application repository.ApplicationRepository
}
ApplicationService interface {
With(ctx context.Context) ApplicationService
FindByID(applicationID uint64) (*types.Application, error)
Find() (types.ApplicationSet, error)
Create(application *types.Application) (*types.Application, error)
Update(application *types.Application) (*types.Application, error)
DeleteByID(id uint64) error
}
)
func Application() ApplicationService {
return (&application{}).With(context.Background())
}
func (svc *application) With(ctx context.Context) ApplicationService {
db := repository.DB(ctx)
return &application{
db: db,
ctx: ctx,
application: repository.Application(ctx, db),
}
}
func (svc *application) FindByID(id uint64) (*types.Application, error) {
// @todo: permission check if current user has access to this application
return svc.application.FindByID(id)
}
func (svc *application) Find() (types.ApplicationSet, error) {
// @todo: permission check to return only applications that current user has access to
return svc.application.Find()
}
func (svc *application) Create(mod *types.Application) (*types.Application, error) {
// @todo: permission check if current user can add/edit application
return svc.application.Create(mod)
}
func (svc *application) Update(mod *types.Application) (t *types.Application, err error) {
// @todo: permission check if current user can add/edit application
// @todo: make sure archived & deleted entries can not be edited
return t, svc.db.Transaction(func() (err error) {
if t, err = svc.application.FindByID(mod.ID); err != nil {
return
}
// Assign changed values
t.Name = mod.Name
t.Enabled = mod.Enabled
t.Unify = mod.Unify
if t, err = svc.application.Update(t); err != nil {
return err
}
return nil
})
}
func (svc *application) DeleteByID(id uint64) error {
// @todo: make history unavailable
// @todo: notify users that application has been removed (remove from web UI)
// @todo: permissions check if current user can remove application
return svc.application.DeleteByID(id)
}
var _ ApplicationService = &application{}
+2
View File
@@ -17,6 +17,7 @@ var (
DefaultRole RoleService
DefaultPermissions PermissionsService
DefaultOrganisation OrganisationService
DefaultApplication ApplicationService
)
func Init() {
@@ -26,5 +27,6 @@ func Init() {
DefaultRole = Role()
DefaultPermissions = Permissions()
DefaultOrganisation = Organisation()
DefaultApplication = Application()
})
}
+6
View File
@@ -12,6 +12,7 @@ var (
"grant": true,
"organisation.create": true,
"role.create": true,
"application.create": true,
},
"system:organisation": map[string]bool{
"access": true,
@@ -22,6 +23,11 @@ var (
"delete": true,
"members.manage": true,
},
"application:role": map[string]bool{
"read": true,
"update": true,
"delete": true,
},
"messaging": map[string]bool{
"access": true,
"grant": true,
+67
View File
@@ -0,0 +1,67 @@
package types
// Hello! This file is auto-generated.
type (
// ApplicationSet slice of Application
//
// This type is auto-generated.
ApplicationSet []*Application
)
// Walk iterates through every slice item and calls w(Application) err
//
// This function is auto-generated.
func (set ApplicationSet) Walk(w func(*Application) error) (err error) {
for i := range set {
if err = w(set[i]); err != nil {
return
}
}
return
}
// Filter iterates through every slice item, calls f(Application) (bool, err) and return filtered slice
//
// This function is auto-generated.
func (set ApplicationSet) Filter(f func(*Application) (bool, error)) (out ApplicationSet, err error) {
var ok bool
out = ApplicationSet{}
for i := range set {
if ok, err = f(set[i]); err != nil {
return
} else if ok {
out = append(out, set[i])
}
}
return
}
// FindByID finds items from slice by its ID property
//
// This function is auto-generated.
func (set ApplicationSet) FindByID(ID uint64) *Application {
for i := range set {
if set[i].ID == ID {
return set[i]
}
}
return nil
}
// IDs returns a slice of uint64s from all items in the set
//
// This function is auto-generated.
func (set ApplicationSet) IDs() (IDs []uint64) {
IDs = make([]uint64, len(set))
for i := range set {
IDs[i] = set[i].ID
}
return
}
+59
View File
@@ -0,0 +1,59 @@
package types
import (
"database/sql/driver"
"encoding/json"
"time"
"github.com/pkg/errors"
)
type (
Application struct {
ID uint64 `json:"applicationID,string" db:"id"`
Name string `json:"name" db:"name"`
OwnerID uint64 `json:"ownerID" db:"rel_owner"`
Enabled bool `json:"enabled" db:"enabled"`
Unify *ApplicationUnify `json:"unify,omitempty" db:"unify"`
CreatedAt time.Time `json:"createdAt,omitempty" db:"created_at"`
UpdatedAt *time.Time `json:"updatedAt,omitempty" db:"updated_at"`
DeletedAt *time.Time `json:"deletedAt,omitempty" db:"deleted_at"`
}
ApplicationUnify struct {
Name string `json:"name,omitempty"`
Listed bool `json:"listed"`
Icon string `json:"icon"`
Logo string `json:"logo"`
Url string `json:"url"`
Config string `json:"config"`
Order uint `json:"order"`
}
)
func (u *Application) Valid() bool {
return u.ID > 0 && u.DeletedAt == nil
}
func (u *Application) Identity() uint64 {
return u.ID
}
func (au *ApplicationUnify) Scan(value interface{}) error {
switch value.(type) {
case nil:
au = nil
case []uint8:
if err := json.Unmarshal(value.([]byte), au); err != nil {
return errors.Wrapf(err, "Can not scan '%v' into ApplicationUnify", value)
}
}
return nil
}
func (au ApplicationUnify) Value() (driver.Value, error) {
return json.Marshal(au)
}