| @ -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...) | |||||
| } | |||||