Add get followers

This commit is contained in:
Marco Realacci 2022-11-18 18:58:12 +01:00
parent b89296c249
commit e8047c77a0
10 changed files with 162 additions and 17 deletions

View file

@ -11,6 +11,8 @@ func (rt *_router) Handler() http.Handler {
rt.router.PUT("/users/:user_id/username", rt.wrap(rt.UpdateUsername)) 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("/", rt.getHelloWorld)
rt.router.GET("/context", rt.wrap(rt.getContextReply)) rt.router.GET("/context", rt.wrap(rt.getContextReply))

View file

@ -33,17 +33,32 @@ func (b *BearerAuth) GetToken() string {
return b.token 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 // this is the way we manage authorization, the bearer token is the user id
state, err := db.UserExists(b.token) state, err := db.UserExists(b.token)
if err != nil { 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) { 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 { if b.token == uid {
auth, err := b.Authorized(db) auth, err := b.Authorized(db)
@ -51,11 +66,7 @@ func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcon
return -1, err return -1, err
} }
if auth { return auth, nil
return reqcontext.AUTHORIZED, nil
} else {
return reqcontext.UNAUTHORIZED, nil
}
} }
return reqcontext.FORBIDDEN, nil return reqcontext.FORBIDDEN, nil
} }

View file

@ -34,5 +34,10 @@ func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcont
w.WriteHeader(http.StatusForbidden) w.WriteHeader(http.StatusForbidden)
return false 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 return true
} }

49
service/api/followers.go Normal file
View file

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

View file

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

View file

@ -1,11 +1,11 @@
package api package api
import ( import (
"encoding/json"
"net/http" "net/http"
"github.com/julienschmidt/httprouter" "github.com/julienschmidt/httprouter"
"github.com/notherealmarco/WASAPhoto/service/api/authorization" "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/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/structures" "github.com/notherealmarco/WASAPhoto/service/structures"
) )
@ -17,14 +17,18 @@ func (rt *_router) UpdateUsername(w http.ResponseWriter, r *http.Request, ps htt
return return
} }
var req structures.UserDetails var req structures.UserDetails
err := json.NewDecoder(r.Body).Decode(&req) //todo: capire se serve close if !helpers.DecodeJsonOrBadRequest(r.Body, w, &req, rt.baseLogger) {
if err != nil {
w.WriteHeader(http.StatusBadRequest) // todo: move to DecodeOrBadRequest helper
return 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 { if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper

View file

@ -15,9 +15,10 @@ import (
type AuthStatus int type AuthStatus int
const ( const (
AUTHORIZED = 0 AUTHORIZED = 0
UNAUTHORIZED = 1 UNAUTHORIZED = 1
FORBIDDEN = 2 FORBIDDEN = 2
USER_NOT_FOUND = 3
) )
// RequestContext is the context of the request, for request-dependent parameters // RequestContext is the context of the request, for request-dependent parameters

View file

@ -34,6 +34,8 @@ import (
"database/sql" "database/sql"
"errors" "errors"
"fmt" "fmt"
"github.com/notherealmarco/WASAPhoto/service/structures"
) )
// AppDatabase is the high level interface for the DB // AppDatabase is the high level interface for the DB
@ -42,6 +44,7 @@ type AppDatabase interface {
GetUserID(name string) (string, error) GetUserID(name string) (string, error)
UpdateUsername(uid, name string) error UpdateUsername(uid, name string) error
CreateUser(name string) (string, error) CreateUser(name string) (string, error)
GetUserFollowers(uid string) ([]structures.UIDName, error)
FollowUser(uid string, follow string) error FollowUser(uid string, follow string) error
UnfollowUser(uid string, unfollow string) error UnfollowUser(uid string, unfollow string) error
BanUser(uid string, ban string) error BanUser(uid string, ban string) error

View file

@ -3,6 +3,7 @@ package database
import ( import (
"github.com/gofrs/uuid" "github.com/gofrs/uuid"
"github.com/notherealmarco/WASAPhoto/service/database/db_errors" "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 //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 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 // Follow a user
func (db *appdbimpl) FollowUser(uid string, follow string) error { func (db *appdbimpl) FollowUser(uid string, follow string) error {
_, err := db.c.Exec(`INSERT INTO "follows" ("follower", "followed") VALUES (?, ?)`, uid, follow) _, err := db.c.Exec(`INSERT INTO "follows" ("follower", "followed") VALUES (?, ?)`, uid, follow)

View file

@ -3,3 +3,8 @@ package structures
type UserDetails struct { type UserDetails struct {
Name string `json:"name"` Name string `json:"name"`
} }
type UIDName struct {
UID string `json:"user_id"`
Name string `json:"name"`
}