| @ -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 | |||
| import ( | |||
| "net/http" | |||
| "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 | |||
| import ( | |||
| "github.com/archsh/go.xql" | |||
| "crypto/sha1" | |||
| "encoding/json" | |||
| "errors" | |||
| "fmt" | |||
| "io/ioutil" | |||
| "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 | |||
| 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...) | |||
| } | |||