Skip to content

Commit

Permalink
Rename admin to service
Browse files Browse the repository at this point in the history
  • Loading branch information
jinzhu committed Mar 3, 2016
0 parents commit cf36a0f
Show file tree
Hide file tree
Showing 177 changed files with 18,387 additions and 0 deletions.
443 changes: 443 additions & 0 deletions README.md

Large diffs are not rendered by default.

67 changes: 67 additions & 0 deletions action.go
@@ -0,0 +1,67 @@
package admin

import (
"fmt"
"reflect"

"github.com/qor/qor"
"github.com/qor/qor/utils"
"github.com/qor/roles"
)

// Action register action for qor resource
func (res *Resource) Action(action *Action) {
if action.Label == "" {
action.Label = utils.HumanizeString(action.Name)
}
res.Actions = append(res.Actions, action)
}

// ActionArgument action argument that used in handle
type ActionArgument struct {
PrimaryValues []string
Context *Context
Argument interface{}
}

// Action action definiation
type Action struct {
Name string
Label string
Handle func(arg *ActionArgument) error
Resource *Resource
Permission *roles.Permission
Visibles []string
}

// ToParam used to register routes for actions
func (action Action) ToParam() string {
return utils.ToParamString(action.Name)
}

// HasPermission check if current user has permission for the action
func (action Action) HasPermission(mode roles.PermissionMode, context *qor.Context) bool {
if action.Permission == nil {
return true
}
return action.Permission.HasPermission(mode, context.Roles...)
}

// FindSelectedRecords find selected records when run bulk actions
func (actionArgument *ActionArgument) FindSelectedRecords() []interface{} {
var (
context = actionArgument.Context
resource = context.Resource
records = []interface{}{}
)

clone := context.clone()
clone.SetDB(clone.GetDB().Where(fmt.Sprintf("%v IN (?)", resource.PrimaryDBName()), actionArgument.PrimaryValues))
results, _ := clone.FindMany()

resultValues := reflect.Indirect(reflect.ValueOf(results))
for i := 0; i < resultValues.Len(); i++ {
records = append(records, resultValues.Index(i).Interface())
}
return records
}
190 changes: 190 additions & 0 deletions admin.go
@@ -0,0 +1,190 @@
package admin

import (
"html/template"
"reflect"

"github.com/jinzhu/inflection"
"github.com/qor/qor"
"github.com/qor/qor/resource"
"github.com/qor/qor/utils"
"github.com/theplant/cldr"
)

// Admin is a struct that used to generate admin/api interface
type Admin struct {
Config *qor.Config
SiteName string
I18n I18n
menus []*Menu
resources []*Resource
searchResources []*Resource
auth Auth
router *Router
funcMaps template.FuncMap
metaConfigorMaps map[string]func(*Meta)
}

// ResourceNamer is an interface for models that defined method `ResourceName`
type ResourceNamer interface {
ResourceName() string
}

// New new admin with configuration
func New(config *qor.Config) *Admin {
admin := Admin{
funcMaps: make(template.FuncMap),
Config: config,
router: newRouter(),
metaConfigorMaps: metaConfigorMaps,
}
return &admin
}

// SetSiteName set site's name, the name will be used as admin HTML title and admin interface will auto load javascripts, stylesheets files based on its value
// For example, if you named it as `Qor Demo`, admin will look up `qor_demo.js`, `qor_demo.css` in QOR view paths, and load them if found
func (admin *Admin) SetSiteName(siteName string) {
admin.SiteName = siteName
}

// SetAuth set admin's authorization gateway
func (admin *Admin) SetAuth(auth Auth) {
admin.auth = auth
}

// RegisterMetaConfigor register configor for a kind, it will be called when register those kind of metas
func (admin *Admin) RegisterMetaConfigor(kind string, fc func(*Meta)) {
admin.metaConfigorMaps[kind] = fc
}

// RegisterFuncMap register view funcs, it could be used in view templates
func (admin *Admin) RegisterFuncMap(name string, fc interface{}) {
admin.funcMaps[name] = fc
}

// GetRouter get router from admin
func (admin *Admin) GetRouter() *Router {
return admin.router
}

// NewResource initialize a new qor resource, won't add it to admin, just initialize it
func (admin *Admin) NewResource(value interface{}, config ...*Config) *Resource {
var configuration *Config
if len(config) > 0 {
configuration = config[0]
}

if configuration == nil {
configuration = &Config{}
}

res := &Resource{
Resource: *resource.New(value),
Config: configuration,
cachedMetas: &map[string][]*Meta{},
filters: map[string]*Filter{},
admin: admin,
}

res.Permission = configuration.Permission

if configuration.PageCount == 0 {
configuration.PageCount = 20
}

if configuration.Name != "" {
res.Name = configuration.Name
} else if namer, ok := value.(ResourceNamer); ok {
res.Name = namer.ResourceName()
}

// Configure resource when initializing
modelType := admin.Config.DB.NewScope(res.Value).GetModelStruct().ModelType
for i := 0; i < modelType.NumField(); i++ {
if fieldStruct := modelType.Field(i); fieldStruct.Anonymous {
if injector, ok := reflect.New(fieldStruct.Type).Interface().(resource.ConfigureResourceBeforeInitializeInterface); ok {
injector.ConfigureQorResourceBeforeInitialize(res)
}
}
}

if injector, ok := res.Value.(resource.ConfigureResourceBeforeInitializeInterface); ok {
injector.ConfigureQorResourceBeforeInitialize(res)
}

findOneHandler := res.FindOneHandler
res.FindOneHandler = func(result interface{}, metaValues *resource.MetaValues, context *qor.Context) error {
if context.ResourceID == "" {
context.ResourceID = res.GetPrimaryValue(context.Request)
}
return findOneHandler(result, metaValues, context)
}

return res
}

// AddResource make a model manageable from admin interface
func (admin *Admin) AddResource(value interface{}, config ...*Config) *Resource {
res := admin.NewResource(value, config...)

if !res.Config.Invisible {
var menuName string
if res.Config.Singleton {
menuName = res.Name
} else {
menuName = inflection.Plural(res.Name)
}

menu := &Menu{rawPath: res.ToParam(), Name: menuName}
admin.menus = appendMenu(admin.menus, res.Config.Menu, menu)
}

admin.resources = append(admin.resources, res)
return res
}

// AddSearchResource make a resource searchable from search center
func (admin *Admin) AddSearchResource(resources ...*Resource) {
admin.searchResources = append(admin.searchResources, resources...)
}

func (admin *Admin) EnabledSearchCenter() bool {
return len(admin.searchResources) > 0
}

// GetResource get resource with name
func (admin *Admin) GetResource(name string) *Resource {
for _, res := range admin.resources {
var typeName = reflect.Indirect(reflect.ValueOf(res.Value)).Type().String()
if res.ToParam() == name || res.Name == name || typeName == name {
return res
}
}
return nil
}

// GetResources get defined resources from admin
func (admin *Admin) GetResources() []*Resource {
return admin.resources
}

// I18n define admin's i18n interface
type I18n interface {
Scope(scope string) I18n
Default(value string) I18n
T(locale string, key string, args ...interface{}) template.HTML
}

// T call i18n backend to translate
func (admin *Admin) T(context *qor.Context, key string, value string, values ...interface{}) template.HTML {
locale := utils.GetLocale(context)

if admin.I18n == nil {
if result, err := cldr.Parse(locale, value, values...); err == nil {
return template.HTML(result)
}
return template.HTML(key)
}

return admin.I18n.Default(value).T(locale, key, values...)
}
78 changes: 78 additions & 0 deletions admin_test.go
@@ -0,0 +1,78 @@
package admin

import (
"testing"

"github.com/qor/qor"
"github.com/qor/qor/test/utils"
)

type User struct {
Name string
ID uint64
}

var db = utils.TestDB()

func TestAddResource(t *testing.T) {
admin := New(&qor.Config{DB: db})
user := admin.AddResource(&User{})

if user != admin.resources[0] {
t.Error("resource not added")
}

if admin.GetMenus()[0].Name != "Users" {
t.Error("resource not added to menu")
}
}

func TestAddResourceWithInvisibleOption(t *testing.T) {
admin := New(&qor.Config{DB: db})
user := admin.AddResource(&User{}, &Config{Invisible: true})

if user != admin.resources[0] {
t.Error("resource not added")
}

if len(admin.GetMenus()) != 0 {
t.Error("invisible resource registered in menu")
}
}

func TestGetResource(t *testing.T) {
admin := New(&qor.Config{DB: db})
user := admin.AddResource(&User{})

if admin.GetResource("User") != user {
t.Error("resource not returned")
}
}

func TestNewResource(t *testing.T) {
admin := New(&qor.Config{DB: db})
user := admin.NewResource(&User{})

if user.Name != "User" {
t.Error("default resource name didn't set")
}

if user.Config.PageCount != 20 {
t.Error("default page count didn't set")
}
}

type UserWithCustomizedName struct{}

func (u *UserWithCustomizedName) ResourceName() string {
return "CustomizedName"
}

func TestNewResourceWithCustomizedName(t *testing.T) {
admin := New(&qor.Config{DB: db})
user := admin.NewResource(&UserWithCustomizedName{})

if user.Name != "CustomizedName" {
t.Error("customize resource name didn't set")
}
}
11 changes: 11 additions & 0 deletions auth.go
@@ -0,0 +1,11 @@
package admin

import "github.com/qor/qor"

// Auth is an auth interface that used to qor admin
// If you want to implement an authorization gateway for admin interface, you could implement this interface, and set it to the admin with `admin.SetAuth(auth)`
type Auth interface {
GetCurrentUser(*Context) qor.CurrentUser
LoginURL(*Context) string
LogoutURL(*Context) string
}
14 changes: 14 additions & 0 deletions config.go
@@ -0,0 +1,14 @@
package admin

import "github.com/qor/roles"

// Config admin config struct
type Config struct {
Name string
Menu []string
Invisible bool
Permission *roles.Permission
Themes []string
PageCount int
Singleton bool
}

0 comments on commit cf36a0f

Please sign in to comment.