Add database query status (and improved response), photos, likes, comments, bans

This commit is contained in:
Marco Realacci 2022-11-20 19:21:26 +01:00
parent 519ae22197
commit abbd5bc494
22 changed files with 1118 additions and 72 deletions

View file

@ -27,8 +27,9 @@ func (rt *_router) wrap(fn httpRouterHandler) func(http.ResponseWriter, *http.Re
auth, err := authorization.BuildAuth(r.Header.Get("Authorization"))
if err != nil {
auth = authorization.BuildAnonymous()
rt.baseLogger.WithError(err).Info("User not authorized")
return
// is not an error, just a not logged in user!
}
var ctx = reqcontext.RequestContext{

View file

@ -11,7 +11,20 @@ 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("/users/:user_id/followers", rt.wrap(rt.GetFollowersFollowing))
rt.router.PUT("/users/:user_id/followers/:follower_uid", rt.wrap(rt.PutFollow))
rt.router.DELETE("/users/:user_id/followers/:follower_uid", rt.wrap(rt.DeleteFollow))
rt.router.GET("/users/:user_id/following", rt.wrap(rt.GetFollowersFollowing))
rt.router.PUT("/users/:user_id/bans/:ban_uid", rt.wrap(rt.PutBan))
rt.router.DELETE("/users/:user_id/bans/:ban_uid", rt.wrap(rt.DeleteBan))
rt.router.POST("/users/:user_id/photos", rt.wrap(rt.PostPhoto))
rt.router.GET("/users/:user_id/photos/:photo_id", rt.wrap(rt.GetPhoto))
rt.router.DELETE("/users/:user_id/photos/:photo_id", rt.wrap(rt.DeletePhoto))
rt.router.PUT("/users/:user_id/photos/:photo_id/likes/:liker_uid", rt.wrap(rt.PutDeleteLike))
rt.router.DELETE("/users/:user_id/photos/:photo_id/likes/:liker_uid", rt.wrap(rt.PutDeleteLike))
rt.router.GET("/", rt.getHelloWorld)
rt.router.GET("/context", rt.wrap(rt.getContextReply))

View file

@ -38,10 +38,11 @@ package api
import (
"errors"
"github.com/notherealmarco/WASAPhoto/service/database"
"github.com/julienschmidt/httprouter"
"github.com/sirupsen/logrus"
"net/http"
"github.com/julienschmidt/httprouter"
"github.com/notherealmarco/WASAPhoto/service/database"
"github.com/sirupsen/logrus"
)
// Config is used to provide dependencies and configuration to the New function.
@ -51,6 +52,8 @@ type Config struct {
// Database is the instance of database.AppDatabase where data are saved
Database database.AppDatabase
DataPath string
}
// Router is the package API interface representing an API handler builder
@ -82,6 +85,7 @@ func New(cfg Config) (Router, error) {
router: router,
baseLogger: cfg.Logger,
db: cfg.Database,
dataPath: cfg.DataPath,
}, nil
}
@ -93,4 +97,6 @@ type _router struct {
baseLogger logrus.FieldLogger
db database.AppDatabase
dataPath string
}

View file

@ -0,0 +1,31 @@
// This identity provider represents non logged-in users.
package authorization
import (
"github.com/notherealmarco/WASAPhoto/service/api/reqcontext"
"github.com/notherealmarco/WASAPhoto/service/database"
)
type AnonymousAuth struct {
}
func BuildAnonymous() *AnonymousAuth {
return &AnonymousAuth{}
}
func (u *AnonymousAuth) GetType() string {
return "Anonymous"
}
func (u *AnonymousAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, error) {
return reqcontext.UNAUTHORIZED, nil
}
func (u *AnonymousAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error) {
return reqcontext.UNAUTHORIZED, nil
}
func (u *AnonymousAuth) GetUserID() string {
return ""
}

View file

@ -29,7 +29,7 @@ func BuildBearer(header string) (*BearerAuth, error) {
return &BearerAuth{token: header[7:]}, nil
}
func (b *BearerAuth) GetToken() string {
func (b *BearerAuth) GetUserID() string {
return b.token
}

View file

@ -19,7 +19,7 @@ func BuildAuth(header string) (reqcontext.Authorization, error) {
return auth, nil
}
func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error), uid string, db database.AppDatabase, w http.ResponseWriter) bool {
func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error), uid string, db database.AppDatabase, w http.ResponseWriter, notFoundStatus int) bool {
auth, err := f(db, uid)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
@ -36,7 +36,7 @@ func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcont
}
// requested user is not found -> 404 as the resource is not found
if auth == reqcontext.USER_NOT_FOUND {
w.WriteHeader(http.StatusNotFound)
w.WriteHeader(notFoundStatus)
return false
}
return true

70
service/api/bans.go Normal file
View file

@ -0,0 +1,70 @@
package api
import (
"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/database"
)
func (rt *_router) PutBan(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
banned := ps.ByName("ban_uid")
// send error if the user has no permission to perform this action
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
if uid == banned {
helpers.SendBadRequest(w, "You cannot ban yourself", rt.baseLogger)
return
}
status, err := rt.db.BanUser(uid, banned)
if err != nil {
helpers.SendInternalError(err, "Database error: BanUser", w, rt.baseLogger)
return
}
if status == database.ERR_NOT_FOUND {
helpers.SendBadRequest(w, "You are trying to ban a non-existent user", rt.baseLogger)
return
}
if status == database.ERR_EXISTS {
w.WriteHeader(http.StatusNoContent)
return
}
helpers.SendStatus(http.StatusCreated, w, "Success", rt.baseLogger)
}
func (rt *_router) DeleteBan(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
banned := ps.ByName("ban_uid")
// send error if the user has no permission to perform this action
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
status, err := rt.db.UnbanUser(uid, banned)
if err != nil {
helpers.SendInternalError(err, "Database error: UnbanUser", w, rt.baseLogger)
return
}
if status == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User not banned", rt.baseLogger)
return
}
w.WriteHeader(http.StatusNoContent)
}

167
service/api/comments.go Normal file
View file

@ -0,0 +1,167 @@
package api
import (
"encoding/json"
"net/http"
"strconv"
"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/database"
)
type reqbody struct {
UID string `json:"user_id"`
Comment string `json:"comment"`
}
func (rt *_router) GetComments(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
// get the user id from the url
uid := ps.ByName("user_id")
photo_id, err := strconv.ParseInt(ps.ByName("photo_id"), 10, 64)
if err != nil {
helpers.SendBadRequest(w, "Invalid photo id", rt.baseLogger)
return
}
// send 404 if the user does not exist
if !helpers.VerifyUserOrNotFound(rt.db, uid, w, rt.baseLogger) {
return
}
// get the user's comments
success, comments, err := rt.db.GetComments(uid, photo_id)
if err != nil {
helpers.SendInternalError(err, "Database error: GetComments", w, rt.baseLogger)
return
}
if success == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User or photo not found", rt.baseLogger)
return
}
// send the response
err = json.NewEncoder(w).Encode(comments)
if err != nil {
helpers.SendInternalError(err, "Error encoding comments", w, rt.baseLogger)
return
}
}
func (rt *_router) PostComment(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
photo_id, err := strconv.ParseInt(ps.ByName("photo_id"), 10, 64)
if err != nil {
helpers.SendBadRequestError(err, "Bad photo_id", w, rt.baseLogger)
return
}
// get the comment from the request
var request_body reqbody
if !helpers.DecodeJsonOrBadRequest(r.Body, w, &request_body, rt.baseLogger) {
return
}
// check if the user is authorized to post a comment
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, request_body.UID, rt.db, w, http.StatusBadRequest) {
return
}
// add the comment to the database
success, err := rt.db.PostComment(uid, photo_id, request_body.UID, request_body.Comment)
if err != nil {
helpers.SendInternalError(err, "Database error: PostComment", w, rt.baseLogger)
return
}
// if user or photo does not exist, send 404
if success == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User or photo not found", rt.baseLogger)
return
}
// send the response
helpers.SendStatus(http.StatusCreated, w, "Comment created with success", rt.baseLogger)
}
func (rt *_router) DeleteComment(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
photo_id, err := strconv.ParseInt(ps.ByName("photo_id"), 10, 64)
if err != nil {
helpers.SendBadRequestError(err, "Bad photo_id", w, rt.baseLogger)
return
}
comment_id, err := strconv.ParseInt(ps.ByName("comment_id"), 10, 64)
if err != nil {
helpers.SendBadRequestError(err, "Bad comment_id", w, rt.baseLogger)
return
}
// Check if the user is authorized to delete that comment
// (only the user who posted the comment or the owner of the photo can delete it)
status, comment_owner, err := rt.db.GetCommentOwner(uid, photo_id, comment_id)
if err != nil {
helpers.SendInternalError(err, "Database error: GetCommentOwner", w, rt.baseLogger)
return
}
if status == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "Resource not found", rt.baseLogger)
return
}
// The authorized user must be either comment_owner or uid
owner_auth, err := ctx.Auth.UserAuthorized(rt.db, comment_owner)
if err != nil {
helpers.SendInternalError(err, "Error checking authorization", w, rt.baseLogger)
return
}
// If the status is UNAUTHORIZED, this means that the Authorization header is missing or invalid
// We don't need to check if user is 'uid' and we can send the error
if owner_auth == reqcontext.UNAUTHORIZED {
helpers.SendStatus(http.StatusUnauthorized, w, "Unauthorized", rt.baseLogger)
}
if owner_auth != reqcontext.AUTHORIZED {
// Authorized user is not the owner of the comment
// let's check if it's the owner of the photo
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusForbidden) {
// The authorized user is not the owner of the photo, so we sent an error
return
}
// If it is authorized, the user can delete the comment.
// (else the status must be FORBIDDEN. It can't be UNAUTHORIZED because we already checked it
// and it can't be NOT_FOUND because we used it before to get the comment owner)
}
// Delete the comment
_, err = rt.db.DeleteComment(uid, photo_id, comment_id)
if err != nil {
helpers.SendInternalError(err, "Database error: DeleteComment", w, rt.baseLogger)
return
}
// We don't need to check the status because if the comment didn't exist
// we'd have already returned an error when getting the comment owner
// so we know the comment existed and was deleted, and we can safely send 204
w.WriteHeader(http.StatusNoContent)
}

View file

@ -3,32 +3,56 @@ package api
import (
"encoding/json"
"net/http"
"strings"
"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/database"
"github.com/notherealmarco/WASAPhoto/service/structures"
)
func (rt *_router) GetFollowers(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
func (rt *_router) GetFollowersFollowing(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
if !helpers.VerifyUserOrNotFound(rt.db, uid, w) {
if !helpers.VerifyUserOrNotFound(rt.db, uid, w, rt.baseLogger) {
return
}
followers, err := rt.db.GetUserFollowers(uid)
var users *[]structures.UIDName
var err error
var status database.QueryResult
// Check if client is asking for followers or following
if strings.HasSuffix(r.URL.Path, "/followers") {
// Get the followers from the database
status, users, err = rt.db.GetUserFollowers(uid)
} else {
// Get the following users from the database
status, users, err = rt.db.GetUserFollowing(uid)
}
// Send a 500 response if there was an error
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok, maybe let's use a helper
helpers.SendInternalError(err, "Database error: GetUserFollowers", w, rt.baseLogger)
return
}
// Send a 404 response if the user was not found
if status == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User not found", rt.baseLogger)
return
}
// Send the users to the client
w.Header().Set("content-type", "application/json")
err = json.NewEncoder(w).Encode(&followers)
err = json.NewEncoder(w).Encode(users)
// Send a 500 response if there was an error
if err != nil {
w.WriteHeader(http.StatusInternalServerError) // todo: is not ok
helpers.SendInternalError(err, "Error encoding json", w, rt.baseLogger)
return
}
}
@ -38,12 +62,52 @@ func (rt *_router) PutFollow(w http.ResponseWriter, r *http.Request, ps httprout
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
// send error if the user has no permission to perform this action
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
w.WriteHeader(http.StatusNoContent) // todo: change to 204 also in API spec
status, err := rt.db.FollowUser(uid, followed)
if err != nil {
helpers.SendInternalError(err, "Database error: FollowUser", w, rt.baseLogger)
return
}
if status == database.ERR_EXISTS {
w.WriteHeader(http.StatusNoContent)
return
}
if status == database.ERR_NOT_FOUND {
helpers.SendBadRequest(w, "You are trying to follow a non-existent user", rt.baseLogger)
return
}
helpers.SendStatus(http.StatusCreated, w, "Success", rt.baseLogger)
}
func (rt *_router) DeleteFollow(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
followed := ps.ByName("follower_uid")
// send error if the user has no permission to perform this action
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
status, err := rt.db.UnfollowUser(uid, followed)
if err != nil {
helpers.SendInternalError(err, "Database error: UnfollowUser", w, rt.baseLogger)
return
}
if status == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User not found", rt.baseLogger)
return
}
w.WriteHeader(http.StatusNoContent)
}

View file

@ -6,6 +6,7 @@ import (
"net/http"
"github.com/notherealmarco/WASAPhoto/service/database"
"github.com/notherealmarco/WASAPhoto/service/structures"
"github.com/sirupsen/logrus"
)
@ -19,24 +20,72 @@ func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l
return true
}
func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter) bool {
func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter, l logrus.FieldLogger) bool {
user_exists, err := db.UserExists(uid)
if err != nil {
SendInternalError(err, "Error verifying user existence", w, nil)
SendInternalError(err, "Error verifying user existence", w, l)
return false
}
if !user_exists {
w.WriteHeader(http.StatusNotFound)
SendNotFound(w, "User not found", l)
return false
}
return true
}
func SendStatus(httpStatus int, w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(httpStatus)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
//todo: empty response?
}
}
func SendNotFound(w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(http.StatusNotFound)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
//todo: empty response?
}
}
func SendBadRequest(w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(http.StatusBadRequest)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
//todo: empty response?
}
}
func SendBadRequestError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) {
w.WriteHeader(http.StatusBadRequest)
l.WithError(err).Error(description)
err = json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
//todo: empty response?
}
}
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
err = json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
//todo: empty response?
}
}
func RollbackOrLogError(tx database.DBTransaction, l logrus.FieldLogger) {
err := tx.Rollback()
if err != nil {
l.WithError(err).Error("Error rolling back transaction")
}
}

96
service/api/likes.go Normal file
View file

@ -0,0 +1,96 @@
package api
import (
"encoding/json"
"net/http"
"strconv"
"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/database"
)
func (rt *_router) GetLikes(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
// get the user id from the url
uid := ps.ByName("user_id")
photo_id, err := strconv.ParseInt(ps.ByName("photo_id"), 10, 64)
if err != nil {
helpers.SendBadRequest(w, "Invalid photo id", rt.baseLogger)
return
}
// send 404 if the user does not exist
if !helpers.VerifyUserOrNotFound(rt.db, uid, w, rt.baseLogger) {
return
}
// get the user's likes
success, likes, err := rt.db.GetPhotoLikes(uid, photo_id)
if err != nil {
helpers.SendInternalError(err, "Database error: GetLikes", w, rt.baseLogger)
return
}
if success == database.ERR_NOT_FOUND {
helpers.SendNotFound(w, "User or photo not found", rt.baseLogger)
return
}
// send the response
err = json.NewEncoder(w).Encode(likes)
if err != nil {
helpers.SendInternalError(err, "Error encoding response", w, rt.baseLogger)
return
}
}
func (rt *_router) PutDeleteLike(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
photo_id, err := strconv.ParseInt(ps.ByName("photo_id"), 10, 64)
if err != nil {
helpers.SendBadRequestError(err, "Bad photo_id", w, rt.baseLogger)
return
}
liker_uid := ps.ByName("liker_uid")
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, liker_uid, rt.db, w, http.StatusBadRequest) {
return
}
var success database.QueryResult
// If the request is a PUT, then we like the photo
if r.Method == "PUT" {
success, err = rt.db.LikePhoto(uid, photo_id, liker_uid)
} else { // Request is a DELETE, so we unlike the photo
success, err = rt.db.UnlikePhoto(uid, photo_id, liker_uid)
}
if err != nil {
helpers.SendInternalError(err, "Database error", w, rt.baseLogger)
return
}
if success == database.ERR_NOT_FOUND {
helpers.SendBadRequest(w, "User or photo not found", rt.baseLogger)
return
}
// User already liked the photo
if success == database.ERR_EXISTS {
w.WriteHeader(http.StatusNoContent)
return
}
// User liked the photo successfully
helpers.SendStatus(http.StatusCreated, w, "Success", rt.baseLogger)
}

131
service/api/photos.go Normal file
View file

@ -0,0 +1,131 @@
package api
import (
"io"
"net/http"
"os"
"path/filepath"
"strconv"
"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"
)
func (rt *_router) PostPhoto(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
defer r.Body.Close()
uid := ps.ByName("user_id")
// send error if the user has no permission to perform this action
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
transaction, photo_id, err := rt.db.PostPhoto(uid)
if err != nil {
helpers.SendInternalError(err, "Database error: PostPhoto", w, rt.baseLogger)
return
}
path := rt.dataPath + "/photos/" + uid + "/" + strconv.FormatInt(photo_id, 10) + ".jpg"
// todo: we should check if the body is a valid jpg image
if err = os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { // perms = 511
helpers.SendInternalError(err, "Error creating directory", w, rt.baseLogger)
return
}
file, err := os.Create(path)
if err != nil {
helpers.SendInternalError(err, "Error creating file", w, rt.baseLogger)
helpers.RollbackOrLogError(transaction, rt.baseLogger)
return
}
if _, err = io.Copy(file, r.Body); err != nil {
helpers.SendInternalError(err, "Error writing the file", w, rt.baseLogger)
helpers.RollbackOrLogError(transaction, rt.baseLogger)
return
}
if err = file.Close(); err != nil {
helpers.SendInternalError(err, "Error closing file", w, rt.baseLogger)
helpers.RollbackOrLogError(transaction, rt.baseLogger)
}
err = transaction.Commit()
if err != nil {
helpers.SendInternalError(err, "Error committing transaction", w, rt.baseLogger)
//todo: should I roll back?
return
}
helpers.SendStatus(http.StatusCreated, w, "Photo uploaded", rt.baseLogger)
}
func (rt *_router) GetPhoto(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
photo_id := ps.ByName("photo_id")
if !helpers.VerifyUserOrNotFound(rt.db, uid, w, rt.baseLogger) {
return
}
path := rt.dataPath + "/photos/" + uid + "/" + photo_id + ".jpg"
file, err := os.Open(path)
if err != nil {
helpers.SendNotFound(w, "Photo not found", rt.baseLogger)
return
}
defer file.Close()
io.Copy(w, file)
}
func (rt *_router) DeletePhoto(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
photo_id := ps.ByName("photo_id")
// photo id to int64
photo_id_int, err := strconv.ParseInt(photo_id, 10, 64)
if err != nil {
helpers.SendBadRequestError(err, "Bad photo id", w, rt.baseLogger)
return
}
// send error if the user has no permission to perform this action (only the author can delete a photo)
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
deleted, err := rt.db.DeletePhoto(uid, photo_id_int)
if err != nil {
helpers.SendInternalError(err, "Error deleting photo from database", w, rt.baseLogger)
return
} //todo: maybe let's use a transaction also here
if !deleted {
helpers.SendNotFound(w, "Photo not found", rt.baseLogger)
return
}
path := rt.dataPath + "/photos/" + uid + "/" + photo_id + ".jpg"
if err := os.Remove(path); err != nil {
helpers.SendInternalError(err, "Error deleting file", w, rt.baseLogger)
return
}
w.WriteHeader(http.StatusNoContent)
}

View file

@ -13,7 +13,7 @@ import (
func (rt *_router) UpdateUsername(w http.ResponseWriter, r *http.Request, ps httprouter.Params, ctx reqcontext.RequestContext) {
uid := ps.ByName("user_id")
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w) {
if !authorization.SendAuthorizationError(ctx.Auth.UserAuthorized, uid, rt.db, w, http.StatusNotFound) {
return
}
var req structures.UserDetails

View file

@ -19,7 +19,7 @@ const (
UNAUTHORIZED = 1
FORBIDDEN = 2
USER_NOT_FOUND = 3
)
) // todo: here?
// RequestContext is the context of the request, for request-dependent parameters
type RequestContext struct {
@ -34,6 +34,7 @@ type RequestContext struct {
type Authorization interface {
GetType() string
Authorized(db database.AppDatabase) (bool, error)
GetUserID() string
Authorized(db database.AppDatabase) (AuthStatus, error)
UserAuthorized(db database.AppDatabase, uid string) (AuthStatus, error)
}