mirror of
https://github.com/astaxie/beego.git
synced 2024-11-25 19:10:54 +00:00
Adapter: utils
This commit is contained in:
parent
35f1bd2119
commit
f4a43814be
85
pkg/adapter/cache/cache.go
vendored
Normal file
85
pkg/adapter/cache/cache.go
vendored
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 cache provide a Cache interface and some implement engine
|
||||||
|
// Usage:
|
||||||
|
//
|
||||||
|
// import(
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// bm, err := cache.NewCache("memory", `{"interval":60}`)
|
||||||
|
//
|
||||||
|
// Use it like this:
|
||||||
|
//
|
||||||
|
// bm.Put("astaxie", 1, 10 * time.Second)
|
||||||
|
// bm.Get("astaxie")
|
||||||
|
// bm.IsExist("astaxie")
|
||||||
|
// bm.Delete("astaxie")
|
||||||
|
//
|
||||||
|
// more docs http://beego.me/docs/module/cache.md
|
||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/client/cache"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cache interface contains all behaviors for cache adapter.
|
||||||
|
// usage:
|
||||||
|
// cache.Register("file",cache.NewFileCache) // this operation is run in init method of file.go.
|
||||||
|
// c,err := cache.NewCache("file","{....}")
|
||||||
|
// c.Put("key",value, 3600 * time.Second)
|
||||||
|
// v := c.Get("key")
|
||||||
|
//
|
||||||
|
// c.Incr("counter") // now is 1
|
||||||
|
// c.Incr("counter") // now is 2
|
||||||
|
// count := c.Get("counter").(int)
|
||||||
|
type Cache cache.Cache
|
||||||
|
|
||||||
|
// Instance is a function create a new Cache Instance
|
||||||
|
type Instance func() Cache
|
||||||
|
|
||||||
|
var adapters = make(map[string]Instance)
|
||||||
|
|
||||||
|
// Register makes a cache adapter available by the adapter name.
|
||||||
|
// If Register is called twice with the same name or if driver is nil,
|
||||||
|
// it panics.
|
||||||
|
func Register(name string, adapter Instance) {
|
||||||
|
if adapter == nil {
|
||||||
|
panic("cache: Register adapter is nil")
|
||||||
|
}
|
||||||
|
if _, ok := adapters[name]; ok {
|
||||||
|
panic("cache: Register called twice for adapter " + name)
|
||||||
|
}
|
||||||
|
adapters[name] = adapter
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCache Create a new cache driver by adapter name and config string.
|
||||||
|
// config need to be correct JSON as string: {"interval":360}.
|
||||||
|
// it will start gc automatically.
|
||||||
|
func NewCache(adapterName, config string) (adapter Cache, err error) {
|
||||||
|
instanceFunc, ok := adapters[adapterName]
|
||||||
|
if !ok {
|
||||||
|
err = fmt.Errorf("cache: unknown adapter name %q (forgot to import?)", adapterName)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
adapter = instanceFunc()
|
||||||
|
err = adapter.StartAndGC(config)
|
||||||
|
if err != nil {
|
||||||
|
adapter = nil
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
191
pkg/adapter/cache/cache_test.go
vendored
Normal file
191
pkg/adapter/cache/cache_test.go
vendored
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCacheIncr(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
//timeoutDuration := 10 * time.Second
|
||||||
|
|
||||||
|
bm.Put("edwardhey", 0, time.Second*20)
|
||||||
|
wg := sync.WaitGroup{}
|
||||||
|
wg.Add(10)
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
bm.Incr("edwardhey")
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
wg.Wait()
|
||||||
|
if bm.Get("edwardhey").(int) != 10 {
|
||||||
|
t.Error("Incr err")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("memory", `{"interval":20}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(30 * time.Second)
|
||||||
|
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileCache(t *testing.T) {
|
||||||
|
bm, err := NewCache("file", `{"CachePath":"cache","FileSuffix":".bin","DirectoryLevel":"2","EmbedExpiry":"0"}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Error("init err")
|
||||||
|
}
|
||||||
|
timeoutDuration := 10 * time.Second
|
||||||
|
if err = bm.Put("astaxie", 1, timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Incr("astaxie"); err != nil {
|
||||||
|
t.Error("Incr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 2 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = bm.Decr("astaxie"); err != nil {
|
||||||
|
t.Error("Decr Error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := bm.Get("astaxie"); v.(int) != 1 {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
bm.Delete("astaxie")
|
||||||
|
if bm.IsExist("astaxie") {
|
||||||
|
t.Error("delete err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test string
|
||||||
|
if err = bm.Put("astaxie", "author", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
if v := bm.Get("astaxie"); v.(string) != "author" {
|
||||||
|
t.Error("get err")
|
||||||
|
}
|
||||||
|
|
||||||
|
//test GetMulti
|
||||||
|
if err = bm.Put("astaxie1", "author1", timeoutDuration); err != nil {
|
||||||
|
t.Error("set Error", err)
|
||||||
|
}
|
||||||
|
if !bm.IsExist("astaxie1") {
|
||||||
|
t.Error("check err")
|
||||||
|
}
|
||||||
|
|
||||||
|
vv := bm.GetMulti([]string{"astaxie", "astaxie1"})
|
||||||
|
if len(vv) != 2 {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[0].(string) != "author" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
if vv[1].(string) != "author1" {
|
||||||
|
t.Error("GetMulti ERROR")
|
||||||
|
}
|
||||||
|
|
||||||
|
os.RemoveAll("cache")
|
||||||
|
}
|
24
pkg/adapter/utils/caller.go
Normal file
24
pkg/adapter/utils/caller.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetFuncName get function name
|
||||||
|
func GetFuncName(i interface{}) string {
|
||||||
|
return utils.GetFuncName(i)
|
||||||
|
}
|
28
pkg/adapter/utils/caller_test.go
Normal file
28
pkg/adapter/utils/caller_test.go
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetFuncName(t *testing.T) {
|
||||||
|
name := GetFuncName(TestGetFuncName)
|
||||||
|
t.Log(name)
|
||||||
|
if !strings.HasSuffix(name, ".TestGetFuncName") {
|
||||||
|
t.Error("get func name error")
|
||||||
|
}
|
||||||
|
}
|
19
pkg/adapter/utils/captcha/LICENSE
Normal file
19
pkg/adapter/utils/captcha/LICENSE
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
Copyright (c) 2011-2014 Dmitry Chestnykh <dmitry@codingrobots.com>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
45
pkg/adapter/utils/captcha/README.md
Normal file
45
pkg/adapter/utils/captcha/README.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Captcha
|
||||||
|
|
||||||
|
an example for use captcha
|
||||||
|
|
||||||
|
```
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego"
|
||||||
|
"github.com/astaxie/beego/cache"
|
||||||
|
"github.com/astaxie/beego/utils/captcha"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cpt *captcha.Captcha
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// use beego cache system store the captcha data
|
||||||
|
store := cache.NewMemoryCache()
|
||||||
|
cpt = captcha.NewWithFilter("/captcha/", store)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MainController struct {
|
||||||
|
beego.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MainController) Get() {
|
||||||
|
this.TplName = "index.tpl"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *MainController) Post() {
|
||||||
|
this.TplName = "index.tpl"
|
||||||
|
|
||||||
|
this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
template usage
|
||||||
|
|
||||||
|
```
|
||||||
|
{{.Success}}
|
||||||
|
<form action="/" method="post">
|
||||||
|
{{create_captcha}}
|
||||||
|
<input name="captcha" type="text">
|
||||||
|
</form>
|
||||||
|
```
|
124
pkg/adapter/utils/captcha/captcha.go
Normal file
124
pkg/adapter/utils/captcha/captcha.go
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 captcha implements generation and verification of image CAPTCHAs.
|
||||||
|
// an example for use captcha
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// package controllers
|
||||||
|
//
|
||||||
|
// import (
|
||||||
|
// "github.com/astaxie/beego"
|
||||||
|
// "github.com/astaxie/beego/cache"
|
||||||
|
// "github.com/astaxie/beego/utils/captcha"
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// var cpt *captcha.Captcha
|
||||||
|
//
|
||||||
|
// func init() {
|
||||||
|
// // use beego cache system store the captcha data
|
||||||
|
// store := cache.NewMemoryCache()
|
||||||
|
// cpt = captcha.NewWithFilter("/captcha/", store)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// type MainController struct {
|
||||||
|
// beego.Controller
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (this *MainController) Get() {
|
||||||
|
// this.TplName = "index.tpl"
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// func (this *MainController) Post() {
|
||||||
|
// this.TplName = "index.tpl"
|
||||||
|
//
|
||||||
|
// this.Data["Success"] = cpt.VerifyReq(this.Ctx.Request)
|
||||||
|
// }
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// template usage
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// {{.Success}}
|
||||||
|
// <form action="/" method="post">
|
||||||
|
// {{create_captcha}}
|
||||||
|
// <input name="captcha" type="text">
|
||||||
|
// </form>
|
||||||
|
// ```
|
||||||
|
package captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/server/web/captcha"
|
||||||
|
beecontext "github.com/astaxie/beego/pkg/server/web/context"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/adapter/cache"
|
||||||
|
"github.com/astaxie/beego/pkg/adapter/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultChars = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// default captcha attributes
|
||||||
|
challengeNums = 6
|
||||||
|
expiration = 600 * time.Second
|
||||||
|
fieldIDName = "captcha_id"
|
||||||
|
fieldCaptchaName = "captcha"
|
||||||
|
cachePrefix = "captcha_"
|
||||||
|
defaultURLPrefix = "/captcha/"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Captcha struct
|
||||||
|
type Captcha captcha.Captcha
|
||||||
|
|
||||||
|
// Handler beego filter handler for serve captcha image
|
||||||
|
func (c *Captcha) Handler(ctx *context.Context) {
|
||||||
|
(*captcha.Captcha)(c).Handler((*beecontext.Context)(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCaptchaHTML template func for output html
|
||||||
|
func (c *Captcha) CreateCaptchaHTML() template.HTML {
|
||||||
|
return (*captcha.Captcha)(c).CreateCaptchaHTML()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateCaptcha create a new captcha id
|
||||||
|
func (c *Captcha) CreateCaptcha() (string, error) {
|
||||||
|
return (*captcha.Captcha)(c).CreateCaptcha()
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyReq verify from a request
|
||||||
|
func (c *Captcha) VerifyReq(req *http.Request) bool {
|
||||||
|
return (*captcha.Captcha)(c).VerifyReq(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify direct verify id and challenge string
|
||||||
|
func (c *Captcha) Verify(id string, challenge string) (success bool) {
|
||||||
|
return (*captcha.Captcha)(c).Verify(id, challenge)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCaptcha create a new captcha.Captcha
|
||||||
|
func NewCaptcha(urlPrefix string, store cache.Cache) *Captcha {
|
||||||
|
return (*Captcha)(captcha.NewCaptcha(urlPrefix, store))
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewWithFilter create a new captcha.Captcha and auto AddFilter for serve captacha image
|
||||||
|
// and add a template func for output html
|
||||||
|
func NewWithFilter(urlPrefix string, store cache.Cache) *Captcha {
|
||||||
|
return (*Captcha)(captcha.NewWithFilter(urlPrefix, store))
|
||||||
|
}
|
35
pkg/adapter/utils/captcha/image.go
Normal file
35
pkg/adapter/utils/captcha/image.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/server/web/captcha"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Image struct
|
||||||
|
type Image captcha.Image
|
||||||
|
|
||||||
|
// NewImage returns a new captcha image of the given width and height with the
|
||||||
|
// given digits, where each digit must be in range 0-9.
|
||||||
|
func NewImage(digits []byte, width, height int) *Image {
|
||||||
|
return (*Image)(captcha.NewImage(digits, width, height))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTo writes captcha image in PNG format into the given writer.
|
||||||
|
func (m *Image) WriteTo(w io.Writer) (int64, error) {
|
||||||
|
return (*captcha.Image)(m).WriteTo(w)
|
||||||
|
}
|
58
pkg/adapter/utils/captcha/image_test.go
Normal file
58
pkg/adapter/utils/captcha/image_test.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 captcha
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/adapter/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Standard width and height of a captcha image.
|
||||||
|
stdWidth = 240
|
||||||
|
stdHeight = 80
|
||||||
|
)
|
||||||
|
|
||||||
|
type byteCounter struct {
|
||||||
|
n int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bc *byteCounter) Write(b []byte) (int, error) {
|
||||||
|
bc.n += int64(len(b))
|
||||||
|
return len(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNewImage(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
d := utils.RandomCreateBytes(challengeNums, defaultChars...)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
NewImage(d, stdWidth, stdHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkImageWriteTo(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
d := utils.RandomCreateBytes(challengeNums, defaultChars...)
|
||||||
|
b.StartTimer()
|
||||||
|
counter := &byteCounter{}
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
img := NewImage(d, stdWidth, stdHeight)
|
||||||
|
img.WriteTo(counter)
|
||||||
|
b.SetBytes(counter.n)
|
||||||
|
counter.n = 0
|
||||||
|
}
|
||||||
|
}
|
34
pkg/adapter/utils/debug.go
Normal file
34
pkg/adapter/utils/debug.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Display print the data in console
|
||||||
|
func Display(data ...interface{}) {
|
||||||
|
utils.Display(data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDisplayString return data print string
|
||||||
|
func GetDisplayString(data ...interface{}) string {
|
||||||
|
return utils.GetDisplayString(data...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack get stack bytes
|
||||||
|
func Stack(skip int, indent string) []byte {
|
||||||
|
return utils.Stack(skip, indent)
|
||||||
|
}
|
46
pkg/adapter/utils/debug_test.go
Normal file
46
pkg/adapter/utils/debug_test.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mytype struct {
|
||||||
|
next *mytype
|
||||||
|
prev *mytype
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrint(t *testing.T) {
|
||||||
|
Display("v1", 1, "v2", 2, "v3", 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintPoint(t *testing.T) {
|
||||||
|
var v1 = new(mytype)
|
||||||
|
var v2 = new(mytype)
|
||||||
|
|
||||||
|
v1.prev = nil
|
||||||
|
v1.next = v2
|
||||||
|
|
||||||
|
v2.prev = v1
|
||||||
|
v2.next = nil
|
||||||
|
|
||||||
|
Display("v1", v1, "v2", v2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrintString(t *testing.T) {
|
||||||
|
str := GetDisplayString("v1", 1, "v2", 2)
|
||||||
|
println(str)
|
||||||
|
}
|
47
pkg/adapter/utils/file.go
Normal file
47
pkg/adapter/utils/file.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SelfPath gets compiled executable file absolute path
|
||||||
|
func SelfPath() string {
|
||||||
|
return utils.SelfPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelfDir gets compiled executable file directory
|
||||||
|
func SelfDir() string {
|
||||||
|
return utils.SelfDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
// FileExists reports whether the named file or directory exists.
|
||||||
|
func FileExists(name string) bool {
|
||||||
|
return utils.FileExists(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchFile Search a file in paths.
|
||||||
|
// this is often used in search config file in /etc ~/
|
||||||
|
func SearchFile(filename string, paths ...string) (fullpath string, err error) {
|
||||||
|
return utils.SearchFile(filename, paths...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GrepFile like command grep -E
|
||||||
|
// for example: GrepFile(`^hello`, "hello.txt")
|
||||||
|
// \n is striped while read
|
||||||
|
func GrepFile(patten string, filename string) (lines []string, err error) {
|
||||||
|
return utils.GrepFile(patten, filename)
|
||||||
|
}
|
75
pkg/adapter/utils/file_test.go
Normal file
75
pkg/adapter/utils/file_test.go
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var noExistedFile = "/tmp/not_existed_file"
|
||||||
|
|
||||||
|
func TestSelfPath(t *testing.T) {
|
||||||
|
path := SelfPath()
|
||||||
|
if path == "" {
|
||||||
|
t.Error("path cannot be empty")
|
||||||
|
}
|
||||||
|
t.Logf("SelfPath: %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSelfDir(t *testing.T) {
|
||||||
|
dir := SelfDir()
|
||||||
|
t.Logf("SelfDir: %s", dir)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileExists(t *testing.T) {
|
||||||
|
if !FileExists("./file.go") {
|
||||||
|
t.Errorf("./file.go should exists, but it didn't")
|
||||||
|
}
|
||||||
|
|
||||||
|
if FileExists(noExistedFile) {
|
||||||
|
t.Errorf("Weird, how could this file exists: %s", noExistedFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSearchFile(t *testing.T) {
|
||||||
|
path, err := SearchFile(filepath.Base(SelfPath()), SelfDir())
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.Log(path)
|
||||||
|
|
||||||
|
_, err = SearchFile(noExistedFile, ".")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("err shouldnt be nil, got path: %s", SelfDir())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGrepFile(t *testing.T) {
|
||||||
|
_, err := GrepFile("", noExistedFile)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expect file-not-existed error, but got nothing")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(".", "testdata", "grepe.test")
|
||||||
|
lines, err := GrepFile(`^\s*[^#]+`, path)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(lines, []string{"hello", "world"}) {
|
||||||
|
t.Errorf("expect [hello world], but receive %v", lines)
|
||||||
|
}
|
||||||
|
}
|
63
pkg/adapter/utils/mail.go
Normal file
63
pkg/adapter/utils/mail.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Email is the type used for email messages
|
||||||
|
type Email utils.Email
|
||||||
|
|
||||||
|
// Attachment is a struct representing an email attachment.
|
||||||
|
// Based on the mime/multipart.FileHeader struct, Attachment contains the name, MIMEHeader, and content of the attachment in question
|
||||||
|
type Attachment utils.Attachment
|
||||||
|
|
||||||
|
// NewEMail create new Email struct with config json.
|
||||||
|
// config json is followed from Email struct fields.
|
||||||
|
func NewEMail(config string) *Email {
|
||||||
|
return (*Email)(utils.NewEMail(config))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes Make all send information to byte
|
||||||
|
func (e *Email) Bytes() ([]byte, error) {
|
||||||
|
return (*utils.Email)(e).Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttachFile Add attach file to the send mail
|
||||||
|
func (e *Email) AttachFile(args ...string) (*Attachment, error) {
|
||||||
|
a, err := (*utils.Email)(e).AttachFile(args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*Attachment)(a), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach is used to attach content from an io.Reader to the email.
|
||||||
|
// Parameters include an io.Reader, the desired filename for the attachment, and the Content-Type.
|
||||||
|
func (e *Email) Attach(r io.Reader, filename string, args ...string) (*Attachment, error) {
|
||||||
|
a, err := (*utils.Email)(e).Attach(r, filename, args...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return (*Attachment)(a), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send will send out the mail
|
||||||
|
func (e *Email) Send() error {
|
||||||
|
return (*utils.Email)(e).Send()
|
||||||
|
}
|
41
pkg/adapter/utils/mail_test.go
Normal file
41
pkg/adapter/utils/mail_test.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestMail(t *testing.T) {
|
||||||
|
config := `{"username":"astaxie@gmail.com","password":"astaxie","host":"smtp.gmail.com","port":587}`
|
||||||
|
mail := NewEMail(config)
|
||||||
|
if mail.Username != "astaxie@gmail.com" {
|
||||||
|
t.Fatal("email parse get username error")
|
||||||
|
}
|
||||||
|
if mail.Password != "astaxie" {
|
||||||
|
t.Fatal("email parse get password error")
|
||||||
|
}
|
||||||
|
if mail.Host != "smtp.gmail.com" {
|
||||||
|
t.Fatal("email parse get host error")
|
||||||
|
}
|
||||||
|
if mail.Port != 587 {
|
||||||
|
t.Fatal("email parse get port error")
|
||||||
|
}
|
||||||
|
mail.To = []string{"xiemengjun@gmail.com"}
|
||||||
|
mail.From = "astaxie@gmail.com"
|
||||||
|
mail.Subject = "hi, just from beego!"
|
||||||
|
mail.Text = "Text Body is, of course, supported!"
|
||||||
|
mail.HTML = "<h1>Fancy Html is supported, too!</h1>"
|
||||||
|
mail.AttachFile("/Users/astaxie/github/beego/beego.go")
|
||||||
|
mail.Send()
|
||||||
|
}
|
26
pkg/adapter/utils/pagination/controller.go
Normal file
26
pkg/adapter/utils/pagination/controller.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/adapter/context"
|
||||||
|
beecontext "github.com/astaxie/beego/pkg/server/web/context"
|
||||||
|
"github.com/astaxie/beego/pkg/server/web/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetPaginator Instantiates a Paginator and assigns it to context.Input.Data("paginator").
|
||||||
|
func SetPaginator(ctx *context.Context, per int, nums int64) (paginator *Paginator) {
|
||||||
|
return (*Paginator)(pagination.SetPaginator((*beecontext.Context)(ctx), per, nums))
|
||||||
|
}
|
58
pkg/adapter/utils/pagination/doc.go
Normal file
58
pkg/adapter/utils/pagination/doc.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
Package pagination provides utilities to setup a paginator within the
|
||||||
|
context of a http request.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
In your beego.Controller:
|
||||||
|
|
||||||
|
package controllers
|
||||||
|
|
||||||
|
import "github.com/astaxie/beego/utils/pagination"
|
||||||
|
|
||||||
|
type PostsController struct {
|
||||||
|
beego.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *PostsController) ListAllPosts() {
|
||||||
|
// sets this.Data["paginator"] with the current offset (from the url query param)
|
||||||
|
postsPerPage := 20
|
||||||
|
paginator := pagination.SetPaginator(this.Ctx, postsPerPage, CountPosts())
|
||||||
|
|
||||||
|
// fetch the next 20 posts
|
||||||
|
this.Data["posts"] = ListPostsByOffsetAndLimit(paginator.Offset(), postsPerPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
In your view templates:
|
||||||
|
|
||||||
|
{{if .paginator.HasPages}}
|
||||||
|
<ul class="pagination pagination">
|
||||||
|
{{if .paginator.HasPrev}}
|
||||||
|
<li><a href="{{.paginator.PageLinkFirst}}">{{ i18n .Lang "paginator.first_page"}}</a></li>
|
||||||
|
<li><a href="{{.paginator.PageLinkPrev}}">«</a></li>
|
||||||
|
{{else}}
|
||||||
|
<li class="disabled"><a>{{ i18n .Lang "paginator.first_page"}}</a></li>
|
||||||
|
<li class="disabled"><a>«</a></li>
|
||||||
|
{{end}}
|
||||||
|
{{range $index, $page := .paginator.Pages}}
|
||||||
|
<li{{if $.paginator.IsActive .}} class="active"{{end}}>
|
||||||
|
<a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
|
||||||
|
</li>
|
||||||
|
{{end}}
|
||||||
|
{{if .paginator.HasNext}}
|
||||||
|
<li><a href="{{.paginator.PageLinkNext}}">»</a></li>
|
||||||
|
<li><a href="{{.paginator.PageLinkLast}}">{{ i18n .Lang "paginator.last_page"}}</a></li>
|
||||||
|
{{else}}
|
||||||
|
<li class="disabled"><a>»</a></li>
|
||||||
|
<li class="disabled"><a>{{ i18n .Lang "paginator.last_page"}}</a></li>
|
||||||
|
{{end}}
|
||||||
|
</ul>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
See also
|
||||||
|
|
||||||
|
http://beego.me/docs/mvc/view/page.md
|
||||||
|
|
||||||
|
*/
|
||||||
|
package pagination
|
112
pkg/adapter/utils/pagination/paginator.go
Normal file
112
pkg/adapter/utils/pagination/paginator.go
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 pagination
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils/pagination"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Paginator within the state of a http request.
|
||||||
|
type Paginator pagination.Paginator
|
||||||
|
|
||||||
|
// PageNums Returns the total number of pages.
|
||||||
|
func (p *Paginator) PageNums() int {
|
||||||
|
return (*pagination.Paginator)(p).PageNums()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nums Returns the total number of items (e.g. from doing SQL count).
|
||||||
|
func (p *Paginator) Nums() int64 {
|
||||||
|
return (*pagination.Paginator)(p).Nums()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetNums Sets the total number of items.
|
||||||
|
func (p *Paginator) SetNums(nums interface{}) {
|
||||||
|
(*pagination.Paginator)(p).SetNums(nums)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Page Returns the current page.
|
||||||
|
func (p *Paginator) Page() int {
|
||||||
|
return (*pagination.Paginator)(p).Page()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pages Returns a list of all pages.
|
||||||
|
//
|
||||||
|
// Usage (in a view template):
|
||||||
|
//
|
||||||
|
// {{range $index, $page := .paginator.Pages}}
|
||||||
|
// <li{{if $.paginator.IsActive .}} class="active"{{end}}>
|
||||||
|
// <a href="{{$.paginator.PageLink $page}}">{{$page}}</a>
|
||||||
|
// </li>
|
||||||
|
// {{end}}
|
||||||
|
func (p *Paginator) Pages() []int {
|
||||||
|
return (*pagination.Paginator)(p).Pages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageLink Returns URL for a given page index.
|
||||||
|
func (p *Paginator) PageLink(page int) string {
|
||||||
|
return (*pagination.Paginator)(p).PageLink(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageLinkPrev Returns URL to the previous page.
|
||||||
|
func (p *Paginator) PageLinkPrev() (link string) {
|
||||||
|
return (*pagination.Paginator)(p).PageLinkPrev()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageLinkNext Returns URL to the next page.
|
||||||
|
func (p *Paginator) PageLinkNext() (link string) {
|
||||||
|
return (*pagination.Paginator)(p).PageLinkNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageLinkFirst Returns URL to the first page.
|
||||||
|
func (p *Paginator) PageLinkFirst() (link string) {
|
||||||
|
return (*pagination.Paginator)(p).PageLinkFirst()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PageLinkLast Returns URL to the last page.
|
||||||
|
func (p *Paginator) PageLinkLast() (link string) {
|
||||||
|
return (*pagination.Paginator)(p).PageLinkLast()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPrev Returns true if the current page has a predecessor.
|
||||||
|
func (p *Paginator) HasPrev() bool {
|
||||||
|
return (*pagination.Paginator)(p).HasPrev()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasNext Returns true if the current page has a successor.
|
||||||
|
func (p *Paginator) HasNext() bool {
|
||||||
|
return (*pagination.Paginator)(p).HasNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsActive Returns true if the given page index points to the current page.
|
||||||
|
func (p *Paginator) IsActive(page int) bool {
|
||||||
|
return (*pagination.Paginator)(p).IsActive(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset Returns the current offset.
|
||||||
|
func (p *Paginator) Offset() int {
|
||||||
|
return (*pagination.Paginator)(p).Offset()
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasPages Returns true if there is more than one page.
|
||||||
|
func (p *Paginator) HasPages() bool {
|
||||||
|
return (*pagination.Paginator)(p).HasPages()
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPaginator Instantiates a paginator struct for the current http request.
|
||||||
|
func NewPaginator(req *http.Request, per int, nums interface{}) *Paginator {
|
||||||
|
return (*Paginator)(pagination.NewPaginator(req, per, nums))
|
||||||
|
}
|
24
pkg/adapter/utils/rand.go
Normal file
24
pkg/adapter/utils/rand.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RandomCreateBytes generate random []byte by specify chars.
|
||||||
|
func RandomCreateBytes(n int, alphabets ...byte) []byte {
|
||||||
|
return utils.RandomCreateBytes(n, alphabets...)
|
||||||
|
}
|
33
pkg/adapter/utils/rand_test.go
Normal file
33
pkg/adapter/utils/rand_test.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
// Copyright 2016 beego Author. 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 utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestRand_01(t *testing.T) {
|
||||||
|
bs0 := RandomCreateBytes(16)
|
||||||
|
bs1 := RandomCreateBytes(16)
|
||||||
|
|
||||||
|
t.Log(string(bs0), string(bs1))
|
||||||
|
if string(bs0) == string(bs1) {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
bs0 = RandomCreateBytes(4, []byte(`a`)...)
|
||||||
|
|
||||||
|
if string(bs0) != "aaaa" {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
}
|
58
pkg/adapter/utils/safemap.go
Normal file
58
pkg/adapter/utils/safemap.go
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BeeMap is a map with lock
|
||||||
|
type BeeMap utils.BeeMap
|
||||||
|
|
||||||
|
// NewBeeMap return new safemap
|
||||||
|
func NewBeeMap() *BeeMap {
|
||||||
|
return (*BeeMap)(utils.NewBeeMap())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get from maps return the k's value
|
||||||
|
func (m *BeeMap) Get(k interface{}) interface{} {
|
||||||
|
return (*utils.BeeMap)(m).Get(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set Maps the given key and value. Returns false
|
||||||
|
// if the key is already in the map and changes nothing.
|
||||||
|
func (m *BeeMap) Set(k interface{}, v interface{}) bool {
|
||||||
|
return (*utils.BeeMap)(m).Set(k, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Returns true if k is exist in the map.
|
||||||
|
func (m *BeeMap) Check(k interface{}) bool {
|
||||||
|
return (*utils.BeeMap)(m).Check(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the given key and value.
|
||||||
|
func (m *BeeMap) Delete(k interface{}) {
|
||||||
|
(*utils.BeeMap)(m).Delete(k)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Items returns all items in safemap.
|
||||||
|
func (m *BeeMap) Items() map[interface{}]interface{} {
|
||||||
|
return (*utils.BeeMap)(m).Items()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count returns the number of items within the map.
|
||||||
|
func (m *BeeMap) Count() int {
|
||||||
|
return (*utils.BeeMap)(m).Count()
|
||||||
|
}
|
89
pkg/adapter/utils/safemap_test.go
Normal file
89
pkg/adapter/utils/safemap_test.go
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
var safeMap *BeeMap
|
||||||
|
|
||||||
|
func TestNewBeeMap(t *testing.T) {
|
||||||
|
safeMap = NewBeeMap()
|
||||||
|
if safeMap == nil {
|
||||||
|
t.Fatal("expected to return non-nil BeeMap", "got", safeMap)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
safeMap = NewBeeMap()
|
||||||
|
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReSet(t *testing.T) {
|
||||||
|
safeMap := NewBeeMap()
|
||||||
|
if ok := safeMap.Set("astaxie", 1); !ok {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
// set diff value
|
||||||
|
if ok := safeMap.Set("astaxie", -1); !ok {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set same value
|
||||||
|
if ok := safeMap.Set("astaxie", -1); ok {
|
||||||
|
t.Error("expected", false, "got", true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheck(t *testing.T) {
|
||||||
|
if exists := safeMap.Check("astaxie"); !exists {
|
||||||
|
t.Error("expected", true, "got", false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGet(t *testing.T) {
|
||||||
|
if val := safeMap.Get("astaxie"); val.(int) != 1 {
|
||||||
|
t.Error("expected value", 1, "got", val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDelete(t *testing.T) {
|
||||||
|
safeMap.Delete("astaxie")
|
||||||
|
if exists := safeMap.Check("astaxie"); exists {
|
||||||
|
t.Error("expected element to be deleted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestItems(t *testing.T) {
|
||||||
|
safeMap := NewBeeMap()
|
||||||
|
safeMap.Set("astaxie", "hello")
|
||||||
|
for k, v := range safeMap.Items() {
|
||||||
|
key := k.(string)
|
||||||
|
value := v.(string)
|
||||||
|
if key != "astaxie" {
|
||||||
|
t.Error("expected the key should be astaxie")
|
||||||
|
}
|
||||||
|
if value != "hello" {
|
||||||
|
t.Error("expected the value should be hello")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCount(t *testing.T) {
|
||||||
|
if count := safeMap.Count(); count != 0 {
|
||||||
|
t.Error("expected count to be", 0, "got", count)
|
||||||
|
}
|
||||||
|
}
|
101
pkg/adapter/utils/slice.go
Normal file
101
pkg/adapter/utils/slice.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reducetype func(interface{}) interface{}
|
||||||
|
type filtertype func(interface{}) bool
|
||||||
|
|
||||||
|
// InSlice checks given string in string slice or not.
|
||||||
|
func InSlice(v string, sl []string) bool {
|
||||||
|
return utils.InSlice(v, sl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InSliceIface checks given interface in interface slice.
|
||||||
|
func InSliceIface(v interface{}, sl []interface{}) bool {
|
||||||
|
return utils.InSliceIface(v, sl)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRandList generate an int slice from min to max.
|
||||||
|
func SliceRandList(min, max int) []int {
|
||||||
|
return utils.SliceRandList(min, max)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceMerge merges interface slices to one slice.
|
||||||
|
func SliceMerge(slice1, slice2 []interface{}) (c []interface{}) {
|
||||||
|
return utils.SliceMerge(slice1, slice2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceReduce generates a new slice after parsing every value by reduce function
|
||||||
|
func SliceReduce(slice []interface{}, a reducetype) (dslice []interface{}) {
|
||||||
|
return utils.SliceReduce(slice, func(i interface{}) interface{} {
|
||||||
|
return a(i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRand returns random one from slice.
|
||||||
|
func SliceRand(a []interface{}) (b interface{}) {
|
||||||
|
return utils.SliceRand(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceSum sums all values in int64 slice.
|
||||||
|
func SliceSum(intslice []int64) (sum int64) {
|
||||||
|
return utils.SliceSum(intslice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceFilter generates a new slice after filter function.
|
||||||
|
func SliceFilter(slice []interface{}, a filtertype) (ftslice []interface{}) {
|
||||||
|
return utils.SliceFilter(slice, func(i interface{}) bool {
|
||||||
|
return a(i)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceDiff returns diff slice of slice1 - slice2.
|
||||||
|
func SliceDiff(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||||
|
return utils.SliceDiff(slice1, slice2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceIntersect returns slice that are present in all the slice1 and slice2.
|
||||||
|
func SliceIntersect(slice1, slice2 []interface{}) (diffslice []interface{}) {
|
||||||
|
return utils.SliceIntersect(slice1, slice2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceChunk separates one slice to some sized slice.
|
||||||
|
func SliceChunk(slice []interface{}, size int) (chunkslice [][]interface{}) {
|
||||||
|
return utils.SliceChunk(slice, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceRange generates a new slice from begin to end with step duration of int64 number.
|
||||||
|
func SliceRange(start, end, step int64) (intslice []int64) {
|
||||||
|
return utils.SliceRange(start, end, step)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SlicePad prepends size number of val into slice.
|
||||||
|
func SlicePad(slice []interface{}, size int, val interface{}) []interface{} {
|
||||||
|
return utils.SlicePad(slice, size, val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceUnique cleans repeated values in slice.
|
||||||
|
func SliceUnique(slice []interface{}) (uniqueslice []interface{}) {
|
||||||
|
return utils.SliceUnique(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SliceShuffle shuffles a slice.
|
||||||
|
func SliceShuffle(slice []interface{}) []interface{} {
|
||||||
|
return utils.SliceShuffle(slice)
|
||||||
|
}
|
29
pkg/adapter/utils/slice_test.go
Normal file
29
pkg/adapter/utils/slice_test.go
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// Copyright 2014 beego Author. 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 utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInSlice(t *testing.T) {
|
||||||
|
sl := []string{"A", "b"}
|
||||||
|
if !InSlice("A", sl) {
|
||||||
|
t.Error("should be true")
|
||||||
|
}
|
||||||
|
if InSlice("B", sl) {
|
||||||
|
t.Error("should be false")
|
||||||
|
}
|
||||||
|
}
|
10
pkg/adapter/utils/utils.go
Normal file
10
pkg/adapter/utils/utils.go
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/astaxie/beego/pkg/infrastructure/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetGOPATHs returns all paths in GOPATH variable.
|
||||||
|
func GetGOPATHs() []string {
|
||||||
|
return utils.GetGOPATHs()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user