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.GET("/users/:user_id/followers", rt.wrap(rt.GetFollowers))
rt.router.GET("/", rt.getHelloWorld)
rt.router.GET("/context", rt.wrap(rt.getContextReply))

View file

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

View file

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

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

View file

@ -18,6 +18,7 @@ const (
AUTHORIZED = 0
UNAUTHORIZED = 1
FORBIDDEN = 2
USER_NOT_FOUND = 3
)
// RequestContext is the context of the request, for request-dependent parameters

View file

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

View file

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

View file

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