diff --git a/service/api/api-handler.go b/service/api/api-handler.go index 06c5972..7c181f8 100644 --- a/service/api/api-handler.go +++ b/service/api/api-handler.go @@ -11,6 +11,8 @@ func (rt *_router) Handler() http.Handler { rt.router.PUT("/users/:user_id/username", rt.wrap(rt.UpdateUsername)) + rt.router.GET("/users/:user_id/followers", rt.wrap(rt.GetFollowers)) + rt.router.GET("/", rt.getHelloWorld) rt.router.GET("/context", rt.wrap(rt.getContextReply)) diff --git a/service/api/authorization/auth-bearer.go b/service/api/authorization/auth-bearer.go index 037f131..6e0d03f 100644 --- a/service/api/authorization/auth-bearer.go +++ b/service/api/authorization/auth-bearer.go @@ -33,17 +33,32 @@ func (b *BearerAuth) GetToken() string { return b.token } -func (b *BearerAuth) Authorized(db database.AppDatabase) (bool, error) { +func (b *BearerAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, error) { // this is the way we manage authorization, the bearer token is the user id state, err := db.UserExists(b.token) if err != nil { - return false, err + return reqcontext.UNAUTHORIZED, err } - return state, nil + + if state { + return reqcontext.AUTHORIZED, nil + } + return reqcontext.UNAUTHORIZED, nil } func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error) { + + // If uid is not a valid user, return USER_NOT_FOUND + user_exists, err := db.UserExists(uid) + + if err != nil { + return reqcontext.UNAUTHORIZED, err + } + if !user_exists { + return reqcontext.USER_NOT_FOUND, nil + } + if b.token == uid { auth, err := b.Authorized(db) @@ -51,11 +66,7 @@ func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcon return -1, err } - if auth { - return reqcontext.AUTHORIZED, nil - } else { - return reqcontext.UNAUTHORIZED, nil - } + return auth, nil } return reqcontext.FORBIDDEN, nil } diff --git a/service/api/authorization/auth-manager.go b/service/api/authorization/auth-manager.go index f6c4f2d..97cc803 100644 --- a/service/api/authorization/auth-manager.go +++ b/service/api/authorization/auth-manager.go @@ -34,5 +34,10 @@ func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcont w.WriteHeader(http.StatusForbidden) return false } + // requested user is not found -> 404 as the resource is not found + if auth == reqcontext.USER_NOT_FOUND { + w.WriteHeader(http.StatusNotFound) + return false + } return true } diff --git a/service/api/followers.go b/service/api/followers.go new file mode 100644 index 0000000..7a9e1a3 --- /dev/null +++ b/service/api/followers.go @@ -0,0 +1,49 @@ +package api + +import ( + "encoding/json" + "net/http" + + "github.com/julienschmidt/httprouter" + "github.com/notherealmarco/WASAPhoto/service/api/helpers" + "github.com/notherealmarco/WASAPhoto/service/api/reqcontext" +) + +func (rt *_router) GetFollowers(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) { + + uid := ps.ByName("user_id") + + if !helpers.VerifyUserOrNotFound(rt.db, uid, w) { + return + } + + followers, err := rt.db.GetUserFollowers(uid) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper + return + } + + w.Header().Set("content-type", "application/json") + err = json.NewEncoder(w).Encode(&followers) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) // todo: is not ok + return + } +} + +func (rt *_router) PutFollow(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) { + + uid := ps.ByName("user_id") + followed := ps.ByName("follower_uid") + + err := rt.db.FollowUser(uid, followed) + + if err != nil { + w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper + return + } + + w.WriteHeader(http.StatusNoContent) // todo: change to 204 also in API spec +} diff --git a/service/api/helpers/api-helpers.go b/service/api/helpers/api-helpers.go new file mode 100644 index 0000000..5302cc6 --- /dev/null +++ b/service/api/helpers/api-helpers.go @@ -0,0 +1,42 @@ +package helpers + +import ( + "encoding/json" + "io" + "net/http" + + "github.com/notherealmarco/WASAPhoto/service/database" + "github.com/sirupsen/logrus" +) + +func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l logrus.FieldLogger) bool { + + err := json.NewDecoder(r).Decode(v) + if err != nil { + SendInternalError(err, "Error decoding json", w, l) + return false + } + return true +} + +func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter) bool { + + user_exists, err := db.UserExists(uid) + + if err != nil { + SendInternalError(err, "Error verifying user existence", w, nil) + return false + } + + if !user_exists { + w.WriteHeader(http.StatusNotFound) + return false + } + return true +} + +func SendInternalError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) { + w.WriteHeader(http.StatusInternalServerError) + l.WithError(err).Error(description) + w.Write([]byte(description)) // todo: maybe in json. But it's not important to send the full error to the client +} diff --git a/service/api/put-updateusername.go b/service/api/put-updateusername.go index dbc002a..860f5f2 100644 --- a/service/api/put-updateusername.go +++ b/service/api/put-updateusername.go @@ -1,11 +1,11 @@ package api import ( - "encoding/json" "net/http" "github.com/julienschmidt/httprouter" "github.com/notherealmarco/WASAPhoto/service/api/authorization" + "github.com/notherealmarco/WASAPhoto/service/api/helpers" "github.com/notherealmarco/WASAPhoto/service/api/reqcontext" "github.com/notherealmarco/WASAPhoto/service/structures" ) @@ -17,14 +17,18 @@ func (rt *_router) UpdateUsername(w http.ResponseWriter, r *http.Request, ps htt return } var req structures.UserDetails - err := json.NewDecoder(r.Body).Decode(&req) //todo: capire se serve close - - if err != nil { - w.WriteHeader(http.StatusBadRequest) // todo: move to DecodeOrBadRequest helper + if !helpers.DecodeJsonOrBadRequest(r.Body, w, &req, rt.baseLogger) { return } - err = rt.db.UpdateUsername(uid, req.Name) + //err := json.NewDecoder(r.Body).Decode(&req) //todo: capire se serve close + + //if err != nil { + // w.WriteHeader(http.StatusBadRequest) // todo: move to DecodeOrBadRequest helper + // return + //} + + err := rt.db.UpdateUsername(uid, req.Name) if err != nil { w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper diff --git a/service/api/reqcontext/request-context.go b/service/api/reqcontext/request-context.go index b0be6c8..0513367 100644 --- a/service/api/reqcontext/request-context.go +++ b/service/api/reqcontext/request-context.go @@ -15,9 +15,10 @@ import ( type AuthStatus int const ( - AUTHORIZED = 0 - UNAUTHORIZED = 1 - FORBIDDEN = 2 + AUTHORIZED = 0 + UNAUTHORIZED = 1 + FORBIDDEN = 2 + USER_NOT_FOUND = 3 ) // RequestContext is the context of the request, for request-dependent parameters diff --git a/service/database/database.go b/service/database/database.go index c8f53e7..75850ad 100644 --- a/service/database/database.go +++ b/service/database/database.go @@ -34,6 +34,8 @@ import ( "database/sql" "errors" "fmt" + + "github.com/notherealmarco/WASAPhoto/service/structures" ) // AppDatabase is the high level interface for the DB @@ -42,6 +44,7 @@ type AppDatabase interface { GetUserID(name string) (string, error) UpdateUsername(uid, name string) error CreateUser(name string) (string, error) + GetUserFollowers(uid string) ([]structures.UIDName, error) FollowUser(uid string, follow string) error UnfollowUser(uid string, unfollow string) error BanUser(uid string, ban string) error diff --git a/service/database/db-users.go b/service/database/db-users.go index 472619b..82120e3 100644 --- a/service/database/db-users.go +++ b/service/database/db-users.go @@ -3,6 +3,7 @@ package database import ( "github.com/gofrs/uuid" "github.com/notherealmarco/WASAPhoto/service/database/db_errors" + "github.com/notherealmarco/WASAPhoto/service/structures" ) //Check if user exists and if exists return the user id by username @@ -44,6 +45,28 @@ func (db *appdbimpl) UpdateUsername(uid string, name string) error { return err } +// Get user followers +func (db *appdbimpl) GetUserFollowers(uid string) ([]structures.UIDName, error) { + rows, err := db.c.Query(`SELECT "follower", "user.name" FROM "follows", "users" + WHERE "follows.follower" = "users.uid" + AND "followed" = ?`, uid) + if err != nil { + return nil, err + } + + var followers []structures.UIDName = make([]structures.UIDName, 0) + for rows.Next() { + var uid string + var name string + err = rows.Scan(&uid, &name) + if err != nil { + return nil, err + } + followers = append(followers, structures.UIDName{UID: uid, Name: name}) + } + return followers, nil +} + // Follow a user func (db *appdbimpl) FollowUser(uid string, follow string) error { _, err := db.c.Exec(`INSERT INTO "follows" ("follower", "followed") VALUES (?, ?)`, uid, follow) diff --git a/service/structures/api-structures.go b/service/structures/api-structures.go index c5020df..7b0f2b8 100644 --- a/service/structures/api-structures.go +++ b/service/structures/api-structures.go @@ -3,3 +3,8 @@ package structures type UserDetails struct { Name string `json:"name"` } + +type UIDName struct { + UID string `json:"user_id"` + Name string `json:"name"` +}