@ -0,0 +1,3 @@ | |||||
glide.lock | |||||
.idea/ | |||||
vendor/ |
@ -0,0 +1,26 @@ | |||||
package restlet | |||||
import ( | |||||
"net/http" | |||||
"fmt" | |||||
"context" | |||||
) | |||||
type AuthFunc func(r *http.Request) (interface{}, error) | |||||
type Authenticator struct { | |||||
next http.Handler | |||||
authenticate AuthFunc | |||||
} | |||||
func (a *Authenticator) ServeHTTP(w http.ResponseWriter, r *http.Request) { | |||||
var ctx context.Context | |||||
if a.authenticate != nil { | |||||
if u, e := a.authenticate(r); nil != e { | |||||
http.Error(w, fmt.Sprint(e), http.StatusUnauthorized) | |||||
} else { | |||||
ctx = context.WithValue(r.Context(),"sessUserInfo",u) | |||||
} | |||||
} | |||||
a.next.ServeHTTP(w, r.WithContext(ctx)) | |||||
} |
@ -0,0 +1,95 @@ | |||||
package restlet | |||||
const CONTENT_HEADER_SIZE = 32 | |||||
const ( | |||||
SUCCESS_OK = 20000 | |||||
SUCCESS_CREATED = 20100 | |||||
SUCCESS_DELETED = 20400 | |||||
) | |||||
const ( | |||||
ERROR_BAD_REQUEST = 40000 + iota | |||||
ERROR_INVALID_REQUEST | |||||
ERROR_INVALID_PATH | |||||
ERROR_INVALID_PARAMS | |||||
ERROR_INVALID_DATA | |||||
) | |||||
const ( | |||||
ERROR_UNAUTHORIZED = 40100 + iota | |||||
ERROR_LOGGIN_REQUIRED | |||||
ERROR_SECURET_REQUIRED | |||||
ERROR_SESSION_EXPIRED | |||||
ERROR_CLIENT_LOCKED | |||||
ERROR_CLIENT_UNREGISTERED | |||||
) | |||||
const ( | |||||
ERROR_PAYMENT_REQUIRED = 40200 + iota | |||||
ERROR_PAYMENT_NOTFOUND | |||||
ERROR_PAYMENT_EXPIRED | |||||
) | |||||
const ( | |||||
ERROR_FORBIDDEN = 40300 + iota | |||||
ERROR_PERMISSION_DENNIED | |||||
ERROR_RESOURCE_LOCKED | |||||
) | |||||
const ( | |||||
ERROR_NOT_FOUND = 40400 + iota | |||||
ERROR_OBJECT_NOT_FOUND | |||||
ERROR_ASSETS_NOT_FOUND | |||||
) | |||||
const ( | |||||
ERROR_METHOD_NOT_ALLOWED = 40500 + iota | |||||
) | |||||
const ( | |||||
ERROR_NOT_ACCEPTABLE = 40600 + iota | |||||
) | |||||
const ( | |||||
ERROR_REQUEST_TIMEOUT = 40800 + iota | |||||
) | |||||
const ( | |||||
ERROR_CONFLICT = 40900 + iota | |||||
ERROR_RESOURCE_EXISTS | |||||
ERROR_RESOURCE_NOT_COMPATIBLE | |||||
) | |||||
const ( | |||||
ERROR_GONE = 41000 + iota | |||||
) | |||||
const ( | |||||
FATAL_INTERNAL_SERVER_ERROR = 50000 + iota | |||||
FATAL_DB_READ_FAILED | |||||
FATAL_DB_WRITE_FAILED | |||||
FATAL_KV_READ_FAILED | |||||
FATAL_KV_WRITE_FAILED | |||||
FATAL_DATA_ENCODE_FAILED | |||||
FATAL_DATA_DECODE_FAILED | |||||
) | |||||
const ( | |||||
FATAL_NOT_IMPLEMENTED = 50100 + iota | |||||
) | |||||
const ( | |||||
FATAL_BAD_GATEWAY = 50200 + iota | |||||
FATAL_BAD_CONFIG | |||||
FATAL_CANNOT_CONNECT | |||||
) | |||||
const ( | |||||
FATAL_SERVICE_UNAVAILABLE = 50300 | |||||
) | |||||
const ( | |||||
FATAL_GATEWAY_TIMEOUT = 50400 + iota | |||||
FATAL_CONNECT_TIMEOUT | |||||
FATAL_RESPOSE_TIMEOUT | |||||
) | |||||
const ( | |||||
QCTRL_OFFSET = "__offset" | |||||
QCTRL_LIMIT = "__limit" | |||||
QCTRL_INCLUDES = "__includes" | |||||
QCTRL_EXCLUDES = "__excludes" | |||||
QCTRL_ORDERBY = "__order_by" | |||||
QCTRL_GROUPBY = "__group_by" | |||||
QCTRL_DEBUG = "__debug" | |||||
QCTRL_OFFSET_DEFAULT int64 = 0 | |||||
QCTRL_LIMIT_DEFAULT int64 = 50 | |||||
) |
@ -1,50 +1,79 @@ | |||||
package restlet | package restlet | ||||
import ( | import ( | ||||
"net/http" | |||||
"database/sql" | "database/sql" | ||||
) | |||||
type Context interface { | |||||
// GetConfig: Return a param value from configurations. | |||||
GetConfig(string, ...interface{}) (interface{}, bool) | |||||
"fmt" | |||||
// | |||||
"cygnux.net/kepler/cache" | |||||
"cygnux.net/kepler/config" | |||||
"cygnux.net/kepler/database" | |||||
"cygnux.net/kepler/kv" | |||||
"cygnux.net/kepler/msq" | |||||
log "github.com/Sirupsen/logrus" | |||||
) | |||||
type dumyContext struct { | |||||
_dbi database.DBI | |||||
_kvi kv.KV | |||||
_cache cache.Cache | |||||
_pub msq.Publisher | |||||
_sub msq.Subscriber | |||||
_cfg config.Config | |||||
} | } | ||||
func GetViaContext(k string, v interface{}) error { | |||||
return nil | |||||
func (d dumyContext) Schema(name ...string) string { | |||||
return d._dbi.Schema() | |||||
} | } | ||||
func AttachSQL(next http.Handler, d *sql.DB) http.Handler { | |||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
next.ServeHTTP(w, r) | |||||
}) | |||||
func (d dumyContext) SQL(name ...string) *sql.DB { | |||||
return d._dbi.DB() | |||||
} | } | ||||
func AttachMongoDB(next http.Handler, d *sql.DB) http.Handler { | |||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
next.ServeHTTP(w, r) | |||||
}) | |||||
func (d dumyContext) DBI(name ...string) database.DBI { | |||||
return d._dbi | |||||
} | } | ||||
func (d dumyContext) Chan(string, ...interface{}) error { | |||||
func AttachCache(next http.Handler, d *sql.DB) http.Handler { | |||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
next.ServeHTTP(w, r) | |||||
}) | |||||
return fmt.Errorf("not implemented") | |||||
} | } | ||||
func AttachKV(next http.Handler, d *sql.DB) http.Handler { | |||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
next.ServeHTTP(w, r) | |||||
}) | |||||
func (d dumyContext) Config() config.Config { | |||||
return d._cfg | |||||
} | |||||
func (d dumyContext) Cache(name ...string) cache.Cache { | |||||
return d._cache | |||||
} | |||||
func (d dumyContext) KV(name ...string) kv.KV { | |||||
return d._kvi | |||||
} | |||||
func (d dumyContext) Publish(topic string, bss ...[]byte) error { | |||||
for _, bs := range bss { | |||||
if e := d._pub.Publish(topic, bs); nil != e { | |||||
return e | |||||
} | |||||
} | |||||
return nil | |||||
} | } | ||||
func AttachConfig(next http.Handler, d interface{}) http.Handler { | |||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | |||||
next.ServeHTTP(w, r) | |||||
}) | |||||
} | |||||
func NewDummyContext(cfg config.Config) (RestletContext, error) { | |||||
d := dumyContext{_cfg: cfg} | |||||
var e error | |||||
if d._dbi, e = database.SetupDBI(cfg.Sub("database")); nil != e { | |||||
log.Errorln("Open database failed:> ", e) | |||||
return nil, e | |||||
} | |||||
if d._cache, e = cache.SetupCache(cfg.Sub("cache")); nil != e { | |||||
log.Errorln("Make Cache failed:> ", e) | |||||
return nil, e | |||||
} | |||||
if d._kvi, e = kv.SetupKV(cfg.Sub("kv")); nil != e { | |||||
log.Errorln("Make KVStore failed:> ", e) | |||||
return nil, e | |||||
} | |||||
if d._pub, e = msq.SetupPublisher(cfg.Sub("publish")); nil != e { | |||||
log.Errorln("Make MessagePub failed:> ", e) | |||||
return nil, e | |||||
} | |||||
if d._sub, e = msq.SetupSubscriber(cfg.Sub("subscribe")); nil != e { | |||||
log.Errorln("Make MessageSub failed:> ", e) | |||||
return nil, e | |||||
} | |||||
return d, nil | |||||
} |
@ -0,0 +1,672 @@ | |||||
package restlet | |||||
import ( | |||||
//"errors" | |||||
"reflect" | |||||
"strconv" | |||||
"time" | |||||
"github.com/archsh/go.xql" | |||||
_ "github.com/archsh/go.xql/dialects/postgres" | |||||
//"cygnux.net/kepler/utils" | |||||
"fmt" | |||||
log "github.com/Sirupsen/logrus" | |||||
//"strings" | |||||
"encoding/json" | |||||
"strings" | |||||
) | |||||
type CURDKind interface { | |||||
Entity() xql.TableIdentified | |||||
} | |||||
type CURDTable interface { | |||||
Table() *xql.Table | |||||
} | |||||
type CURDPreRead interface { | |||||
PreRead(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters) (*QueryController, error) | |||||
} | |||||
type CURDPreCreate interface { | |||||
PreCreate(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters, entity interface{}) error | |||||
} | |||||
type CURDPreUpdate interface { | |||||
PreUpdate(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters, columns []xql.UpdateColumn) (*QueryController, []xql.UpdateColumn, error) | |||||
} | |||||
type CURDPreDelete interface { | |||||
PreDelete(ctx RequestContext, session *xql.Session, url_params Parameters, queries Parameters) (*QueryController, error) | |||||
} | |||||
type CURDPostRead interface { | |||||
PostRead(ctx RequestContext, session *xql.Session, qc *QueryController, entity interface{}) (interface{}, error) | |||||
} | |||||
type CURDPostCreate interface { | |||||
PostCreate(ctx RequestContext, session *xql.Session, entity interface{}) (interface{}, error) | |||||
} | |||||
type CURDPostUpdate interface { | |||||
PostUpdate(ctx RequestContext, session *xql.Session, qc *QueryController, columns []xql.UpdateColumn) ([]xql.UpdateColumn, error) | |||||
} | |||||
type CURDPostDelete interface { | |||||
PostDelete(ctx RequestContext, session *xql.Session, qc *QueryController) error | |||||
} | |||||
type DefaultCURDHandler struct { | |||||
dbiName string | |||||
table *xql.Table | |||||
entity xql.TableIdentified | |||||
pks []string | |||||
notAllowed []string | |||||
preRead CURDPreRead | |||||
preCreate CURDPreCreate | |||||
preUpdate CURDPreUpdate | |||||
preDelete CURDPreDelete | |||||
postRead CURDPostRead | |||||
postCreate CURDPostCreate | |||||
postUpdate CURDPostUpdate | |||||
postDelete CURDPostDelete | |||||
} | |||||
func (h DefaultCURDHandler) Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
for _, x := range h.notAllowed { | |||||
if strings.ToUpper(x) == ctx.Request().Method { | |||||
return Failure_Response(ERROR_METHOD_NOT_ALLOWED, "Method Not Allowed!") | |||||
} | |||||
} | |||||
switch ctx.Request().Method { | |||||
case "GET": | |||||
return h.read(ctx, url_params, queries, post_data) | |||||
case "PATCH": | |||||
return h.update(ctx, url_params, queries, post_data) | |||||
case "PUT": | |||||
return h.update(ctx, url_params, queries, post_data) | |||||
case "POST": | |||||
return h.create(ctx, url_params, queries, post_data) | |||||
case "DELETE": | |||||
return h.delete(ctx, url_params, queries, post_data) | |||||
case "OPTIONS": | |||||
return h.options(ctx, url_params, queries, post_data) | |||||
default: | |||||
return Failure_Response(ERROR_METHOD_NOT_ALLOWED, "Method Not Allowed!") | |||||
} | |||||
} | |||||
func (h DefaultCURDHandler) options(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
result := &RestletResult{} | |||||
result.Model = reflect.TypeOf(h.entity).Name() | |||||
result.Data = h.entity | |||||
return result, nil | |||||
} | |||||
func (h DefaultCURDHandler) read(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||||
defer session.Close() | |||||
result := &RestletResult{} | |||||
entityType := reflect.TypeOf(h.entity) | |||||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||||
entityType = val.Elem().Type() //reflect.TypeOf() | |||||
} | |||||
log.Debugln("DefaultCURDHandler.read> ", h.table.TableName, entityType) | |||||
var queryControl *QueryController | |||||
if h.preRead != nil { | |||||
qc, e := h.preRead.PreRead(ctx, session, url_params, queries) | |||||
if nil != e { | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||||
} | |||||
queryControl = qc | |||||
} else if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||||
} else { | |||||
queryControl = qc | |||||
} | |||||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||||
if nil != pk_mapping { | |||||
obj := reflect.New(entityType) | |||||
row := session.Query(h.table, queryControl.Includes...).Filter(pk_mapping).One() | |||||
err := row.Scan(obj.Elem().Addr().Interface()) | |||||
if nil != err { | |||||
//if e, ok := err.(*pq.Error); ok { | |||||
// log.Errorln("DefaultCURDHandler.read:1> failure:", e) | |||||
//} | |||||
log.Errorln("_make_get_handle:2> failure:", reflect.TypeOf(err), err) | |||||
//return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||||
return Failure_Response(ERROR_NOT_FOUND, "Record Not Found!") | |||||
} | |||||
result.Data = obj.Elem().Addr().Interface() | |||||
//return result, nil | |||||
} else { | |||||
for k, v := range url_params { | |||||
f := xql.QueryFilter{Field: k, Operator: "="} | |||||
f.Value = _build_column_query_value(h.table, k, f.Operator, v) | |||||
queryControl.Filters = append(queryControl.Filters, f) | |||||
} | |||||
qs := session.Query(h.table, queryControl.Includes...).Filter(queryControl.Filters...) | |||||
total, err := qs.Count() | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
result.Control = &ControlResult{} | |||||
result.Control.Total = total | |||||
result.Control.Offset = queryControl.Offset | |||||
result.Control.Limit = queryControl.Limit | |||||
qs = qs.Offset(queryControl.Offset).Limit(queryControl.Limit) | |||||
qs = qs.OrderBy(queryControl.OrderBy...) | |||||
rows, err := qs.All() | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
defer rows.Close() | |||||
objects := reflect.MakeSlice(reflect.SliceOf(reflect.PtrTo(entityType)), 0, 0) | |||||
for rows.Next() { | |||||
obj := reflect.New(entityType) | |||||
err = rows.Scan(obj.Elem().Addr().Interface()) | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.read:> failure:", err) | |||||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
objects = reflect.Append(objects, obj) | |||||
result.Control.Count += 1 | |||||
} | |||||
result.Model = reflect.TypeOf(h.entity).Name() | |||||
result.Data = objects.Interface() | |||||
} | |||||
if h.postRead != nil { | |||||
if ret, e := h.postRead.PostRead(ctx, session, queryControl, result.Data); nil != e { | |||||
log.Errorln("DefaultCURDHandler.read:> failure:", e) | |||||
return Failure_Response(FATAL_DB_READ_FAILED, fmt.Sprintf("%s", e)) | |||||
} else { | |||||
result.Data = ret | |||||
} | |||||
} | |||||
return result, nil | |||||
} | |||||
func (h DefaultCURDHandler) create(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
entityType := reflect.TypeOf(h.entity) | |||||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||||
entityType = val.Elem().Type() //reflect.TypeOf() | |||||
} | |||||
log.Debugln("DefaultCURDHandler.create> ", h.table.TableName, entityType) | |||||
if nil == post_data || len(post_data) < 1 { | |||||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||||
} | |||||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||||
defer session.Close() | |||||
entityObjs := reflect.MakeSlice(reflect.SliceOf(entityType), 1, 2) | |||||
p := reflect.New(reflect.SliceOf(entityType)) | |||||
reflect.Indirect(p).Set(entityObjs) | |||||
if err := json.Unmarshal(post_data, entityObjs.Index(0).Addr().Interface()); nil == err { | |||||
log.Debugln("DefaultCURDHandler.create>>>: Single Object") | |||||
} else if err = json.Unmarshal(post_data, p.Interface()); nil == err { | |||||
log.Debugln("DefaultCURDHandler.create>>>: Slice Objects") | |||||
} else { | |||||
log.Warnln("DefaultCURDHandler.create> Invalid Data:", err) | |||||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Data!") | |||||
} | |||||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||||
for k, v := range url_params { | |||||
if nil == pk_mapping { | |||||
pk_mapping = make(map[string]interface{}) | |||||
pk_mapping[k] = _build_column_query_value(h.table, k, "=", v) | |||||
continue | |||||
} | |||||
if _, ok := pk_mapping[k]; ! ok { | |||||
pk_mapping[k] = _build_column_query_value(h.table, k, "=", v) | |||||
} | |||||
} | |||||
result := &RestletResult{} | |||||
err := session.Begin() | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
//n := 0 | |||||
for i := 0; i < entityObjs.Len(); i++ { | |||||
obj := entityObjs.Index(i) | |||||
if nil != pk_mapping { | |||||
_assign_entity_from_map(obj.Addr().Interface(), pk_mapping, false) | |||||
} | |||||
if h.preCreate != nil { | |||||
if e := h.preCreate.PreCreate(ctx, session, url_params, queries, obj.Addr().Interface()); nil != e { | |||||
log.Errorln("DefaultCURDHandler.create> preCreate failed:", e) | |||||
return Failure_Response(ERROR_INVALID_DATA, fmt.Sprintf("%s", e)) | |||||
} | |||||
} | |||||
log.Debugln("DefaultCURDHandler.create:> Inserting :", obj.Addr().Interface()) | |||||
_, err = session.Query(h.table).Insert(obj.Addr().Interface()) | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||||
session.Rollback() | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
} | |||||
result.Data = entityObjs.Interface() | |||||
if h.postCreate != nil { | |||||
if ret, e := h.postCreate.PostCreate(ctx, session, result.Data); nil != e { | |||||
log.Errorln("DefaultCURDHandler.create:> postCreate failed:", e) | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||||
} else { | |||||
result.Data = ret | |||||
} | |||||
} | |||||
err = session.Commit() | |||||
if nil != err { | |||||
log.Errorln("DefaultCURDHandler.create:> failure:", err) | |||||
session.Rollback() | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", err)) | |||||
} | |||||
result.Code = SUCCESS_CREATED | |||||
return result, nil | |||||
} | |||||
func (h DefaultCURDHandler) update(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
entityType := reflect.TypeOf(h.entity) | |||||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||||
entityType = val.Elem().Type() //reflect.TypeOf() | |||||
} | |||||
log.Debugln("DefaultCURDHandler.update> ", h.table.TableName, entityType) | |||||
if nil == post_data || len(post_data) < 1 { | |||||
log.Warnln("DefaultCURDHandler.update:> Empty DATA.") | |||||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||||
} | |||||
entityObj := reflect.New(entityType) | |||||
var entityMap = make(map[string]interface{}) | |||||
err := json.Unmarshal(post_data, entityObj.Elem().Addr().Interface()) | |||||
e1 := json.Unmarshal(post_data, &entityMap) | |||||
if nil != err || nil != e1 { | |||||
log.Warnln("DefaultCURDHandler.update:> Invalid DATA:", err, e1) | |||||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Data!") | |||||
} | |||||
for k, _ := range entityMap { | |||||
if _, ok := h.table.GetColumn(k); ok { | |||||
continue | |||||
} else { | |||||
log.Warnln("DefaultCURDHandler.update:> Invalid Field:", k) | |||||
return Failure_Response(ERROR_INVALID_DATA, "Invalid Field:"+k) | |||||
} | |||||
} | |||||
if len(entityMap) == 0 { | |||||
log.Warnln("DefaultCURDHandler.update:> Empty DATA:", err, e1) | |||||
return Failure_Response(ERROR_INVALID_DATA, "Empty Data!") | |||||
} | |||||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||||
defer session.Close() | |||||
var updateCols []xql.UpdateColumn | |||||
for _, c := range h.table.GetColumns() { | |||||
if _, ok := entityMap[c.FieldName]; ok { | |||||
} else if _, ok := entityMap[c.Jtag]; ok { | |||||
} else if _, ok := entityMap[c.ElemName]; ok { | |||||
} else { | |||||
continue | |||||
} | |||||
if c.PrimaryKey { | |||||
return Failure_Response(ERROR_FORBIDDEN, "Not Allowed to Change Primary Key(s).") | |||||
} | |||||
uc := xql.UpdateColumn{Field: c.FieldName, Operator: "="} | |||||
val := entityObj.Elem().FieldByName(c.ElemName) | |||||
switch val.Kind() { | |||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | |||||
uc.Value = val.Int() | |||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | |||||
uc.Value = val.Uint() | |||||
case reflect.String: | |||||
uc.Value = val.String() | |||||
case reflect.Bool: | |||||
uc.Value = val.Bool() | |||||
case reflect.Float32, reflect.Float64: | |||||
uc.Value = val.Float() | |||||
default: | |||||
uc.Value = val.Interface() | |||||
} | |||||
updateCols = append(updateCols, uc) | |||||
} | |||||
var queryControl *QueryController | |||||
if h.preUpdate != nil { | |||||
if qc, cols, e := h.preUpdate.PreUpdate(ctx, session, url_params, queries, updateCols); nil != e { | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||||
} else { | |||||
queryControl = qc | |||||
updateCols = cols | |||||
} | |||||
} else { | |||||
if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||||
log.Errorln("DefaultCURDHandler.update:> failure:", err) | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||||
} else { | |||||
queryControl = qc | |||||
} | |||||
} | |||||
log.Debugln("DefaultCURDHandler.update> QueryControl:", queryControl) | |||||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||||
if nil != pk_mapping { | |||||
queryControl.Filters = append(queryControl.Filters, pk_mapping) | |||||
} | |||||
n, e := session.Query(h.table).Filter(queryControl.Filters...).Update(updateCols) | |||||
if nil != e { | |||||
log.Errorln("DefaultCURDHandler.update:> failure:", e) | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||||
} | |||||
if h.postUpdate != nil { | |||||
updateCols, e = h.postUpdate.PostUpdate(ctx, session, queryControl, updateCols) | |||||
if nil != e { | |||||
log.Errorln("DefaultCURDHandler.update:> post update failure:", e) | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprintf("%s", e)) | |||||
} | |||||
} | |||||
var updates = make(map[string]interface{}) | |||||
for _, x := range updateCols { | |||||
updates[x.Field] = x.Value | |||||
} | |||||
result := &RestletResult{ | |||||
Code: SUCCESS_OK, | |||||
Model: entityType.Name(), | |||||
Data: map[string]interface{}{ | |||||
"count": n, | |||||
"updates": updates, | |||||
}, | |||||
} | |||||
return result, nil | |||||
} | |||||
func (h DefaultCURDHandler) delete(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
session := xql.MakeSession(ctx.SQL(h.dbiName), "postgres", true) | |||||
defer session.Close() | |||||
result := &RestletResult{} | |||||
entityType := reflect.TypeOf(h.entity) | |||||
if val := reflect.ValueOf(h.entity); val.Kind() == reflect.Ptr { | |||||
entityType = val.Elem().Type() //reflect.TypeOf() | |||||
} | |||||
result.Model = reflect.TypeOf(h.entity).Name() | |||||
log.Debugln("DefaultCURDHandler.delete> ", h.table.TableName, entityType) | |||||
var queryControl *QueryController | |||||
if h.preDelete != nil { | |||||
if qc, e := h.preDelete.PreDelete(ctx, session, url_params, queries); nil != e { | |||||
log.Errorln("DefaultCURDHandler.delete:> pre delete failure:", e) | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", e)) | |||||
} else { | |||||
queryControl = qc | |||||
} | |||||
} else { | |||||
if qc, err := Build_QueryControl(queries, h.table); nil != err { | |||||
log.Errorln("DefaultCURDHandler.delete:> failure:", err) | |||||
return Failure_Response(ERROR_BAD_REQUEST, fmt.Sprintf("%s", err)) | |||||
} else { | |||||
queryControl = qc | |||||
} | |||||
} | |||||
var pk_mapping = _build_params_map(h.table, url_params, h.pks...) | |||||
if nil != pk_mapping { | |||||
n, err := session.Query(h.table).Filter(pk_mapping).Delete() | |||||
if nil != err { | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(err)) | |||||
} | |||||
result.Code = SUCCESS_DELETED | |||||
result.Data = map[string]interface{}{"Deleted": n} | |||||
return result, nil | |||||
} | |||||
for k, v := range url_params { | |||||
f := xql.QueryFilter{Field: k, Operator: "="} | |||||
f.Value = _build_column_query_value(h.table, k, f.Operator, v) | |||||
queryControl.Filters = append(queryControl.Filters, f) | |||||
} | |||||
if len(queryControl.Filters) < 1 { | |||||
return Failure_Response(ERROR_FORBIDDEN, "Not allowed to delete without conditions!") | |||||
} | |||||
n, err := session.Query(h.table).Filter(queryControl.Filters...).Delete() | |||||
if nil != err { | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(err)) | |||||
} | |||||
if h.postDelete != nil { | |||||
if e := h.postDelete.PostDelete(ctx, session, queryControl); nil != e { | |||||
return Failure_Response(FATAL_DB_WRITE_FAILED, fmt.Sprint(e)) | |||||
} | |||||
} | |||||
result.Code = SUCCESS_OK | |||||
result.Data = map[string]interface{}{"deleted": n} | |||||
return result, nil | |||||
} | |||||
func NewCURDHandler(dbiname string, kind interface{}, notAllowedMethods ...string) RestletHandler { | |||||
var h = DefaultCURDHandler{dbiName: dbiname, notAllowed: notAllowedMethods} | |||||
if h.dbiName == "" { | |||||
h.dbiName = "default" | |||||
} | |||||
if k, b := kind.(xql.TableIdentified); b { | |||||
h.entity = k | |||||
} else if k, b := kind.(CURDKind); b { | |||||
h.entity = k.Entity() | |||||
} else { | |||||
panic("kind param should at least implement CURDKind or xql.TableIdentified interface!") | |||||
} | |||||
if k, b := kind.(CURDTable); b { | |||||
h.table = k.Table() | |||||
} else { | |||||
h.table = xql.DeclareTable(h.entity) | |||||
} | |||||
for _, x := range h.table.GetPrimaryKeys() { | |||||
h.pks = append(h.pks, x.FieldName) | |||||
} | |||||
if k, b := kind.(CURDPreRead); b { | |||||
h.preRead = k | |||||
} | |||||
if k, b := kind.(CURDPreCreate); b { | |||||
h.preCreate = k | |||||
} | |||||
if k, b := kind.(CURDPreUpdate); b { | |||||
h.preUpdate = k | |||||
} | |||||
if k, b := kind.(CURDPreDelete); b { | |||||
h.preDelete = k | |||||
} | |||||
if k, b := kind.(CURDPostRead); b { | |||||
h.postRead = k | |||||
} | |||||
if k, b := kind.(CURDPostCreate); b { | |||||
h.postCreate = k | |||||
} | |||||
if k, b := kind.(CURDPostUpdate); b { | |||||
h.postUpdate = k | |||||
} | |||||
if k, b := kind.(CURDPostDelete); b { | |||||
h.postDelete = k | |||||
} | |||||
return h | |||||
} | |||||
func _build_params_map(table *xql.Table, params Parameters, pks ...string) map[string]interface{} { | |||||
var pk_mapping map[string]interface{} | |||||
for i, pk := range pks { | |||||
if v, ok := params.GetString(pk); ok { | |||||
log.Debugln("_make_get_handle:> Pk", i, pk, v) | |||||
if nil == pk_mapping { | |||||
pk_mapping = make(map[string]interface{}) | |||||
} | |||||
pk_mapping[table.GetPrimaryKeys()[i].FieldName] = | |||||
_build_column_query_value(table, table.GetPrimaryKeys()[i].FieldName, "=", v) | |||||
delete(params, pk) | |||||
} | |||||
} | |||||
if len(pks) > 0 { | |||||
return pk_mapping | |||||
} | |||||
for k, v := range params { | |||||
if nil == pk_mapping { | |||||
pk_mapping = make(map[string]interface{}) | |||||
} | |||||
pk_mapping[k] = _build_column_query_value(table, k, "=", v) | |||||
} | |||||
return pk_mapping | |||||
} | |||||
func _assign_entity_from_map(entity interface{}, params map[string]interface{}, recursive bool) error { | |||||
log.Debugln("_assign_entity_from_map:>>>", entity, params) | |||||
if nil != entity { | |||||
et := reflect.TypeOf(entity) | |||||
ev := reflect.ValueOf(entity) | |||||
for i := 0; i < et.Elem().NumField(); i++ { | |||||
f := et.Elem().Field(i) | |||||
x_tag := strings.Split(f.Tag.Get("xql"), ",")[0] | |||||
if x_tag == "-" { | |||||
continue | |||||
} | |||||
if f.Anonymous && !recursive { | |||||
_assign_entity_from_map(ev.Elem().Field(i).Addr().Interface(), params, true) | |||||
continue | |||||
} | |||||
json_tag := strings.Split(f.Tag.Get("json"), ",")[0] | |||||
var val interface{} | |||||
if v, ok := params[x_tag]; ok { | |||||
log.Debugln("_assign_entity_from_map:>>>", 1, v) | |||||
val = v | |||||
} else if v, ok := params[json_tag]; ok { | |||||
log.Debugln("_assign_entity_from_map:>>>", 2, v) | |||||
val = v | |||||
} else if v, ok := params[f.Name]; ok { | |||||
log.Debugln("_assign_entity_from_map:>>>", 3, v) | |||||
val = v | |||||
} else if v, ok := params[xql.Camel2Underscore(f.Name)]; ok { | |||||
log.Debugln("_assign_entity_from_map:>>>", 4, v) | |||||
val = v | |||||
} else { | |||||
continue | |||||
} | |||||
fv := ev.Elem().Field(i) | |||||
if fv.IsValid() && fv.CanSet() { | |||||
fv.Set(reflect.ValueOf(val)) | |||||
} | |||||
} | |||||
} else { | |||||
panic("Empty pointer of entity!") | |||||
} | |||||
return nil | |||||
} | |||||
func translate_datetime(s string) time.Time { | |||||
if t, e := time.Parse(time.RFC3339Nano, s); nil == e { | |||||
return t | |||||
} else if t, e := time.Parse(time.RFC3339, s); nil == e { | |||||
return t | |||||
} else if t, e := time.Parse("2006-01-02T15:04:05", s); nil == e { | |||||
return t | |||||
} else if t, e := time.Parse("2006-01-02T15:04", s); nil == e { | |||||
return t | |||||
} else if t, e := time.Parse("2006-01-02", s); nil == e { | |||||
return t | |||||
} else if t, e := time.Parse("2006", s); nil == e { | |||||
return t | |||||
} else { | |||||
return time.Time{} | |||||
} | |||||
} | |||||
func translate_single_value(k reflect.Kind, val string) interface{} { | |||||
switch k { | |||||
case reflect.Bool: | |||||
switch strings.ToLower(val) { | |||||
case "yes", "true", "y", "t", "ok": | |||||
return true | |||||
default: | |||||
return false | |||||
} | |||||
case reflect.Int: | |||||
n, _ := strconv.ParseInt(val, 10, 32) | |||||
return int(n) | |||||
case reflect.Int8: | |||||
n, _ := strconv.ParseInt(val, 10, 8) | |||||
return int8(n) | |||||
case reflect.Int16: | |||||
n, _ := strconv.ParseInt(val, 10, 16) | |||||
return int16(n) | |||||
case reflect.Int64: | |||||
n, _ := strconv.ParseInt(val, 10, 64) | |||||
return int64(n) | |||||
case reflect.Uint: | |||||
n, _ := strconv.ParseUint(val, 10, 32) | |||||
return uint(n) | |||||
case reflect.Uint8: | |||||
n, _ := strconv.ParseUint(val, 10, 8) | |||||
return uint8(n) | |||||
case reflect.Uint16: | |||||
n, _ := strconv.ParseUint(val, 10, 16) | |||||
return uint16(n) | |||||
case reflect.Uint64: | |||||
n, _ := strconv.ParseUint(val, 10, 64) | |||||
return uint64(n) | |||||
case reflect.String: | |||||
return val | |||||
case reflect.Float32: | |||||
f, _ := strconv.ParseFloat(val, 32) | |||||
return float32(f) | |||||
case reflect.Float64: | |||||
f, _ := strconv.ParseFloat(val, 64) | |||||
return float64(f) | |||||
case reflect.TypeOf(time.Time{}).Kind(): | |||||
t := translate_datetime(val) | |||||
return t | |||||
case reflect.TypeOf(&time.Time{}).Kind(): | |||||
t := translate_datetime(val) | |||||
return &t | |||||
default: | |||||
return val | |||||
} | |||||
return nil | |||||
} | |||||
func _build_column_query_value(table *xql.Table, field string, operator string, val string) interface{} { | |||||
if col, b := table.GetColumn(field); !b { | |||||
return val | |||||
} else { | |||||
if col.Type.Kind() == reflect.Slice { | |||||
et := col.Type.Elem() | |||||
vv := reflect.MakeSlice(col.Type, 0, 0) | |||||
for _, x := range strings.Split(val, ",") { | |||||
if x == "" { | |||||
continue | |||||
} | |||||
v := translate_single_value(et.Kind(), x) | |||||
//fmt.Println(">>>>", v,reflect.ValueOf(v)) | |||||
vv = reflect.Append(vv, reflect.ValueOf(v)) | |||||
//fmt.Println(">>>>",vv) | |||||
} | |||||
return vv.Interface() | |||||
} else if operator == "IN" { | |||||
vv := reflect.MakeSlice(reflect.SliceOf(col.Type), 0, 0) | |||||
for _, x := range strings.Split(val, ",") { | |||||
if x == "" { | |||||
continue | |||||
} | |||||
v := translate_single_value(col.Type.Kind(), x) | |||||
//fmt.Println(">>>>", v,reflect.ValueOf(v)) | |||||
vv = reflect.Append(vv, reflect.ValueOf(v)) | |||||
//fmt.Println(">>>>",vv) | |||||
} | |||||
return vv.Interface() | |||||
} else { | |||||
return translate_single_value(col.Type.Kind(), val) | |||||
} | |||||
} | |||||
return val | |||||
} |
@ -0,0 +1,42 @@ | |||||
package restlet | |||||
import ( | |||||
"errors" | |||||
"github.com/dgrijalva/jwt-go" | |||||
"fmt" | |||||
) | |||||
const ( | |||||
RESTLET_JWT_KEYNAME = "jwt_secret" | |||||
RESTLET_JWT_DEFAULT_KEY = "thisisanotherrun" | |||||
) | |||||
func ParseToken(ctx RequestContext, claims jwt.Claims, tkstring string) error { | |||||
if token, err := jwt.ParseWithClaims(tkstring, claims, func(tk *jwt.Token)(interface{}, error){ | |||||
if _, ok := tk.Method.(*jwt.SigningMethodHMAC); !ok { | |||||
return nil, fmt.Errorf("Unexpected signing method: %v", tk.Header["alg"]) | |||||
} | |||||
secret := ctx.Config().GetString(RESTLET_JWT_KEYNAME, RESTLET_JWT_DEFAULT_KEY) | |||||
return []byte(secret), nil | |||||
}); nil != err { | |||||
return err | |||||
}else if !token.Valid { | |||||
return errors.New("Invalid token!") | |||||
}else{ | |||||
return nil | |||||
} | |||||
} | |||||
func SignToken(ctx RequestContext, claims jwt.Claims) (string, error) { | |||||
tk := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) | |||||
secret := ctx.Config().GetString(RESTLET_JWT_KEYNAME, RESTLET_JWT_DEFAULT_KEY) | |||||
if s, e := tk.SignedString([]byte(secret));nil != e { | |||||
fmt.Println("Error:1>", e) | |||||
return "", e | |||||
}else { | |||||
return s, nil | |||||
} | |||||
} |
@ -0,0 +1,53 @@ | |||||
package restlet | |||||
import ( | |||||
"fmt" | |||||
log "github.com/Sirupsen/logrus" | |||||
"github.com/dgrijalva/jwt-go" | |||||
"github.com/dgrijalva/jwt-go/request" | |||||
"net/http" | |||||
) | |||||
func GetSession(ctx RequestContext, secret, cookiename string, claims jwt.Claims) error { | |||||
if e := ExtractClaimsViaHeader(ctx.Request(), secret, claims); nil == e { | |||||
log.Debugln("> GetSession via Header:>", claims) | |||||
return nil | |||||
} else { | |||||
log.Debugln("> GetSession via Header failed:>", e) | |||||
} | |||||
if e := ExtractClaimsViaCookie(ctx.Request(), cookiename, secret, claims); nil == e { | |||||
log.Debugln("> GetSession via Cookie:>", claims) | |||||
return nil | |||||
} else { | |||||
log.Debugln("> GetSession via Cookie failed:>", e) | |||||
} | |||||
return fmt.Errorf("can not read session token") | |||||
} | |||||
func ExtractClaimsViaHeader(r *http.Request, secret string, o jwt.Claims) error { | |||||
if token, err := request.ParseFromRequestWithClaims(r, request.AuthorizationHeaderExtractor, o, | |||||
func(token *jwt.Token) (interface{}, error) { | |||||
return []byte(secret), nil | |||||
}); nil != err { | |||||
return err | |||||
} else if ! token.Valid { | |||||
return fmt.Errorf("token invalid") | |||||
} | |||||
return nil | |||||
} | |||||
func ExtractClaimsViaCookie(r *http.Request, ckname string, secret string, o jwt.Claims) error { | |||||
for _, x := range r.Cookies() { | |||||
if x.Name == ckname { | |||||
if token, e := jwt.ParseWithClaims(x.Value, o, func(token *jwt.Token) (interface{}, error) { | |||||
return []byte(secret), nil | |||||
}); nil != e { | |||||
return e | |||||
} else if token.Valid { | |||||
return nil | |||||
} | |||||
break | |||||
} | |||||
} | |||||
return fmt.Errorf("not found") | |||||
} |
@ -1,11 +1,347 @@ | |||||
package restlet | package restlet | ||||
import ( | import ( | ||||
"github.com/archsh/go.xql" | |||||
"crypto/sha1" | |||||
"encoding/json" | |||||
"errors" | |||||
"fmt" | |||||
"io/ioutil" | |||||
"net/http" | "net/http" | ||||
"reflect" | |||||
"strings" | |||||
"time" | |||||
log "github.com/Sirupsen/logrus" | |||||
"github.com/archsh/go.xql" | |||||
//"cygnux.net/kepler/utils" | |||||
"github.com/gorilla/mux" | |||||
) | ) | ||||
func MakeRestletHandler(entity xql.TableIdentified, schema string) http.Handler { | |||||
var _debugInfo bool = false | |||||
func Build_QueryControl(queries Parameters, table *xql.Table, skips ...bool) (*QueryController, error) { | |||||
log.Debugln("_process_request_ctrl>>", table.TableName, queries) | |||||
qc := &QueryController{} | |||||
var include_fields = make(map[string]bool) | |||||
qc.Offset, _ = queries.GetInt64(QCTRL_OFFSET, QCTRL_OFFSET_DEFAULT) | |||||
delete(queries, QCTRL_OFFSET) | |||||
qc.Limit, _ = queries.GetInt64(QCTRL_LIMIT, QCTRL_LIMIT_DEFAULT) | |||||
delete(queries, QCTRL_LIMIT) | |||||
if ss, ok := queries.GetString(QCTRL_ORDERBY); ok { | |||||
kss := strings.Split(ss, ",") | |||||
for _, x := range kss { | |||||
qc.OrderBy = append(qc.OrderBy, x) | |||||
} | |||||
delete(queries, QCTRL_ORDERBY) | |||||
} | |||||
if ss, ok := queries.GetString(QCTRL_GROUPBY); ok { | |||||
kss := strings.Split(ss, ",") | |||||
for _, x := range kss { | |||||
qc.GroupBy = append(qc.GroupBy, x) | |||||
} | |||||
delete(queries, QCTRL_GROUPBY) | |||||
} | |||||
if ss, ok := queries.GetString(QCTRL_DEBUG); ok { | |||||
switch strings.ToLower(ss) { | |||||
case "true", "yes", "ok", "1", "t", "y": | |||||
qc.Debug = true | |||||
} | |||||
delete(queries, QCTRL_DEBUG) | |||||
} | |||||
if ss, ok := queries.GetString(QCTRL_INCLUDES); ok { | |||||
for _, x := range strings.Split(ss, ",") { | |||||
include_fields[x] = true | |||||
} | |||||
delete(queries, QCTRL_INCLUDES) | |||||
} else { | |||||
for _, c := range table.GetColumns() { | |||||
include_fields[c.FieldName] = true | |||||
} | |||||
} | |||||
if ss, ok := queries.GetString(QCTRL_EXCLUDES); ok { | |||||
for _, x := range strings.Split(ss, ",") { | |||||
include_fields[x] = false | |||||
} | |||||
delete(queries, QCTRL_EXCLUDES) | |||||
} | |||||
for k, v := range include_fields { | |||||
if ! v { | |||||
continue | |||||
} | |||||
if c, ok := table.GetColumn(k); ! ok { | |||||
return nil, errors.New("Invalid column:" + k) | |||||
} else { | |||||
qc.Includes = append(qc.Includes, c.FieldName) | |||||
} | |||||
} | |||||
if len(skips) > 0 && skips[0] == true { | |||||
return qc, nil | |||||
} | |||||
for k, v := range queries { | |||||
keys := strings.Split(k, "__") | |||||
if len(keys) > 3 { | |||||
return nil, fmt.Errorf("invalid expression: %s", k) | |||||
} | |||||
c, ok := table.GetColumn(keys[0]) | |||||
if ! ok { | |||||
return nil, fmt.Errorf("unknow column: %s", keys[0]) | |||||
} | |||||
f := xql.QueryFilter{Field: c.FieldName} | |||||
if len(keys) == 1 { | |||||
f.Operator = "=" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
} else if len(keys) == 2 { | |||||
switch strings.ToUpper(keys[1]) { | |||||
case "=": | |||||
f.Operator = "=" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
case "IN": | |||||
f.Operator = "IN" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
//fmt.Println(">IN>>>>>>>>>>>",f.Value) | |||||
if reflect.TypeOf(f.Value).Kind() == reflect.Slice { | |||||
if reflect.ValueOf(f.Value).Len() < 1 { | |||||
continue | |||||
} | |||||
} | |||||
case "HAS": | |||||
f.Operator = "@>" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
//fmt.Println(">HAS>>>>>>>>>>>",f.Value) | |||||
if reflect.TypeOf(f.Value).Kind() == reflect.Slice { | |||||
if reflect.ValueOf(f.Value).Len() < 1 { | |||||
continue | |||||
} | |||||
} | |||||
case "STARTSWITH": | |||||
f.Operator = "LIKE" | |||||
f.Value = v + "%" | |||||
case "ENDSWITH": | |||||
f.Operator = "LIKE" | |||||
f.Value = "%" + v | |||||
case "CONTAINS": | |||||
f.Operator = "LIKE" | |||||
f.Value = "%" + v + "%" | |||||
case "GT": | |||||
f.Operator = ">" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
case "GTE": | |||||
f.Operator = ">=" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
case "LT": | |||||
f.Operator = "<" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
case "LTE": | |||||
f.Operator = "<=" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
case "NOT": | |||||
f.Operator = "<>" | |||||
f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
default: | |||||
return nil, fmt.Errorf("unknow operator: %s", keys[1]) | |||||
} | |||||
} | |||||
if len(keys) == 3 { | |||||
switch strings.ToUpper(keys[2]) { | |||||
case "AND": | |||||
f.Condition = xql.CONDITION_AND | |||||
case "OR": | |||||
f.Condition = xql.CONDITION_OR | |||||
default: | |||||
return nil, fmt.Errorf("unknow condition: %s", keys[2]) | |||||
} | |||||
} | |||||
//f.Value = _build_column_query_value(table, c.FieldName, f.Operator, v) | |||||
qc.Filters = append(qc.Filters, f) | |||||
} | |||||
return qc, nil | |||||
} | |||||
func MatchMethod(m string, methods []string) bool { | |||||
if len(methods) == 0 || methods[0] == "*" { | |||||
return true | |||||
} | |||||
for _, s := range methods { | |||||
if s == m { | |||||
return true | |||||
} | |||||
} | |||||
return false | |||||
} | |||||
func HashKey(s ...string) string { | |||||
h := sha1.New() | |||||
ns := strings.Join(s, "") | |||||
h.Write([]byte(ns)) | |||||
bs := h.Sum(nil) | |||||
return fmt.Sprintf("%x", bs) | |||||
} | |||||
func SetDebug(d bool) { | |||||
_debugInfo = d | |||||
} | |||||
func Failure_Response(code uint, message string) (*RestletResult, error) { | |||||
return &RestletResult{ | |||||
Code: code, | |||||
Message: message, | |||||
}, nil //errors.New(message) | |||||
} | |||||
func MakeRestletHandler(h RestletHandler, predictor RequestPredictor, ctx_provider RequestContextProvider, methods []string, cache *CacheController) http.Handler { | |||||
var f = func(response http.ResponseWriter, request *http.Request) { | |||||
var t1 int64 | |||||
if _debugInfo { | |||||
t1 = time.Now().Unix() | |||||
} | |||||
log.Infoln(">", request.Method, request.URL.String()) | |||||
if msg, ret := predictor.Predicate(request); !ret { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(403) | |||||
response.Write([]byte(msg)) | |||||
return | |||||
} | |||||
if ! MatchMethod(request.Method, methods) { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(405) | |||||
response.Write([]byte(fmt.Sprintf("Method '%s' not allowed!", request.Method))) | |||||
return | |||||
} | |||||
//var ctx *RequestContext | |||||
var url_params Parameters | |||||
var queries Parameters = Parameters(make(map[string]string)) | |||||
var post_data []byte | |||||
var c_key string | |||||
ctx := ctx_provider.NewContext(request) | |||||
url_params = Parameters(mux.Vars(request)) | |||||
log.Debugln("makeRestletHandler:>>>", request.URL.Query()) | |||||
for k, v := range request.URL.Query() { | |||||
log.Debugln("makeRestletHandler:>>>", k, v) | |||||
queries[k] = v[0] | |||||
} | |||||
if nil != cache && MatchMethod(request.Method, cache.CacheMethods) { | |||||
c_key = cache.KeyPrefix + HashKey(request.Method, request.URL.String()) | |||||
if d, t := ctx.Cache().Get(c_key); t { | |||||
log.Debugln("Restlet:> Hit cache for :> ", c_key, request.Method, request.URL.String()) | |||||
content_type := strings.TrimRightFunc(string(d[:CONTENT_HEADER_SIZE]), func(i rune) bool { | |||||
return i == 0 | |||||
}) // http.DetectContentType(d) | |||||
log.Debugln("Restlet:> detect content-type:", content_type) | |||||
response.Header().Set("Content-Type", content_type) | |||||
response.WriteHeader(200) | |||||
response.Write(d[CONTENT_HEADER_SIZE:]) | |||||
if _debugInfo { | |||||
t2 := time.Now().Unix() | |||||
log.Debugln("Request Time:", t1, t2, t2-t1, request.URL.String()) | |||||
} | |||||
return | |||||
} | |||||
} | |||||
switch request.Method { | |||||
case "POST", "PUT", "PATCH": | |||||
if buf, e := ioutil.ReadAll(request.Body); e == nil { | |||||
post_data = buf | |||||
} else { | |||||
log.Errorln("Restlet:> Read Body error:>", e) | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR) | |||||
response.Write([]byte(fmt.Sprintf("Read Body error:> %s", e))) | |||||
return | |||||
} | |||||
} | |||||
if r, e := h.Handle(ctx, url_params, queries, post_data); nil != e { | |||||
if nil == r { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR) | |||||
response.Write([]byte(fmt.Sprintf("Failed:> %s", e))) | |||||
} else { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(int(r.Code / 100)) | |||||
response.Write([]byte(r.Message)) | |||||
} | |||||
} else { | |||||
r.ETag = fmt.Sprintf("%016x", time.Now().Unix()) | |||||
if _debugInfo { | |||||
t2 := time.Now().Unix() | |||||
r.Debug = &DebugResult{Start: t1, Finish: t2} | |||||
} | |||||
var output interface{} | |||||
var err error | |||||
var pbytes []byte | |||||
var ok bool | |||||
if r.DataOnly { | |||||
output = r.Data | |||||
} else { | |||||
output = r | |||||
} | |||||
if r.RawBytes { | |||||
err = nil | |||||
pbytes, ok = output.([]byte) | |||||
if !ok { | |||||
panic("Restlet:> Data not row bytes!") | |||||
} | |||||
} else { | |||||
r.ContentType = "application/json" | |||||
pbytes, err = json.Marshal(output) | |||||
} | |||||
if nil != err { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(FATAL_INTERNAL_SERVER_ERROR / 100) | |||||
response.Write([]byte(fmt.Sprintf("Failed:> %s", e))) | |||||
} else { | |||||
statusCode := int(r.Code / 100) | |||||
if statusCode/100 < 1 || statusCode/100 > 5 { | |||||
statusCode = 200 | |||||
} | |||||
for _, x := range r.Cookies { | |||||
//x.Domain = ctx.Config().GetString("service.cookie_domain", "*.cygnux-tv.top") | |||||
http.SetCookie(response, x) | |||||
} | |||||
response.Header().Set("Content-Type", r.ContentType) | |||||
response.WriteHeader(statusCode) | |||||
response.Write(pbytes) | |||||
if nil != cache && MatchMethod(request.Method, cache.CacheMethods) /* && r.Cacheable */ { | |||||
var ct_bytes [CONTENT_HEADER_SIZE]byte | |||||
copy(ct_bytes[:], []byte(r.ContentType)[:CONTENT_HEADER_SIZE]) | |||||
ctx.Cache().Set(c_key, append(ct_bytes[:], pbytes...), cache.Expires) | |||||
log.Debugln("Restlet:> Cached Content for:> ", c_key, request.Method, request.URL.String()) | |||||
} | |||||
} | |||||
if _debugInfo { | |||||
t2 := time.Now().Unix() | |||||
log.Debugln("Request Time:", t1, t2, t2-t1, request.URL.String()) | |||||
} | |||||
} | |||||
} | |||||
return http.HandlerFunc(f) | |||||
} | |||||
return nil | |||||
func MakeRawletHandler(h RawletHandler, predictor RequestPredictor, ctx_provider RequestContextProvider, methods []string) http.Handler { | |||||
var f = func(response http.ResponseWriter, request *http.Request) { | |||||
log.Infoln(">", request.Method, request.URL.String()) | |||||
if msg, ret := predictor.Predicate(request); !ret { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(403) | |||||
response.Write([]byte(msg)) | |||||
return | |||||
} | |||||
if ! MatchMethod(request.Method, methods) { | |||||
response.Header().Set("Content-Type", "text/plain") | |||||
response.WriteHeader(405) | |||||
response.Write([]byte(fmt.Sprintf("Method '%s' not allowed!", request.Method))) | |||||
return | |||||
} | |||||
var params Parameters | |||||
ctx := ctx_provider.NewContext(request) | |||||
params = Parameters(mux.Vars(request)) | |||||
log.Debugln("MakeRawletHandler:>>>", request.URL.Query()) | |||||
h.Handle(ctx, params, response, request) | |||||
} | |||||
return http.HandlerFunc(f) | |||||
} | } |
@ -0,0 +1,112 @@ | |||||
package restlet | |||||
import ( | |||||
"strings" | |||||
"strconv" | |||||
"github.com/archsh/go.uuid" | |||||
) | |||||
func (h Parameters) HasKey(k string) bool { | |||||
if nil == h { | |||||
return false | |||||
} | |||||
_, ok := h[k] | |||||
return ok | |||||
} | |||||
func (h Parameters) GetInt(k string, defaults ... int) (int, bool) { | |||||
d := 0 | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
if n, e := strconv.Atoi(ns); e == nil { | |||||
return n, true | |||||
} | |||||
} | |||||
return d, false | |||||
} | |||||
func (h Parameters) GetInt64(k string, defaults ... int64) (int64, bool) { | |||||
var d int64 = 0 | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
if n, e := strconv.ParseInt(ns, 10, 64); e == nil { | |||||
return n, true | |||||
} | |||||
} | |||||
return d, false | |||||
} | |||||
func (h Parameters) GetUInt(k string, defaults ... uint) (uint, bool) { | |||||
var d uint = 0 | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
if n, e := strconv.ParseUint(ns, 10, 32); e == nil { | |||||
return uint(n), true | |||||
} | |||||
} | |||||
return d, false | |||||
} | |||||
func (h Parameters) GetString(k string, defaults ... string) (string, bool) { | |||||
d := "" | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
return ns, true | |||||
} | |||||
return d, false | |||||
} | |||||
func (h Parameters) GetBool(k string, defaults ... bool) (bool, bool) { | |||||
d := false | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
switch strings.ToLower(ns) { | |||||
case "t", "true", "yes", "ok": | |||||
return true, true | |||||
case "f", "false", "no": | |||||
return false, true | |||||
} | |||||
} | |||||
return d, false | |||||
} | |||||
func (h Parameters) GetUUID(k string, defaults ... uuid.UUID) (uuid.UUID, bool) { | |||||
d := uuid.UUID{} | |||||
if len(defaults) > 0 { | |||||
d = defaults[0] | |||||
} | |||||
if nil == h { | |||||
return d, false | |||||
} | |||||
if ns, ok := h[k]; ok { | |||||
if uid, e := uuid.FromString(ns); nil == e { | |||||
return uid, true | |||||
} | |||||
} | |||||
return d, false | |||||
} |
@ -1,15 +1,162 @@ | |||||
package restlet | package restlet | ||||
type RestletControl struct { | |||||
Offset int64 `json:"offset" xml:"offset,attr"` | |||||
Limit int64 `json:"limit" xml:"limit,attr"` | |||||
Count int64 `json:"count" xml:"count,attr"` | |||||
Total int64 `json:"total" xml:"total,attr"` | |||||
} | |||||
type RestletResponse struct { | |||||
Code int `json:"Code" xml:"Code"` | |||||
Message string `json:"message" xml:"Message"` | |||||
Control *RestletControl `json:"ctrl,omitempty" xml:"Control,omitempty"` | |||||
Data interface{} `json:"data" xml:"Data"` | |||||
} | |||||
import ( | |||||
"database/sql" | |||||
"net/http" | |||||
"cygnux.net/kepler/cache" | |||||
"cygnux.net/kepler/config" | |||||
"cygnux.net/kepler/database" | |||||
"cygnux.net/kepler/kv" | |||||
) | |||||
type Parameters map[string]string | |||||
type ControlResult struct { | |||||
Total int64 `json:"total"` | |||||
Count int64 `json:"count"` | |||||
Offset int64 `json:"offset"` | |||||
Limit int64 `json:"limit"` | |||||
} | |||||
type DebugResult struct { | |||||
Start int64 | |||||
Finish int64 | |||||
} | |||||
type RestletResult struct { | |||||
Code uint `json:"code"` | |||||
Message string `json:"message,omitempty"` | |||||
ContentType string `json:"-"` | |||||
DataOnly bool `json:"-"` | |||||
RawBytes bool `json:"-"` | |||||
Cacheable bool | |||||
Headers http.Header `json:"-"` | |||||
Cookies []*http.Cookie `json:"-"` | |||||
ETag string `json:"__eTag,omitempty"` | |||||
Debug *DebugResult `json:"__debug,omitempty"` | |||||
Model string `json:"__model,omitempty"` | |||||
IsList bool `json:"__is_list,omitempty"` | |||||
Control *ControlResult `json:"__control,omitempty"` | |||||
Data interface{} `json:"data"` | |||||
} | |||||
type RawletResult struct { | |||||
Code uint | |||||
Message string | |||||
ContentType string | |||||
Data []byte | |||||
} | |||||
type QueryController struct { | |||||
Offset int64 | |||||
Limit int64 | |||||
Debug bool | |||||
Includes []interface{} | |||||
Excludes []interface{} | |||||
OrderBy []interface{} | |||||
GroupBy []interface{} | |||||
Filters []interface{} | |||||
Params map[string]interface{} | |||||
} | |||||
type RestletError struct { | |||||
msg string | |||||
code uint | |||||
} | |||||
func (e RestletError) Error() string { | |||||
return e.msg | |||||
} | |||||
func (e RestletError) Code() uint { | |||||
return e.code | |||||
} | |||||
func MakeError(code uint, msg string) *RestletError { | |||||
return &RestletError{ | |||||
code: code, | |||||
msg: msg, | |||||
} | |||||
} | |||||
//type MessageHandleFunc func ([]byte) error | |||||
// | |||||
//type MessageQueue interface { | |||||
// Chan(channel string, body []byte) error | |||||
// Subscribe(channel string, queue string, handle MessageHandleFunc) error | |||||
//} | |||||
type RestletContext interface { | |||||
Schema(name ...string) string | |||||
SQL(name ...string) *sql.DB | |||||
DBI(name ...string) database.DBI | |||||
Chan(string, ...interface{}) error | |||||
Config() config.Config | |||||
Cache(name ...string) cache.Cache | |||||
KV(name ...string) kv.KV | |||||
Publish(string, ...[]byte) error | |||||
} | |||||
type RequestContext interface { | |||||
RestletContext | |||||
Request() *http.Request | |||||
} | |||||
type RequestContextProvider interface { | |||||
NewContext(*http.Request) RequestContext | |||||
} | |||||
type RequestPredictor interface { | |||||
Predicate(*http.Request) (string, bool) | |||||
} | |||||
type CacheController struct { | |||||
KeyPrefix string | |||||
CacheMethods []string | |||||
CacheStatus []int | |||||
Expires int64 | |||||
} | |||||
type RestletHandler interface { | |||||
Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) | |||||
} | |||||
type RawletHandler interface { | |||||
Handle(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) | |||||
} | |||||
type RawletHandleFunc func(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) | |||||
func (f RawletHandleFunc) Handle(ctx RequestContext, params Parameters, w http.ResponseWriter, r *http.Request) { | |||||
f(ctx, params, w, r) | |||||
} | |||||
type TaskContext interface { | |||||
RestletContext | |||||
} | |||||
type TaskContextProvider interface { | |||||
NewContext() TaskContext | |||||
} | |||||
type TaskObject struct { | |||||
Queue string | |||||
Params []interface{} | |||||
} | |||||
type TaskletHandler interface { | |||||
Handle(TaskContext, ...interface{}) error | |||||
} | |||||
type RestletHandleFunc func(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) | |||||
type TaskletHandlerFunc func(TaskContext, ...interface{}) error | |||||
func (f RestletHandleFunc) Handle(ctx RequestContext, url_params Parameters, queries Parameters, post_data []byte) (*RestletResult, error) { | |||||
return f(ctx, url_params, queries, post_data) | |||||
} | |||||
func (f TaskletHandlerFunc) Handle(ctx TaskContext, params ...interface{}) error { | |||||
return f(ctx, params...) | |||||
} |