Compare commits

...

11 Commits
v1.155.0 ... ts

Author SHA1 Message Date
8435f7b5a2 Merge pull request #7 from nodece/feat_application
feat: add application module
2020-11-24 14:48:25 +00:00
98734837a8 Merge pull request #6 from nodece/improve_user_style
fix: improve user style
2020-11-24 14:04:29 +00:00
218b96ed74 feat: add application
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-11-23 23:50:59 +08:00
89476a79ad fix: improve user style
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-11-23 22:10:11 +08:00
b003838b00 Merge pull request #5 from nodece/master
refactor: adjust the structure for decoupling
2020-11-12 00:09:18 +08:00
5efc866a84 refactor: adjust the structure for decoupling
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-11-11 21:38:51 +08:00
d77278fb93 Merge pull request #4 from nodece/master
feat: building the main layout
2020-11-07 11:08:54 +08:00
17b974f895 feat: building the main layout
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-11-07 01:08:03 +08:00
046ed8fe3b Merge pull request #3 from nodece/master
refactor: migrate from Beego to Gin
2020-11-01 10:41:43 +08:00
2422943a59 refactor: migrate from Beego to Gin
Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-11-01 01:09:49 +08:00
723c68822c refactor: migrate to TypeScript (#2)
* refactor: migrate to TypeScript

Signed-off-by: Zixuan Liu <nodeces@gmail.com>

* fix: cors

Signed-off-by: Zixuan Liu <nodeces@gmail.com>
2020-10-31 23:20:40 +08:00
55 changed files with 5543 additions and 4400 deletions

2
.env.template Normal file
View File

@ -0,0 +1,2 @@
HTTP_PORT=:7000
DB_DATABASE_SOURCE=root:123@tcp(localhost:3306)/casdoor

4
.gitignore vendored
View File

@ -22,4 +22,6 @@ tmpFiles/
*.tmp
logs/
lastupdate.tmp
commentsRouter*.go
commentsRouter*.go
.env

View File

@ -1 +1,36 @@
# casdoor
# Casdoor
## Development
### Prerequisites
Go (1.15 or later)
MySQL (5.8 or higher)
Node.js (version 8 or higher)
Yarn
### Initialize project
Create a database named casdoor.
```sql
CREATE DATABASE IF NOT EXISTS casdoor default charset utf8 COLLATE utf8_general_ci
```
Configure database source in the `.env` file. If you have not copied the `.env.template` file to a new file named `.env`, which should now be performed.
### Run project
#### backend
```shell
go run cmd/main.go
```
### frontend
```shell
cd web
yarn
yarn start
```

68
cmd/main.go Normal file
View File

@ -0,0 +1,68 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"
"github.com/casdoor/casdoor/internal/config"
"github.com/casdoor/casdoor/internal/handler"
"github.com/casdoor/casdoor/internal/store"
"github.com/casdoor/casdoor/internal/store/shared"
)
func main() {
cfg, err := config.NewConfig()
if err != nil {
log.Fatal(err)
}
db, err := shared.NewDB(cfg)
if err != nil {
log.Fatal(err)
}
userStore := store.NewUserStore(db)
applicationStore := store.NewApplicationStore(db)
srv := &http.Server{
Addr: cfg.HTTPPort,
Handler: handler.New(userStore, applicationStore),
}
go func() {
log.Printf("listen and serve on %s", srv.Addr)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("listen: %s\n", err)
}
}()
quit := make(chan os.Signal)
signal.Notify(quit, os.Interrupt)
<-quit
log.Println("Shutdown Server ...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatal("Server Shutdown:", err)
}
log.Println("Server exiting")
}

View File

@ -1,6 +0,0 @@
appname = casdoor
httpport = 7000
runmode = dev
SessionOn = true
copyrequestbody = true
dataSourceName = root:123@tcp(localhost:3306)/

View File

@ -1,70 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import (
"encoding/json"
"github.com/casdoor/casdoor/object"
)
func (c *ApiController) GetUsers() {
owner := c.Input().Get("owner")
c.Data["json"] = object.GetUsers(owner)
c.ServeJSON()
}
func (c *ApiController) GetUser() {
id := c.Input().Get("id")
c.Data["json"] = object.GetUser(id)
c.ServeJSON()
}
func (c *ApiController) UpdateUser() {
id := c.Input().Get("id")
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
}
c.Data["json"] = object.UpdateUser(id, &user)
c.ServeJSON()
}
func (c *ApiController) AddUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
}
c.Data["json"] = object.AddUser(&user)
c.ServeJSON()
}
func (c *ApiController) DeleteUser() {
var user object.User
err := json.Unmarshal(c.Ctx.Input.RequestBody, &user)
if err != nil {
panic(err)
}
c.Data["json"] = object.DeleteUser(&user)
c.ServeJSON()
}

15
go.mod
View File

@ -3,8 +3,21 @@ module github.com/casdoor/casdoor
go 1.15
require (
github.com/astaxie/beego v1.12.2
github.com/gin-contrib/cors v1.3.1
github.com/gin-gonic/gin v1.6.3
github.com/go-playground/validator/v10 v10.4.0 // indirect
github.com/go-sql-driver/mysql v1.5.0
github.com/golang/protobuf v1.4.3 // indirect
github.com/google/uuid v1.1.2
github.com/joho/godotenv v1.3.0
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/ugorji/go v1.1.12 // indirect
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 // indirect
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 // indirect
google.golang.org/protobuf v1.25.0 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
xorm.io/core v0.7.2
xorm.io/xorm v0.8.1
)

184
go.sum
View File

@ -1,55 +1,53 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.37.4 h1:glPeL3BQJsbF6aIIYfZizMwc5LTYz250bDMjttbBGAU=
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/astaxie/beego v1.12.2 h1:CajUexhSX5ONWDiSCpeQBNVfTzOtPb9e9d+3vuU5FuU=
github.com/astaxie/beego v1.12.2/go.mod h1:TMcqhsbhN3UFpN+RCfysaxPAbrhox6QSS3NIAEp/uzE=
github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4 h1:YcpmyvADGYw5LqMnHqSkyIELsHCGF6PkrmM31V8rF7o=
github.com/denisenkom/go-mssqldb v0.0.0-20190707035753-2be1aa521ff4/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
github.com/gin-contrib/cors v1.3.1 h1:doAsuITavI4IOcd0Y19U4B+O0dNWihRyX//nn4sEmgA=
github.com/gin-contrib/cors v1.3.1/go.mod h1:jjEJ4268OPZUcU7k9Pm653S7lXUGcqMADzFA61xsmDk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.5.0/go.mod h1:Nd6IXA8m5kNZdNEHMBd93KT+mdY3+bewLgRvmCsR2Do=
github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.0 h1:72qIR/m8ybvL8L5TIyfgrigqkrw7kVYAvjEvpT85l70=
github.com/go-playground/validator/v10 v10.4.0/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -57,117 +55,117 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.7.0 h1:wCi7urQOGBsYcQROHqpUUX4ct84xp40t9R9JX0FuA/U=
github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0 h1:RyRA7RzGXQZiW+tGMr7sxa85G1z0yOpM1qq5c8lNawc=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644 h1:X+yvsM2yrEktyI+b2qND5gpH8YhURn0k8OCaeRnkINo=
github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.1.12 h1:CzCiySZwEUgSwlgT8bTjFfV3rR6+Ti0atNsQkCRJnek=
github.com/ugorji/go v1.1.12/go.mod h1:Ne4Hz4JKQXNr/qi+hC0ovwseF9muoPjAZp2lylyih70=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.1.12 h1:pv4DBnMb5X9XXCNC0DyEmhU3I/61gWDdyH7iZps5DLs=
github.com/ugorji/go/codec v1.1.12/go.mod h1:U/SFD954ms+MwaHihwfeIz/sGz5OFgHt81tHc+Duy5k=
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897 h1:pLI5jrR7OSLijeIDcmRxNmw2api+jEfxLoykJVice/E=
golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -177,9 +175,6 @@ golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -187,7 +182,6 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -195,21 +189,22 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1 h1:ogLJMz+qpzav7lGMh10LMvAkM/fAoGlaiiHYiFYdm80=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418 h1:HlFl4V6pEMziuLXyRkm5BIYq1y1GAbb02pRlWvI54OM=
golang.org/x/sys v0.0.0-20201029080932-201ba4db2418/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2 h1:z99zHgr7hKfrUcX/KsoJk5FJfjTceCKIp96+biqP4To=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20200117065230-39095c1d176c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
@ -218,32 +213,39 @@ google.golang.org/appengine v1.6.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE=
gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
xorm.io/builder v0.3.6 h1:ha28mQ2M+TFx96Hxo+iq6tQgnkC9IZkM6D8w9sKHHF8=
xorm.io/builder v0.3.6/go.mod h1:LEFAPISnRzG+zxaxj2vPicRwz67BdhFreKg8yv8/TgU=
xorm.io/core v0.7.2 h1:mEO22A2Z7a3fPaZMk6gKL/jMD80iiyNwRrX5HOv3XLw=

48
internal/config/config.go Normal file
View File

@ -0,0 +1,48 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"errors"
"fmt"
"os"
"github.com/joho/godotenv"
)
// Config holds shared configuration on server.
type Config struct {
HTTPPort string
DBDataSource string
}
// NewConfig reads shared configuration from .env file.
func NewConfig() (*Config, error) {
err := godotenv.Load()
if err != nil {
return nil, fmt.Errorf("godotenv.Load: %v", err)
}
cfg := &Config{
DBDataSource: os.Getenv("DB_DATABASE_SOURCE"),
HTTPPort: os.Getenv("HTTP_PORT"),
}
if cfg.DBDataSource == "" {
return nil, errors.New("DB_DATABASE_SOURCE is empty")
}
return cfg, nil
}

46
internal/handler/api.go Normal file
View File

@ -0,0 +1,46 @@
package handler
import (
"net/http"
"github.com/casdoor/casdoor/internal/handler/application"
"github.com/casdoor/casdoor/internal/handler/user"
"github.com/casdoor/casdoor/internal/store"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
var corsConfig = cors.Config{
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials
AllowOrigins: []string{"http://localhost:3000"},
MaxAge: 300,
}
func New(userStore *store.UserStore, applicationStore *store.ApplicationStore) http.Handler {
r := gin.New()
r.Use(gin.Logger())
r.Use(gin.Recovery())
r.Use(cors.New(corsConfig))
//r.StaticFS("/", http.Dir("web/build/index.html"))
apiGroup := r.Group("/api")
userHandler := user.New(userStore)
apiGroup.GET("/get-users", userHandler.GetUsers)
apiGroup.GET("/get-user", userHandler.GetUser)
apiGroup.POST("/update-user", userHandler.UpdateUser)
apiGroup.POST("/add-user", userHandler.AddUser)
apiGroup.POST("/delete-user", userHandler.DeleteUser)
applicationHandler := application.New(applicationStore)
apiGroup.GET("/applications", applicationHandler.List)
apiGroup.POST("/applications", applicationHandler.Create)
return r
}

View File

@ -0,0 +1,62 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package application
import (
"net/http"
"strconv"
"time"
"github.com/casdoor/casdoor/internal/object"
"github.com/casdoor/casdoor/internal/store"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
type Handler struct {
applicationStore *store.ApplicationStore
}
func New(applicationStore *store.ApplicationStore) *Handler {
return &Handler{applicationStore: applicationStore}
}
func (h *Handler) Create(g *gin.Context) {
app := &object.Application{}
err := g.BindJSON(app)
if err != nil {
_ = g.Error(err)
return
}
app.Id = uuid.New().String()
app.CreatedTime = time.Now().Format(time.RFC3339)
app.CreatedBy = "TODO"
err = h.applicationStore.Create(app)
if err != nil {
_ = g.Error(err)
return
}
}
func (h *Handler) List(g *gin.Context) {
limit, _ := strconv.Atoi(g.DefaultQuery("limit", "20"))
offset, _ := strconv.Atoi(g.DefaultQuery("offset", "0"))
apps, err := h.applicationStore.List(limit, offset)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, apps)
}

View File

@ -0,0 +1,85 @@
package user
import (
"net/http"
"github.com/casdoor/casdoor/internal/object"
"github.com/casdoor/casdoor/internal/store"
"github.com/gin-gonic/gin"
)
type Handler struct {
userStore *store.UserStore
}
func New(userStore *store.UserStore) *Handler {
return &Handler{userStore: userStore}
}
func (h *Handler) GetUsers(g *gin.Context) {
owner := g.GetString("owner")
user, err := h.userStore.GetUsers(owner)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, user)
}
func (h *Handler) GetUser(g *gin.Context) {
id := g.GetString("id")
u, err := h.userStore.GetUser(id)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, u)
}
func (h *Handler) UpdateUser(g *gin.Context) {
id := g.GetString("id")
var user object.User
err := g.BindJSON(&user)
if err != nil {
panic(err)
}
ok, err := h.userStore.UpdateUser(id, &user)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, ok)
}
func (h *Handler) AddUser(g *gin.Context) {
var user object.User
err := g.BindJSON(&user)
if err != nil {
panic(err)
}
ok, err := h.userStore.AddUser(&user)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, ok)
}
func (h *Handler) DeleteUser(g *gin.Context) {
var user object.User
err := g.BindJSON(&user)
if err != nil {
_ = g.Error(err)
return
}
ok, err := h.userStore.DeleteUser(&user)
if err != nil {
_ = g.Error(err)
return
}
g.JSON(http.StatusOK, ok)
}

View File

@ -0,0 +1,9 @@
package object
type Application struct {
Id string `xorm:"notnull pk" json:"id"`
Name string `xorm:"notnull pk" json:"name"`
CreatedTime string `json:"createdTime"`
CreatedBy string `xorm:"notnull" json:"createdBy"`
}

27
internal/object/user.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
}

View File

@ -0,0 +1,41 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package store
import (
"github.com/casdoor/casdoor/internal/object"
"github.com/casdoor/casdoor/internal/store/shared"
)
type ApplicationStore struct {
db *shared.DB
}
func NewApplicationStore(db *shared.DB) *ApplicationStore {
return &ApplicationStore{
db: db,
}
}
func (a *ApplicationStore) Create(app *object.Application) error {
_, err := a.db.GetEngine().Insert(app)
return err
}
func (a *ApplicationStore) List(limit, offset int) ([]*object.Application, error) {
var apps []*object.Application
err := a.db.GetEngine().Desc("created_time").Limit(limit, offset).Find(&apps)
return apps, err
}

View File

@ -0,0 +1,63 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package shared
import (
"fmt"
"github.com/casdoor/casdoor/internal/config"
"github.com/casdoor/casdoor/internal/object"
_ "github.com/go-sql-driver/mysql"
"xorm.io/xorm"
)
type DB struct {
engine *xorm.Engine
driverName string
dataSourceName string
}
func NewDB(cfg *config.Config) (*DB, error) {
db := &DB{
dataSourceName: cfg.DBDataSource,
driverName: "mysql",
}
engine, err := xorm.NewEngine(db.driverName, db.dataSourceName)
if err != nil {
return nil, fmt.Errorf("xorm.NewEngine: %v", err)
}
err = engine.Ping()
if err != nil {
return nil, fmt.Errorf("engine.Ping(): %v", err)
}
db.engine = engine
err = db.createTable()
if err != nil {
return nil, fmt.Errorf("db.createTable(): %v", err)
}
return db, nil
}
func (db *DB) GetEngine() *xorm.Engine {
return db.engine
}
func (db *DB) createTable() error {
err := db.engine.Sync2(new(object.User), new(object.Application))
return err
}

95
internal/store/user.go Normal file
View File

@ -0,0 +1,95 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package store
import (
"github.com/casdoor/casdoor/internal/object"
"github.com/casdoor/casdoor/internal/store/shared"
"github.com/casdoor/casdoor/pkg/util"
"xorm.io/core"
)
type UserStore struct {
db *shared.DB
}
func NewUserStore(db *shared.DB) *UserStore {
return &UserStore{
db: db,
}
}
func (u *UserStore) GetUsers(owner string) ([]*object.User, error) {
var users []*object.User
err := u.db.GetEngine().Desc("created_time").Find(&users, &object.User{Owner: owner})
if err != nil {
return nil, err
}
return users, nil
}
func (u *UserStore) getUser(owner string, name string) (*object.User, error) {
user := object.User{Owner: owner, Name: name}
existed, err := u.db.GetEngine().Get(&user)
if err != nil {
return nil, err
}
if existed {
return &user, nil
} else {
return nil, nil
}
}
func (u *UserStore) GetUser(id string) (*object.User, error) {
owner, name := util.GetOwnerAndNameFromId(id)
return u.getUser(owner, name)
}
func (u *UserStore) UpdateUser(id string, user *object.User) (bool, error) {
owner, name := util.GetOwnerAndNameFromId(id)
data, err := u.getUser(owner, name)
if err != nil || data == nil {
return false, err
}
_, err = u.db.GetEngine().Id(core.PK{owner, name}).AllCols().Update(user)
if err != nil {
return false, err
}
return true, nil
}
func (u *UserStore) AddUser(user *object.User) (bool, error) {
affected, err := u.db.GetEngine().Insert(user)
if err != nil {
return false, err
}
return affected != 0, nil
}
func (u *UserStore) DeleteUser(user *object.User) (bool, error) {
affected, err := u.db.GetEngine().Id(core.PK{user.Owner, user.Name}).Delete(&object.User{})
if err != nil {
return false, err
}
return affected != 0, nil
}

48
main.go
View File

@ -1,48 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"github.com/astaxie/beego"
"github.com/astaxie/beego/plugins/cors"
"github.com/casdoor/casdoor/object"
"github.com/casdoor/casdoor/routers"
_ "github.com/casdoor/casdoor/routers"
)
func main() {
object.InitAdapter()
beego.InsertFilter("*", beego.BeforeRouter, cors.Allow(&cors.Options{
AllowOrigins: []string{"*"},
AllowMethods: []string{"GET", "PUT", "PATCH"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
}))
//beego.DelStaticPath("/static")
beego.SetStaticPath("/static", "web/build/static")
// https://studygolang.com/articles/2303
beego.InsertFilter("/", beego.BeforeRouter, routers.TransparentStatic) // must has this for default page
beego.InsertFilter("/*", beego.BeforeRouter, routers.TransparentStatic)
beego.BConfig.WebConfig.Session.SessionProvider="file"
beego.BConfig.WebConfig.Session.SessionProviderConfig = "./tmp"
beego.BConfig.WebConfig.Session.SessionGCMaxLifetime = 3600 * 24 * 365
beego.Run()
}

View File

@ -1,96 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"runtime"
"github.com/astaxie/beego"
_ "github.com/go-sql-driver/mysql"
"xorm.io/xorm"
)
var adapter *Adapter
func InitAdapter() {
adapter = NewAdapter("mysql", beego.AppConfig.String("dataSourceName"))
}
// Adapter represents the MySQL adapter for policy storage.
type Adapter struct {
driverName string
dataSourceName string
engine *xorm.Engine
}
// finalizer is the destructor for Adapter.
func finalizer(a *Adapter) {
err := a.engine.Close()
if err != nil {
panic(err)
}
}
// NewAdapter is the constructor for Adapter.
func NewAdapter(driverName string, dataSourceName string) *Adapter {
a := &Adapter{}
a.driverName = driverName
a.dataSourceName = dataSourceName
// Open the DB, create it if not existed.
a.open()
// Call the destructor when the object is released.
runtime.SetFinalizer(a, finalizer)
return a
}
func (a *Adapter) createDatabase() error {
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName)
if err != nil {
return err
}
defer engine.Close()
_, err = engine.Exec("CREATE DATABASE IF NOT EXISTS casdoor default charset utf8 COLLATE utf8_general_ci")
return err
}
func (a *Adapter) open() {
if err := a.createDatabase(); err != nil {
panic(err)
}
engine, err := xorm.NewEngine(a.driverName, a.dataSourceName+"casdoor")
if err != nil {
panic(err)
}
a.engine = engine
a.createTable()
}
func (a *Adapter) close() {
a.engine.Close()
a.engine = nil
}
func (a *Adapter) createTable() {
err := a.engine.Sync2(new(User))
if err != nil {
panic(err)
}
}

View File

@ -1,94 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package object
import (
"github.com/casdoor/casdoor/util"
"xorm.io/core"
)
type User struct {
Owner string `xorm:"varchar(100) notnull pk" json:"owner"`
Name string `xorm:"varchar(100) notnull pk" json:"name"`
CreatedTime string `xorm:"varchar(100)" json:"createdTime"`
Password string `xorm:"varchar(100)" json:"password"`
PasswordType string `xorm:"varchar(100)" json:"passwordType"`
DisplayName string `xorm:"varchar(100)" json:"displayName"`
Email string `xorm:"varchar(100)" json:"email"`
Phone string `xorm:"varchar(100)" json:"phone"`
}
func GetUsers(owner string) []*User {
users := []*User{}
err := adapter.engine.Desc("created_time").Find(&users, &User{Owner: owner})
if err != nil {
panic(err)
}
return users
}
func getUser(owner string, name string) *User {
user := User{Owner: owner, Name: name}
existed, err := adapter.engine.Get(&user)
if err != nil {
panic(err)
}
if existed {
return &user
} else {
return nil
}
}
func GetUser(id string) *User {
owner, name := util.GetOwnerAndNameFromId(id)
return getUser(owner, name)
}
func UpdateUser(id string, user *User) bool {
owner, name := util.GetOwnerAndNameFromId(id)
if getUser(owner, name) == nil {
return false
}
_, err := adapter.engine.Id(core.PK{owner, name}).AllCols().Update(user)
if err != nil {
panic(err)
}
//return affected != 0
return true
}
func AddUser(user *User) bool {
affected, err := adapter.engine.Insert(user)
if err != nil {
panic(err)
}
return affected != 0
}
func DeleteUser(user *User) bool {
affected, err := adapter.engine.Id(core.PK{user.Owner, user.Name}).Delete(&User{})
if err != nil {
panic(err)
}
return affected != 0
}

View File

@ -1,41 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package routers
import (
"github.com/astaxie/beego"
"github.com/casdoor/casdoor/controllers"
)
func init() {
initAPI()
}
func initAPI() {
ns :=
beego.NewNamespace("/api",
beego.NSInclude(
&controllers.ApiController{},
),
)
beego.AddNamespace(ns)
beego.Router("/api/get-users", &controllers.ApiController{}, "GET:GetUsers")
beego.Router("/api/get-user", &controllers.ApiController{}, "GET:GetUser")
beego.Router("/api/update-user", &controllers.ApiController{}, "POST:UpdateUser")
beego.Router("/api/add-user", &controllers.ApiController{}, "POST:AddUser")
beego.Router("/api/delete-user", &controllers.ApiController{}, "POST:DeleteUser")
}

5
web/.prettierrc Normal file
View File

@ -0,0 +1,5 @@
{
"semi": true,
"singleQuote": true,
"printWidth": 140
}

46
web/README.md Normal file
View File

@ -0,0 +1,46 @@
# Getting Started with Create React App
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.\
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.\
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.\
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.\
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).

36
web/craco.config.js Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
const CracoLessPlugin = require('craco-less');
const emotionPresetOptions = {};
const emotionBabelPreset = require('@emotion/babel-preset-css-prop').default(undefined, emotionPresetOptions);
module.exports = {
babel: {
plugins: [...emotionBabelPreset.plugins],
},
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
javascriptEnabled: true,
},
},
},
},
],
};

View File

@ -4,25 +4,28 @@
"private": true,
"dependencies": {
"@ant-design/icons": "^4.2.2",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2",
"@emotion/core": "^10.1.1",
"@emotion/styled": "^10.0.27",
"antd": "^4.7.2",
"axios": "^0.21.0",
"history": "^5.0.0",
"moment": "^2.29.1",
"react": "^16.14.0",
"react": "^17.0.1",
"react-device-detect": "^1.14.0",
"react-dom": "^16.14.0",
"react-router-dom": "^5.2.0",
"react-scripts": "3.4.3"
"react-dom": "^17.0.1",
"react-router-dom": "^6.0.0-beta.0",
"twin.macro": "^1.12.0"
},
"scripts": {
"start": "set PORT=7001 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"start": "craco start",
"build": "craco build",
"test": "craco test"
},
"eslintConfig": {
"extends": "react-app"
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
@ -35,5 +38,20 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@craco/craco": "^5.7.0",
"@emotion/babel-preset-css-prop": "^10.1.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"craco-less": "^1.17.0",
"prettier": "2.1.2",
"react-scripts": "^4.0.0",
"typescript": "^4.0.3"
}
}

View File

@ -5,10 +5,7 @@
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<meta content="Web site created using create-react-app" name="description" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a

View File

@ -1,41 +0,0 @@
@import '~antd/dist/antd.css';
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
#parent-area {
position: relative;
min-height: 100vh;
}
#content-wrap {
padding-bottom: 70px; /* Footer height */
}
#footer {
position: absolute;
bottom: 0;
width: 100%;
height: 70px; /* Footer height */
}

View File

@ -1,289 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, {Component} from 'react';
import './App.css';
import * as Setting from "./Setting";
import {DownOutlined, LogoutOutlined, SettingOutlined} from '@ant-design/icons';
import {Avatar, BackTop, Dropdown, Layout, Menu} from 'antd';
import {Switch, Route, withRouter, Redirect} from 'react-router-dom'
import * as AccountBackend from "./backend/AccountBackend";
import UserListPage from "./UserListPage";
import UserEditPage from "./UserEditPage";
const { Header, Footer } = Layout;
class App extends Component {
constructor(props) {
super(props);
this.state = {
classes: props,
selectedMenuKey: 0,
account: undefined,
};
Setting.initServerUrl();
}
componentWillMount() {
this.updateMenuKey();
this.getAccount();
}
updateMenuKey() {
// eslint-disable-next-line no-restricted-globals
const uri = location.pathname;
if (uri === '/') {
this.setState({ selectedMenuKey: 0 });
} else if (uri.includes('users')) {
this.setState({ selectedMenuKey: 1 });
} else {
this.setState({ selectedMenuKey: -1 });
}
}
onLogined() {
this.getAccount();
}
onUpdateAccount(account) {
this.setState({
account: account
});
}
getAccount() {
AccountBackend.getAccount()
.then((res) => {
const account = Setting.parseJson(res.data);
if (window.location.pathname === '/' && account === null) {
Setting.goToLink("/");
}
this.setState({
account: account,
});
if (account !== undefined && account !== null) {
window.mouselogUserId = account.username;
}
});
}
logout() {
this.setState({
expired: false,
submitted: false,
});
AccountBackend.logout()
.then((res) => {
if (res.status === 'ok') {
this.setState({
account: null
});
Setting.showMessage("success", `Successfully logged out, redirected to homepage`);
Setting.goToLink("/");
} else {
Setting.showMessage("error", `Logout failed: ${res.msg}`);
}
});
}
handleRightDropdownClick(e) {
if (e.key === '0') {
this.props.history.push(`/account`);
} else if (e.key === '1') {
this.logout();
}
}
renderRightDropdown() {
const menu = (
<Menu onClick={this.handleRightDropdownClick.bind(this)}>
<Menu.Item key='0'>
<SettingOutlined />
My Account
</Menu.Item>
<Menu.Item key='1'>
<LogoutOutlined />
Logout
</Menu.Item>
</Menu>
);
return (
<Dropdown key="4" overlay={menu} >
<a className="ant-dropdown-link" href="#" style={{float: 'right'}}>
<Avatar style={{ backgroundColor: Setting.getAvatarColor(this.state.account.name), verticalAlign: 'middle' }} size="large">
{Setting.getShortName(this.state.account.name)}
</Avatar>
&nbsp;
&nbsp;
{Setting.isMobile() ? null : Setting.getShortName(this.state.account.name)} &nbsp; <DownOutlined />
&nbsp;
&nbsp;
&nbsp;
</a>
</Dropdown>
)
}
renderAccount() {
let res = [];
if (this.state.account !== null && this.state.account !== undefined) {
res.push(this.renderRightDropdown());
} else {
res.push(
<Menu.Item key="1" style={{float: 'right', marginRight: '20px'}}>
<a href="/register">
Register
</a>
</Menu.Item>
);
res.push(
<Menu.Item key="2" style={{float: 'right'}}>
<a href="/login">
Login
</a>
</Menu.Item>
);
res.push(
<Menu.Item key="4" style={{float: 'right'}}>
<a href="/">
Home
</a>
</Menu.Item>
);
}
return res;
}
renderMenu() {
let res = [];
// if (this.state.account === null || this.state.account === undefined) {
// return [];
// }
res.push(
<Menu.Item key="0">
<a href="/">
Home
</a>
</Menu.Item>
);
res.push(
<Menu.Item key="1">
<a href="/users">
Users
</a>
</Menu.Item>
);
return res;
}
renderHomeIfLogined(component) {
if (this.state.account !== null && this.state.account !== undefined) {
return <Redirect to='/' />
} else {
return component;
}
}
renderLoginIfNotLogined(component) {
if (this.state.account === null) {
return <Redirect to='/login' />
} else if (this.state.account === undefined) {
return null;
}
else {
return component;
}
}
isStartPages() {
return window.location.pathname.startsWith('/login') ||
window.location.pathname.startsWith('/register') ||
window.location.pathname === '/';
}
renderContent() {
return (
<div>
<Header style={{ padding: '0', marginBottom: '3px'}}>
{
Setting.isMobile() ? null : <a className="logo" href={"/"} />
}
<Menu
// theme="dark"
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
defaultSelectedKeys={[`${this.state.selectedMenuKey}`]}
style={{ lineHeight: '64px' }}
>
{
this.renderMenu()
}
{
this.renderAccount()
}
</Menu>
</Header>
<Switch>
<Route exact path="/users" component={UserListPage}/>
<Route exact path="/users/:userName" component={UserEditPage}/>
</Switch>
</div>
)
}
renderFooter() {
// How to keep your footer where it belongs ?
// https://www.freecodecamp.org/neyarnws/how-to-keep-your-footer-where-it-belongs-59c6aa05c59c/
return (
<Footer id="footer" style={
{
borderTop: '1px solid #e8e8e8',
backgroundColor: 'white',
textAlign: 'center',
}
}>
Made with <span style={{color: 'rgb(255, 255, 255)'}}></span> by <a style={{fontWeight: "bold", color: "black"}} target="_blank" href="https://casbin.org">Casbin</a>
</Footer>
)
}
render() {
return (
<div id="parent-area">
<BackTop />
<div id="content-wrap">
{
this.renderContent()
}
</div>
{
this.renderFooter()
}
</div>
);
}
}
export default withRouter(App);

View File

@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@ -1,167 +0,0 @@
import React from "react";
import {Button, Card, Col, Input, Row} from 'antd';
import {LinkOutlined} from "@ant-design/icons";
import * as UserBackend from "./backend/UserBackend";
import * as Setting from "./Setting";
class UserEditPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
userName: props.match.params.userName,
user: null,
tasks: [],
resources: [],
};
}
componentWillMount() {
this.getUser();
}
getUser() {
UserBackend.getUser("admin", this.state.userName)
.then((user) => {
this.setState({
user: user,
});
});
}
parseUserField(key, value) {
// if ([].includes(key)) {
// value = Setting.myParseInt(value);
// }
return value;
}
updateUserField(key, value) {
value = this.parseUserField(key, value);
let user = this.state.user;
user[key] = value;
this.setState({
user: user,
});
}
renderUser() {
return (
<Card size="small" title={
<div>
Edit User&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={this.submitUserEdit.bind(this)}>Save</Button>
</div>
} style={{marginLeft: '5px'}} type="inner">
<Row style={{marginTop: '10px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Name:
</Col>
<Col span={22} >
<Input value={this.state.user.name} onChange={e => {
this.updateUserField('name', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Password Type:
</Col>
<Col span={22} >
<Input value={this.state.user.passwordType} onChange={e => {
this.updateUserField('passwordType', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Password:
</Col>
<Col span={22} >
<Input value={this.state.user.password} onChange={e => {
this.updateUserField('password', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Display Name:
</Col>
<Col span={22} >
<Input value={this.state.user.displayName} onChange={e => {
this.updateUserField('displayName', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Email:
</Col>
<Col span={22} >
<Input value={this.state.user.email} onChange={e => {
this.updateUserField('email', e.target.value);
}} />
</Col>
</Row>
<Row style={{marginTop: '20px'}} >
<Col style={{marginTop: '5px'}} span={2}>
Phone:
</Col>
<Col span={22} >
<Input value={this.state.user.phone} onChange={e => {
this.updateUserField('phone', e.target.value);
}} />
</Col>
</Row>
</Card>
)
}
submitUserEdit() {
let user = Setting.deepCopy(this.state.user);
UserBackend.updateUser(this.state.user.owner, this.state.userName, user)
.then((res) => {
if (res) {
Setting.showMessage("success", `Successfully saved`);
this.setState({
userName: this.state.user.name,
});
this.props.history.push(`/users/${this.state.user.name}`);
} else {
Setting.showMessage("error", `failed to save: server side failure`);
this.updateUserField('name', this.state.userName);
}
})
.catch(error => {
Setting.showMessage("error", `failed to save: ${error}`);
});
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.state.user !== null ? this.renderUser() : null
}
</Col>
<Col span={1}>
</Col>
</Row>
<Row style={{margin: 10}}>
<Col span={2}>
</Col>
<Col span={18}>
<Button type="primary" size="large" onClick={this.submitUserEdit.bind(this)}>Save</Button>
</Col>
</Row>
</div>
);
}
}
export default UserEditPage;

View File

@ -1,185 +0,0 @@
import React from "react";
import {Button, Col, Popconfirm, Row, Table} from 'antd';
import moment from "moment";
import * as Setting from "./Setting";
import * as UserBackend from "./backend/UserBackend";
class UserListPage extends React.Component {
constructor(props) {
super(props);
this.state = {
classes: props,
users: null,
};
}
componentWillMount() {
this.getUsers();
}
getUsers() {
UserBackend.getUsers("admin")
.then((res) => {
this.setState({
users: res,
});
});
}
newUser() {
return {
owner: "admin", // this.props.account.username,
name: `user_${this.state.users.length}`,
createdTime: moment().format(),
password: "123456",
passwordType: "plain",
displayName: `New User - ${this.state.users.length}`,
email: "user@example.com",
phone: "1-12345678",
}
}
addUser() {
const newUser = this.newUser();
UserBackend.addUser(newUser)
.then((res) => {
Setting.showMessage("success", `User added successfully`);
this.setState({
users: Setting.prependRow(this.state.users, newUser),
});
}
)
.catch(error => {
Setting.showMessage("error", `User failed to add: ${error}`);
});
}
deleteUser(i) {
UserBackend.deleteUser(this.state.users[i])
.then((res) => {
Setting.showMessage("success", `User deleted successfully`);
this.setState({
users: Setting.deleteRow(this.state.users, i),
});
}
)
.catch(error => {
Setting.showMessage("error", `User failed to delete: ${error}`);
});
}
renderTable(users) {
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '120px',
sorter: (a, b) => a.name.localeCompare(b.name),
render: (text, record, index) => {
return (
<a href={`/users/${text}`}>{text}</a>
)
}
},
{
title: 'Created Time',
dataIndex: 'createdTime',
key: 'createdTime',
width: '160px',
sorter: (a, b) => a.createdTime.localeCompare(b.createdTime),
render: (text, record, index) => {
return Setting.getFormattedDate(text);
}
},
{
title: 'PasswordType',
dataIndex: 'passwordType',
key: 'passwordType',
width: '150px',
sorter: (a, b) => a.passwordType.localeCompare(b.passwordType),
},
{
title: 'Password',
dataIndex: 'password',
key: 'password',
width: '150px',
sorter: (a, b) => a.password.localeCompare(b.password),
},
{
title: 'Display Name',
dataIndex: 'displayName',
key: 'displayName',
// width: '100px',
sorter: (a, b) => a.displayName.localeCompare(b.displayName),
},
{
title: 'Email',
dataIndex: 'email',
key: 'email',
width: '150px',
sorter: (a, b) => a.email.localeCompare(b.email),
},
{
title: 'Phone',
dataIndex: 'phone',
key: 'phone',
width: '120px',
sorter: (a, b) => a.phone.localeCompare(b.phone),
},
{
title: 'Action',
dataIndex: '',
key: 'op',
width: '170px',
render: (text, record, index) => {
return (
<div>
<Button style={{marginTop: '10px', marginBottom: '10px', marginRight: '10px'}} type="primary" onClick={() => Setting.goToLink(`/users/${record.name}`)}>Edit</Button>
<Popconfirm
title={`Sure to delete user: ${record.name} ?`}
onConfirm={() => this.deleteUser(index)}
>
<Button style={{marginBottom: '10px'}} type="danger">Delete</Button>
</Popconfirm>
</div>
)
}
},
];
return (
<div>
<Table columns={columns} dataSource={users} rowKey="name" size="middle" bordered pagination={{pageSize: 100}}
title={() => (
<div>
Users&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" size="small" onClick={this.addUser.bind(this)}>Add</Button>
</div>
)}
loading={users === null}
/>
</div>
);
}
render() {
return (
<div>
<Row style={{width: "100%"}}>
<Col span={1}>
</Col>
<Col span={22}>
{
this.renderTable(this.state.users)
}
</Col>
<Col span={1}>
</Col>
</Row>
</div>
);
}
}
export default UserListPage;

222
web/src/app.tsx Normal file
View File

@ -0,0 +1,222 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { useEffect, useState } from 'react';
import * as Setting from './setting';
import { DownOutlined, LogoutOutlined, SettingOutlined } from '@ant-design/icons';
import { Avatar, BackTop, Dropdown, Layout, Menu } from 'antd';
import { Route, Routes, useNavigate } from 'react-router-dom';
import * as AccountBackend from './backend/account-backend';
import { UserRoutes } from './user/user-routes';
import tw from 'twin.macro';
const { Header, Footer } = Layout;
const { SubMenu } = Menu;
interface Account {
username: string;
name: string;
}
function isStartPages() {
return (
window.location.pathname.startsWith('/login') || window.location.pathname.startsWith('/register') || window.location.pathname === '/'
);
}
function AppHeader() {
return (
<Header css={tw`fixed w-screen px-0`}>
{/*eslint-disable-next-line*/}
{Setting.isMobile() ? null : <a href="/" className="logo" />}
<AppMenu />
</Header>
);
}
function AppMenu() {
const [selectedMenuKey, setSelectedMenuKey] = useState(0);
const [account, setAccount] = useState<Account | undefined>(undefined);
const navigate = useNavigate();
useEffect(() => {
// TODO: Waiting for consolidation backend
// if (window.location.pathname !== '/' && window.location.pathname !== '/login' && window.location.pathname !== '/register' && !account) {
// history.replace('/login');
// return;
// }
updateMenu();
// getAccount();
}, []);
function handleRightDropdownClick(e: any) {
if (e.key === 'account') {
navigate(`/account`);
} else if (e.key === 'logout') {
logout();
}
}
function updateMenu() {
const uri = window.location.pathname;
if (uri === '/') {
setSelectedMenuKey(0);
} else if (uri.includes('users')) {
setSelectedMenuKey(1);
} else {
setSelectedMenuKey(-1);
}
}
function getAccount() {
AccountBackend.getAccount().then((res) => {
const account = Setting.parseJson(res.data);
setAccount(account);
if (account) {
// @ts-ignore Mouselog plugins
window.mouselogUserId = account.username;
}
});
}
function logout() {
AccountBackend.logout().then((res) => {
// if (res.statusText === 'ok') {
// setAccount(undefined);
// Setting.showMessage('success', `Successfully logged out, redirected to homepage`);
// history.replace('/');
// } else {
// Setting.showMessage('error', `Logout failed: ${res.msg}`);
// }
});
}
return (
<div css={tw`h-16`}>
<Menu
// theme="dark"
mode={Setting.isMobile() && isStartPages() ? 'inline' : 'horizontal'}
defaultSelectedKeys={[`${selectedMenuKey}`]}
>
<Menu.Item key="home">
<a href="/">Home</a>
</Menu.Item>
{account ? (
<Dropdown
key="4"
overlay={
<Menu onClick={handleRightDropdownClick}>
<Menu.Item key="account">
<SettingOutlined />
My Account
</Menu.Item>
<Menu.Item key="logout">
<LogoutOutlined />
Logout
</Menu.Item>
</Menu>
}
>
{/*eslint-disable-next-line*/}
<a className="ant-dropdown-link" href="#" style={{ float: 'right' }}>
<Avatar
style={{
backgroundColor: Setting.getAvatarColor(account.name),
verticalAlign: 'middle',
}}
size="large"
>
{Setting.getShortName(account.name)}
</Avatar>
&nbsp; &nbsp;
{Setting.isMobile() ? null : Setting.getShortName(account.name)} &nbsp;
<DownOutlined />
&nbsp; &nbsp; &nbsp;
</a>
</Dropdown>
) : (
<>
<Menu.Item key="register" style={{ float: 'right', marginRight: '20px' }}>
<a href="/register">Register</a>
</Menu.Item>
<Menu.Item key="login" style={{ float: 'right' }}>
<a href="/login">Login</a>
</Menu.Item>
</>
)}
</Menu>
</div>
);
}
function AppFooter() {
return (
<Footer css={tw`bg-white text-center px-0 py-0`}>
<div>
Made with <span css={tw`text-white`}></span> by{' '}
<a css={tw`font-bold text-black`} rel="noreferrer" target="_blank" href="https://casbin.org">
Casbin
</a>
</div>
</Footer>
);
}
function App() {
const navigator = useNavigate();
return (
<>
<AppHeader />
<BackTop />
<div css={tw`flex h-screen pt-16`}>
<div css={tw`flex flex-col justify-between w-64 overflow-y-auto static py-4 shadow`}>
<Menu
onClick={({ key }) => navigator(key as string)}
style={{ border: 'none' }}
defaultSelectedKeys={['applications-list']}
defaultOpenKeys={['applications']}
mode="inline"
>
<SubMenu key="applications" title="Applications">
<Menu.Item key="applications-list">List</Menu.Item>
</SubMenu>
<SubMenu key="identities" title="Identities">
<Menu.Item key="groups">Groups</Menu.Item>
<Menu.Item key="users">Users</Menu.Item>
</SubMenu>
<SubMenu key="permissions" title="Permissions">
<Menu.Item key="authorization">Authorization</Menu.Item>
<Menu.Item key="policies">Policies</Menu.Item>
</SubMenu>
<SubMenu key="audit-logs" title="Audit Logs">
<Menu.Item key="user-activity">User Activity</Menu.Item>
<Menu.Item key="admin-activity">Admin Activity</Menu.Item>
</SubMenu>
</Menu>
<AppFooter />
</div>
<div css={tw`flex flex-1 flex-col overflow-x-hidden overflow-y-auto px-6 py-8`}>
<div css={tw`container ml-auto mr-auto`}>
<Routes>
<Route path="users/*" element={<UserRoutes />} />
</Routes>
</div>
</div>
</div>
</>
);
}
export default App;

View File

@ -1,52 +0,0 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import * as Setting from "../Setting";
export function getUser(username) {
return fetch(`${Setting.ServerUrl}/api/get-user?username=${username}`, {
method: 'GET',
credentials: 'include'
}).then(res => res.json());
}
export function getAccount() {
return fetch(`${Setting.ServerUrl}/api/get-account`, {
method: 'GET',
credentials: 'include'
}).then(res => res.json());
}
export function register(values) {
return fetch(`${Setting.ServerUrl}/api/register`, {
method: 'POST',
credentials: "include",
body: JSON.stringify(values),
}).then(res => res.json());
}
export function login(values) {
return fetch(`${Setting.ServerUrl}/api/login`, {
method: 'POST',
credentials: "include",
body: JSON.stringify(values),
}).then(res => res.json());
}
export function logout() {
return fetch(`${Setting.ServerUrl}/api/logout`, {
method: 'POST',
credentials: "include",
}).then(res => res.json());
}

View File

@ -1,42 +0,0 @@
import * as Setting from "../Setting";
export function getUsers(owner) {
return fetch(`${Setting.ServerUrl}/api/get-users?owner=${owner}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function getUser(owner, name) {
return fetch(`${Setting.ServerUrl}/api/get-user?id=${owner}/${encodeURIComponent(name)}`, {
method: "GET",
credentials: "include"
}).then(res => res.json());
}
export function updateUser(owner, name, user) {
let newUser = Setting.deepCopy(user);
return fetch(`${Setting.ServerUrl}/api/update-user?id=${owner}/${encodeURIComponent(name)}`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newUser),
}).then(res => res.json());
}
export function addUser(user) {
let newUser = Setting.deepCopy(user);
return fetch(`${Setting.ServerUrl}/api/add-user`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newUser),
}).then(res => res.json());
}
export function deleteUser(user) {
let newUser = Setting.deepCopy(user);
return fetch(`${Setting.ServerUrl}/api/delete-user`, {
method: 'POST',
credentials: 'include',
body: JSON.stringify(newUser),
}).then(res => res.json());
}

View File

@ -12,32 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package routers
import { httpClient } from './backend';
import (
"net/http"
"strings"
"github.com/astaxie/beego/context"
"github.com/casdoor/casdoor/util"
)
func TransparentStatic(ctx *context.Context) {
urlPath := ctx.Request.URL.Path
if strings.HasPrefix(urlPath, "/api/") {
return
}
path := "web/build"
if urlPath == "/" {
path += "/index.html"
} else {
path += urlPath
}
if util.FileExist(path) {
http.ServeFile(ctx.ResponseWriter, ctx.Request, path)
} else {
http.ServeFile(ctx.ResponseWriter, ctx.Request, "web/build/index.html")
}
export function getUser(username: string) {
return httpClient.get(`/get-user?username=${username}`);
}
export function getAccount() {
return httpClient.get(`/get-account`);
}
export function register(values: object) {
return httpClient.post(`/register`, values);
}
export function login(values: object) {
return httpClient.post(`/login`, values);
}
export function logout() {
return httpClient.post(`/logout`);
}

View File

@ -12,23 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package controllers
import axios from 'axios';
import "github.com/astaxie/beego"
type ApiController struct {
beego.Controller
function getBaseURL() {
const hostname = window.location.hostname;
if (hostname === 'localhost') {
return `http://${hostname}:7000/api`;
}
return '';
}
func (c *ApiController) GetSessionUser() string {
user := c.GetSession("username")
if user == nil {
return ""
}
return user.(string)
}
func (c *ApiController) SetSessionUser(user string) {
c.SetSession("username", user)
}
export const httpClient = axios.create({
baseURL: getBaseURL(),
withCredentials: true,
});

View File

@ -0,0 +1,45 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { httpClient } from './backend';
export interface User {
email: string;
name: string;
owner: string;
passwordType: string;
password: string;
displayName: string;
phone: string;
}
export function getUsers(owner: string) {
return httpClient.get(`/get-users?owner=${owner}`);
}
export function getUser(owner: string, name: string) {
return httpClient.get(`/get-user?id=${owner}/${name}`);
}
export function updateUser(owner: string, name: string, user: any) {
return httpClient.post(`/update-user?id=${owner}/${name}`, user);
}
export function addUser(user: any) {
return httpClient.post(`/add-user`, user);
}
export function deleteUser(user: any) {
return httpClient.post(`/delete-user`, user);
}

View File

@ -1,31 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
.logo {
background: url("logo.png");
background-size: 108px, 33px;
width: 108px;
height: 27px;
/*background: rgba(0, 0, 0, 0.2);*/
margin: 17px 10px 16px 20px;
float: left;
}
.ant-table.ant-table-middle .ant-table-title, .ant-table.ant-table-middle .ant-table-footer, .ant-table.ant-table-middle thead > tr > th, .ant-table.ant-table-middle tbody > tr > td {
padding: 1px 8px !important;
}
.ant-list-sm .ant-list-item {
padding: 2px !important;
}

38
web/src/index.less Normal file
View File

@ -0,0 +1,38 @@
@import '~antd/dist/antd.less';
@primary-color: #0052cc; // primary color for all components
@link-color: #0052cc; // link color
@success-color: #36b37e; // success state color
@warning-color: #ffab00; // warning state color
@error-color: #ff5630; // error state color
@font-size-base: 14px; // major text font size
@heading-color: rgba(0, 0, 0, 0.85); // heading text color
@text-color: rgba(0, 0, 0, 0.65); // major text color
@text-color-secondary: rgba(0, 0, 0, 0.45); // secondary text color
@disabled-color: rgba(0, 0, 0, 0.25); // disable state color
@border-radius-base: 2px; // major border radius
@border-color-base: #d9d9d9; // major border color
@box-shadow-base: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05); // major shadow for layers
.logo {
background: url('logo.png');
background-size: 108px, 33px;
width: 108px;
height: 27px;
/*background: rgba(0, 0, 0, 0.2);*/
margin: 17px 10px 16px 20px;
float: left;
}
.ant-table.ant-table-middle .ant-table-title,
.ant-table.ant-table-middle .ant-table-footer,
.ant-table.ant-table-middle thead > tr > th,
.ant-table.ant-table-middle tbody > tr > td {
padding: 1px 8px !important;
}
.ant-list-sm .ant-list-item {
padding: 2px !important;
}

View File

@ -14,19 +14,17 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
import App from './app';
import { ConfigProvider } from 'antd';
import enUS from 'antd/es/locale/en_US';
import { BrowserRouter } from 'react-router-dom';
import './index.less';
ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
<ConfigProvider locale={enUS}>
<BrowserRouter>
<App />
</BrowserRouter>
</ConfigProvider>,
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

15
web/src/react-app-env.d.ts vendored Normal file
View File

@ -0,0 +1,15 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// <reference types="react-scripts" />

View File

@ -1,141 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

View File

@ -1,125 +1,115 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import {message} from "antd";
import React from "react";
import {isMobile as isMobileDevice} from "react-device-detect";
export let ServerUrl = '';
export function initServerUrl() {
const hostname = window.location.hostname;
if (hostname === 'localhost') {
ServerUrl = `http://${hostname}:7000`;
}
}
export function parseJson(s) {
if (s === "") {
return null;
} else {
return JSON.parse(s);
}
}
export function myParseInt(i) {
const res = parseInt(i);
return isNaN(res) ? 0 : res;
}
export function openLink(link) {
// this.props.history.push(link);
const w = window.open('about:blank');
w.location.href = link;
}
export function goToLink(link) {
window.location.href = link;
}
export function showMessage(type, text) {
if (type === "") {
return;
} else if (type === "success") {
message.success(text);
} else if (type === "error") {
message.error(text);
}
}
export function deepCopy(obj) {
return Object.assign({}, obj);
}
export function addRow(array, row) {
return [...array, row];
}
export function prependRow(array, row) {
return [row, ...array];
}
export function deleteRow(array, i) {
// return array = array.slice(0, i).concat(array.slice(i + 1));
return [...array.slice(0, i), ...array.slice(i + 1)];
}
export function swapRow(array, i, j) {
return [...array.slice(0, i), array[j], ...array.slice(i + 1, j), array[i], ...array.slice(j + 1)];
}
export function isMobile() {
// return getIsMobileView();
return isMobileDevice;
}
export function getFormattedDate(date) {
if (date === undefined) {
return null;
}
date = date.replace('T', ' ');
date = date.replace('+08:00', ' ');
return date;
}
export function getFormattedDateShort(date) {
return date.slice(0, 10);
}
export function getShortName(s) {
return s.split('/').slice(-1)[0];
}
function getRandomInt(s) {
let hash = 0;
if (s.length !== 0) {
for (let i = 0; i < s.length; i ++) {
let char = s.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
}
return hash;
}
export function getAvatarColor(s) {
const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
let random = getRandomInt(s);
if (random < 0) {
random = -random;
}
return colorList[random % 4];
}
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import { message } from 'antd';
import { isMobile as isMobileDevice } from 'react-device-detect';
export function parseJson(s: string) {
if (s === '') {
return null;
} else {
return JSON.parse(s);
}
}
export function myParseInt(i: string) {
const res = parseInt(i);
return isNaN(res) ? 0 : res;
}
export function openLink(link: string) {
const w = window.open('about:blank');
if (w) {
w.location.href = link;
}
}
export function goToLink(link: string) {
window.location.href = link;
}
export function showMessage(type: '' | 'success' | 'error', text: string) {
if (type === '') {
return;
} else if (type === 'success') {
message.success(text);
} else if (type === 'error') {
message.error(text);
}
}
export function deepCopy(obj: object) {
return Object.assign({}, obj);
}
export function addRow(array: Array<any>, row: any) {
return [...array, row];
}
export function prependRow(array: Array<any>, row: any) {
return [row, ...array];
}
export function deleteRow(array: Array<any>, i: number) {
// return array = array.slice(0, i).concat(array.slice(i + 1));
return [...array.slice(0, i), ...array.slice(i + 1)];
}
export function swapRow(array: Array<any>, i: number, j: number) {
return [...array.slice(0, i), array[j], ...array.slice(i + 1, j), array[i], ...array.slice(j + 1)];
}
export function isMobile() {
return isMobileDevice;
}
export function getFormattedDate(date: string | undefined) {
if (date === undefined) {
return null;
}
date = date.replace('T', ' ');
date = date.replace('+08:00', ' ');
return date;
}
export function getFormattedDateShort(date: string) {
return date.slice(0, 10);
}
export function getShortName(s: string) {
return s.split('/').slice(-1)[0];
}
function getRandomInt(s: string) {
let hash = 0;
if (s.length !== 0) {
for (let i = 0; i < s.length; i++) {
let char = s.charCodeAt(i);
hash = (hash << 5) - hash + char;
hash = hash & hash;
}
}
return hash;
}
export function getAvatarColor(s: string) {
const colorList = ['#f56a00', '#7265e6', '#ffbf00', '#00a2ae'];
let random = getRandomInt(s);
if (random < 0) {
random = -random;
}
return colorList[random % 4];
}

19
web/src/setup-tests.ts Normal file
View File

@ -0,0 +1,19 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';

View File

@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

View File

@ -0,0 +1,184 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { useEffect, useState } from 'react';
import { Button, Card, Col, Input, Row } from 'antd';
import * as userBackend from '../backend/user-backend';
import { useNavigate, useParams } from 'react-router-dom';
import { showMessage } from '../setting';
function UserEditPage() {
const [userName, setUserName] = useState(useParams().userName);
const [user, setUser] = useState<userBackend.User | undefined>(undefined);
const navigate = useNavigate();
useEffect(() => {
userBackend.getUser('admin', userName).then((res) => {
setUser(res.data);
});
}, []);
function submitUserEdit() {
if (user == undefined) {
return;
}
userBackend
.updateUser(user.owner, userName, user)
.then((res) => {
if (res.data) {
showMessage('success', `Successfully saved`);
setUserName(user.name);
navigate(`/users/${user.name}`);
} else {
showMessage('error', `failed to save: server side failure`);
updateUserField('name', userName);
}
})
.catch((error) => {
showMessage('error', `failed to save: ${error}`);
});
}
function updateUserField(key: keyof userBackend.User, value: string) {
if (user) {
let newUser = {
...user,
};
newUser[key] = value;
setUser(newUser);
}
}
function renderUser() {
if (!user) {
return;
}
return (
<Card
size="small"
title={
<div>
Edit User&nbsp;&nbsp;&nbsp;&nbsp;
<Button type="primary" onClick={submitUserEdit}>
Save
</Button>
</div>
}
style={{ marginLeft: '5px' }}
type="inner"
>
<Row style={{ marginTop: '10px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Name:
</Col>
<Col span={22}>
<Input
value={user.name}
onChange={(e) => {
console.warn(e.target.value);
updateUserField('name', e.target.value);
}}
/>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Password Type:
</Col>
<Col span={22}>
<Input
value={user.passwordType}
onChange={(e) => {
updateUserField('passwordType', e.target.value);
}}
/>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Password:
</Col>
<Col span={22}>
<Input
value={user.password}
onChange={(e) => {
updateUserField('password', e.target.value);
}}
/>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Display Name:
</Col>
<Col span={22}>
<Input
value={user.displayName}
onChange={(e) => {
updateUserField('displayName', e.target.value);
}}
/>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Email:
</Col>
<Col span={22}>
<Input
value={user.email}
onChange={(e) => {
updateUserField('email', e.target.value);
}}
/>
</Col>
</Row>
<Row style={{ marginTop: '20px' }}>
<Col style={{ marginTop: '5px' }} span={2}>
Phone:
</Col>
<Col span={22}>
<Input
value={user.phone}
onChange={(e) => {
updateUserField('phone', e.target.value);
}}
/>
</Col>
</Row>
</Card>
);
}
return (
<div>
<Row style={{ width: '100%' }}>
<Col span={1} />
<Col span={22}>{renderUser()}</Col>
<Col span={1} />
</Row>
<Row style={{ margin: 10 }}>
<Col span={2} />
<Col span={18}>
<Button type="primary" size="large" onClick={submitUserEdit}>
Save
</Button>
</Col>
</Row>
</div>
);
}
export default UserEditPage;

View File

@ -0,0 +1,188 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React, { useEffect, useState } from 'react';
import { Button, Popconfirm, Table } from 'antd';
import { getFormattedDate, showMessage } from '../setting';
import { useNavigate } from 'react-router-dom';
import moment from 'moment';
import * as userBackend from '../backend/user-backend';
import tw from 'twin.macro';
function UserTable() {
const [users, setUsers] = useState<Array<any>>([]);
const navigate = useNavigate();
useEffect(() => {
userBackend.getUsers('admin').then((res) => {
setUsers(res.data);
});
}, []);
function newUser() {
return {
owner: 'admin', // this.props.account.username,
name: `user_${users.length}`,
createdTime: moment().format(),
password: '123456',
passwordType: 'plain',
displayName: `New User - ${users.length}`,
email: 'user@example.com',
phone: '1-12345678',
};
}
function addUser() {
const value = newUser();
userBackend
.addUser(value)
.then((res) => {
showMessage('success', `User added successfully`);
setUsers([...users, value]);
})
.catch((error) => {
showMessage('error', `User failed to add: ${error}`);
});
}
function deleteUser(i: number) {
userBackend
.deleteUser(users[i])
.then((res) => {
showMessage('success', `User deleted successfully`);
setUsers(users.filter((n) => n != users[i]));
})
.catch((error) => {
showMessage('error', `User failed to delete: ${error}`);
});
}
const columns = [
{
title: 'Name',
dataIndex: 'name',
key: 'name',
width: '120px',
sorter: (a: any, b: any) => a.name.localeCompare(b.name),
render: (text: string) => {
return <a href={`/users/${text}`}>{text}</a>;
},
},
{
title: 'Created Time',
dataIndex: 'createdTime',
key: 'createdTime',
width: '160px',
sorter: (a: any, b: any) => a.createdTime.localeCompare(b.createdTime),
render: (text: string) => {
return getFormattedDate(text);
},
},
{
title: 'PasswordType',
dataIndex: 'passwordType',
key: 'passwordType',
width: '150px',
sorter: (a: any, b: any) => a.passwordType.localeCompare(b.passwordType),
},
{
title: 'Password',
dataIndex: 'password',
key: 'password',
width: '150px',
sorter: (a: any, b: any) => a.password.localeCompare(b.password),
},
{
title: 'Display Name',
dataIndex: 'displayName',
key: 'displayName',
// width: '100px',
sorter: (a: any, b: any) => a.displayName.localeCompare(b.displayName),
},
{
title: 'Email',
dataIndex: 'email',
key: 'email',
width: '150px',
sorter: (a: any, b: any) => a.email.localeCompare(b.email),
},
{
title: 'Phone',
dataIndex: 'phone',
key: 'phone',
width: '120px',
sorter: (a: any, b: any) => a.phone.localeCompare(b.phone),
},
{
title: 'Action',
dataIndex: '',
key: 'op',
width: '170px',
render: (text: string, record: any, index: number) => {
return (
<div>
<Button
style={{
marginTop: '10px',
marginBottom: '10px',
marginRight: '10px',
}}
type="primary"
onClick={() => {
navigate(`/users/${record.name}`);
}}
>
Edit
</Button>
<Popconfirm title={`Sure to delete user: ${record.name} ?`} onConfirm={() => deleteUser(index)}>
<Button style={{ marginBottom: '10px' }} type="dashed">
Delete
</Button>
</Popconfirm>
</div>
);
},
},
];
return (
<div>
<div css={tw`flex items-center justify-between`}>
<h1 css={tw`m-0 text-2xl`}>Users</h1>
<Button type="primary" size="middle" onClick={addUser}>
Add
</Button>
</div>
<div css={tw`py-4`}>
<Table
columns={columns}
dataSource={users}
rowKey="name"
size="middle"
pagination={{ pageSize: 100 }}
loading={users === null || users === undefined}
/>
</div>
</div>
);
}
function UserListPage() {
return (
<>
<UserTable />
</>
);
}
export default UserListPage;

View File

@ -0,0 +1,27 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import React from 'react';
import { Route, Routes } from 'react-router-dom';
import UserListPage from './user-list-page';
import UserEditPage from './user-edit-page';
export function UserRoutes() {
return (
<Routes>
<Route path="/" element={<UserListPage />} />
<Route path=":userName" element={<UserEditPage />} />
</Routes>
);
}

17
web/tailwind.config.js Normal file
View File

@ -0,0 +1,17 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
module.exports = {
plugins: [],
};

20
web/tsconfig.json Normal file
View File

@ -0,0 +1,20 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react"
},
"include": ["src","types"]
}

59
web/types/static.d.ts vendored Normal file
View File

@ -0,0 +1,59 @@
/* Use this file to declare any custom file extensions for importing */
/* Use this folder to also add/extend a package d.ts file, if needed. */
/* CSS MODULES */
declare module '*.module.css' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.scss' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.sass' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.less' {
const classes: { [key: string]: string };
export default classes;
}
declare module '*.module.styl' {
const classes: { [key: string]: string };
export default classes;
}
/* CSS */
declare module '*.css';
declare module '*.scss';
declare module '*.sass';
declare module '*.less';
declare module '*.styl';
/* IMAGES */
declare module '*.svg' {
const ref: string;
export default ref;
}
declare module '*.bmp' {
const ref: string;
export default ref;
}
declare module '*.gif' {
const ref: string;
export default ref;
}
declare module '*.jpg' {
const ref: string;
export default ref;
}
declare module '*.jpeg' {
const ref: string;
export default ref;
}
declare module '*.png' {
const ref: string;
export default ref;
}
/* CUSTOM: ADD YOUR OWN HERE */

36
web/types/twin.d.ts vendored Normal file
View File

@ -0,0 +1,36 @@
// Copyright 2020 The casbin Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
import 'twin.macro';
import styledImport from '@emotion/styled';
import { css as cssImport } from '@emotion/core';
import 'tailwindcss/dist/base.min.css';
declare module 'twin.macro' {
// The styled and css imports
const styled: typeof styledImport;
const css: typeof cssImport;
}
declare module 'react' {
// The css prop
interface HTMLAttributes<T> extends DOMAttributes<T> {
css?: CSSProp;
}
// The inline svg css prop
interface SVGProps<T> extends SVGProps<SVGSVGElement> {
css?: CSSProp;
}
}

File diff suppressed because it is too large Load Diff