diff --git a/.gitignore b/.gitignore index f1fe8d1..d7a1847 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -*.so \ No newline at end of file +*.so +.build \ No newline at end of file diff --git a/cmd/test/main.go b/cmd/test/main.go index 40e6aa6..7677cb2 100644 --- a/cmd/test/main.go +++ b/cmd/test/main.go @@ -7,13 +7,14 @@ import ( func main() { - srv, err := gfw.Init("manifest.json", nil) + server, err := aicra.New("manifest.json") if err != nil { - log.Fatal(err) + log.Fatal("cannot load config", err) } - log.Printf("Server up and running\n") - err = srv.Launch(4444) + log.Printf("[Server up] 0.0.0.0:4242\n") + + err = server.Listen(4242) if err != nil { log.Fatalf("*** server failed (%s)\n", err) } diff --git a/manifest.json b/manifest.json index dc49e76..16a033a 100644 --- a/manifest.json +++ b/manifest.json @@ -1,115 +1,56 @@ { - "GET": { - "info": "returns api information", + "info": "redirects to given tiny url", "scope": [[]], - "in": {}, - "out": { - "version": { "info": "current api version", "type": "varchar(1,8)" }, - "info": { "info": "api information details", "type": "" } - } + "in": { + "URL#0": { "info": "tiny url to redirect to", "name": "url", "type": "varchar(1,30)" } + }, + "out": {} + }, + + "POST": { + "info": "creates a new tiny url", + "scope": [["admin"]], + "in": { + "URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" }, + "target": { "info": "url to shorten", "type": "varchar(5,300)" } + }, + "out": {} + }, + + "PUT": { + "info": "overrides an existing tiny url", + "scope": [["admin"]], + "in": { + "URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" }, + "target": { "info": "url to shorten", "type": "varchar(5,300)" } + }, + "out": {} + }, + + "DELETE": { + "info": "removes an existing tiny url", + "scope": [["admin"]], + "in": { + "URL#0": { "info": "preferred tiny url", "type": "varchar(1,30)", "name": "url" } + }, + "out": {} }, "/": { - - "user": { - - "GET": { - "info": "gets all | a specific user by id", + "auth": { + "POST": { + "info": "returns a 1-minute access token", "scope": [[]], "in": { - "URL#0": { "info": "optional user id | all users are returned if missing", "name": "uid", "type": "?int", "default": null } + "username": { "info": "user name", "type": "varchar(3,20)" }, + "password": { "info": "password", "type": "varchar(5,150)" } }, "out": { - "users": { "info": "matching user(s) data", "type": "" } - } - }, - - "POST": { - "info": "creates a new user", - "scope": [["admin"]], - "in": { - "firstname": { "info": "new first name", "type": "varchar(3,20)" }, - "lastname": { "info": "new last name", "type": "varchar(3,20)" }, - "mail": { "info": "new mail", "type": "string" } - }, - "out": { - "user": { "info": "user data to acknowledge creation", "type": "" } - } - }, - - "PUT": { - "info": "updates data of an existing user; only the data given will be updated, all fields are optional", - "scope": [["admin"], ["user"]], - "in": { - "URL#0": { "info": "id of the user to update", "type": "int", "name": "user_id" }, - "firstname": { "info": "new first name", "type": "?varchar(3,20)", "default": null }, - "lastname": { "info": "new last name", "type": "?varchar(3,20)", "default": null }, - "mail": { "info": "new mail", "type": "?string", "default": null } - }, - "out": { - "user": { "info": "new updated data to acknowledge updates", "type": "" } - } - }, - - "DELETE": { - "info": "deletes an existing user", - "scope": [["admin"], ["user"]], - "in": { - "URL#0": { "info": "id of the user to delete", "type": "int" } - }, - "out": {} - }, - - "/": { - "post": { - "GET": { - "info": "gets all | a specific post for an existing user", - "scope": [["user"], []], - "in": { - "URL#0": { "info": "optional post id", "type": "int", "name": "?post_id", "default": null }, - "URL#1": { "info": "user id (if missing, current connected user)", "type": "int", "name": "?user_id", "default": null } - }, - "out": { - "posts": { "info": "matching post(s) data", "type": "" } - } - }, - - "POST": { - "info": "creates a new post for the current connected user", - "scope": [["user"]], - "in": { - "title": { "info": "new post title", "type": "varchar(3,20)" }, - "content": { "info": "new post content", "type": "string" } - }, - "out": { - "post": { "info": "post data to acknowledge creation", "type": "" } - } - }, - - "PUT": { - "info": "updates data of an existing post of the current connected user; only the data given will be updated, all fields are optional", - "scope": [["user"]], - "in": { - "title": { "info": "new post title", "type": "?varchar(3,20)", "default": null }, - "content": { "info": "new post content", "type": "?string", "default": null } - }, - "out": { - "post": { "info": "new updated data to acknowledge updates", "type": "" } - } - }, - - "DELETE": { - "info": "deletes an existing post of the current connected user", - "scope": [["user"]], - "in": { - "URL#0": { "info": "id of the post to delete", "type": "int", "name": "post_id" } - }, - "out": {} - } + "token": { "info": "access token", "type": "varchar(256,256)" } } } } - } + } \ No newline at end of file diff --git a/middleware/1-auth/main.go b/middleware/1-auth/main.go new file mode 100644 index 0000000..6bfcc9d --- /dev/null +++ b/middleware/1-auth/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "git.xdrm.io/go/aicra/middleware" + "net/http" +) + +// Authentication middleware +func Inspect(req http.Request, scope *middleware.Scope) { + *scope = append(*scope, "admin") +} diff --git a/root/authi.go b/root/authi.go new file mode 100644 index 0000000..a91037b --- /dev/null +++ b/root/authi.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + e "git.xdrm.io/go/aicra/err" + i "git.xdrm.io/go/aicra/implement" +) + +// Builds an access token from credentials +func Post(d i.Arguments, r *i.Response) i.Response { + + if d.Has("_AUTHORIZATION_") { + fmt.Printf("authorization: '%s'\n", d["_AUTHORIZATION_"].(string)) + } + + r.Err = e.Success + return *r +} diff --git a/root/db/db.go b/root/db/db.go new file mode 100644 index 0000000..d8fa576 --- /dev/null +++ b/root/db/db.go @@ -0,0 +1,75 @@ +package db + +import ( + "fmt" + "github.com/go-redis/redis" +) + +const NONCE = "go-tiny-url" + +var domain = "data" + +type db redis.Client + +// Returns a connected client to dataset +func Connect() *db { + cli := redis.NewClient(&redis.Options{ + Addr: "127.0.0.1:6379", + Password: "", + DB: 0, + }) + + if _, err := cli.Ping().Result(); err != nil { + return nil + } + + return (*db)(cli) +} + +// returns value from key (nil if nothing) +func (c *db) Get(key string) []byte { + + // 1. Try to get + if val, err := (*redis.Client)(c).Get(fmt.Sprintf("%s:%s:%s", NONCE, domain, key)).Result(); err != nil { + // 2. nil if nothing found + return nil + } else { + // 3. return if found + return []byte(val) + } + +} + +// stores a value for a key (success state in return) +func (c *db) Set(key string, value string) bool { + + // 1. Try to set + if (*redis.Client)(c).Set(fmt.Sprintf("%s:%s:%s", NONCE, domain, key), value, 0).Err() != nil { + // 2. failure + return false + } + + // 3. success + return true + +} + +// deletes the value for a key (success state in return) +func (c *db) Del(key string) bool { + + // 1. Try to set + if (*redis.Client)(c).Del(fmt.Sprintf("%s:%s:%s", NONCE, domain, key)).Err() != nil { + // 2. failure + return false + } + + // 3. success + return true + +} + +// Switch following operations to DATA dataset +func (c *db) SwitchData() { domain = "data" } + +// Switch following operations to AUTH dataset +func (c *db) SwitchAuth() { domain = "auth" } diff --git a/root/i.go b/root/i.go index c2976a0..82bbe53 100644 --- a/root/i.go +++ b/root/i.go @@ -1,15 +1,142 @@ package main import ( - "fmt" + "git.xdrm.io/example/aicra/root/db" e "git.xdrm.io/go/aicra/err" i "git.xdrm.io/go/aicra/implement" ) +// Redirects to an url from a key func Get(d i.Arguments, r *i.Response) i.Response { - fmt.Printf("GET /\n") - r.Set("api_version", 1.2) - r.Set("received_data", d) + + /* (1) Init redis connection */ + cli := db.Connect() + if cli == nil { + r.Err = e.Failure + return *r + } + + /* (2) Extract api input */ + key, ok := d["url"].(string) + + if !ok { + r.Err = e.InvalidParam + r.Err.BindArgument("url") + return *r + } + + /* (3) Check if match for this key */ + val := cli.Get(key) + if val == nil { + r.Err = e.NoMatchFound + return *r + } + + /* (4) Redirect to value */ + r.Set("_REDIRECT_", string(val)) + r.Err = e.Success + return *r +} + +// Stores a new tinyurl/fullurl combination +func Post(d i.Arguments, r *i.Response) i.Response { + /* (1) Init redis connection */ + cli := db.Connect() + if cli == nil { + r.Err = e.Failure + return *r + } + + /* (2) Extract api input */ + target, ok1 := d["target"].(string) + url, ok2 := d["url"].(string) + + if !ok1 || !ok2 { + r.Err = e.InvalidParam + return *r + } + + /* (3) Check if key already used */ + if cli.Get(url) != nil { + r.Err = e.AlreadyExists + return *r + } + + /* (4) Store */ + if !cli.Set(url, target) { + r.Err = e.Failure + return *r + } + + r.Err = e.Success + return *r +} + +// Overrides a existing tinyurl with new target +func Put(d i.Arguments, r *i.Response) i.Response { + + /* (1) Init redis connection */ + cli := db.Connect() + if cli == nil { + r.Err = e.Failure + return *r + } + + /* (2) Extract api input */ + target, ok1 := d["target"].(string) + url, ok2 := d["url"].(string) + + if !ok1 || !ok2 { + r.Err = e.InvalidParam + return *r + } + + /* (3) Check if key already used */ + if cli.Get(url) == nil { + r.Err = e.NoMatchFound + return *r + } + + /* (4) Update */ + if !cli.Set(url, target) { + r.Err = e.Failure + return *r + } + + r.Err = e.Success + return *r +} + +// Deletes an existing tinyurl +func Delete(d i.Arguments, r *i.Response) i.Response { + + /* (1) Init redis connection */ + cli := db.Connect() + if cli == nil { + r.Err = e.Failure + return *r + } + + /* (2) Extract api input */ + url, ok := d["url"].(string) + + if !ok { + r.Err = e.InvalidParam + return *r + } + + /* (3) Check if key already used */ + if cli.Get(url) == nil { + r.Err = e.NoMatchFound + return *r + } + + /* (4) Delete */ + if !cli.Del(url) { + r.Err = e.Failure + return *r + } + r.Err = e.Success return *r }