Improve comments and code readability

This commit is contained in:
Marco Realacci 2023-01-10 01:21:53 +01:00
parent f6ad6db2f7
commit 3de158e5a5
19 changed files with 84 additions and 43 deletions

View file

@ -210,7 +210,7 @@ paths:
$ref: "#/components/schemas/generic_response" $ref: "#/components/schemas/generic_response"
example: example:
status: "Resource not found" status: "Resource not found"
'400': # todo: not sure if this is the right error code '400':
description: Trying to follow a user that does not exist. description: Trying to follow a user that does not exist.
content: content:
application/json: application/json:

View file

@ -7,9 +7,11 @@ import (
"github.com/notherealmarco/WASAPhoto/service/database" "github.com/notherealmarco/WASAPhoto/service/database"
) )
// AnonymousAuth is the authentication provider for non logged-in users
type AnonymousAuth struct { type AnonymousAuth struct {
} }
// Returns a newly created AnonymousAuth instance
func BuildAnonymous() *AnonymousAuth { func BuildAnonymous() *AnonymousAuth {
return &AnonymousAuth{} return &AnonymousAuth{}
} }
@ -18,14 +20,17 @@ func (u *AnonymousAuth) GetType() string {
return "Anonymous" return "Anonymous"
} }
// Returns UNAUTHORIZED, as anonymous users are logged in
func (u *AnonymousAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, error) { func (u *AnonymousAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, error) {
return reqcontext.UNAUTHORIZED, nil return reqcontext.UNAUTHORIZED, nil
} }
// Returns UNAUTHORIZED, as anonymous users are not logged in
func (u *AnonymousAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error) { func (u *AnonymousAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error) {
return reqcontext.UNAUTHORIZED, nil return reqcontext.UNAUTHORIZED, nil
} }
// Returns an empty string, as anonymous users have no user ID
func (u *AnonymousAuth) GetUserID() string { func (u *AnonymousAuth) GetUserID() string {
return "" return ""
} }

View file

@ -8,6 +8,8 @@ import (
"github.com/notherealmarco/WASAPhoto/service/database" "github.com/notherealmarco/WASAPhoto/service/database"
) )
// BearerAuth is the authentication provider that authorizes users by Bearer tokens
// In this case, a token is the unique identifier for a user.
type BearerAuth struct { type BearerAuth struct {
token string token string
} }
@ -16,6 +18,8 @@ func (b *BearerAuth) GetType() string {
return "Bearer" return "Bearer"
} }
// Given the content of the Authorization header, returns a BearerAuth instance for the user
// Returns an error if the header is not valid
func BuildBearer(header string) (*BearerAuth, error) { func BuildBearer(header string) (*BearerAuth, error) {
if header == "" { if header == "" {
return nil, errors.New("missing authorization header") return nil, errors.New("missing authorization header")
@ -29,10 +33,12 @@ func BuildBearer(header string) (*BearerAuth, error) {
return &BearerAuth{token: header[7:]}, nil return &BearerAuth{token: header[7:]}, nil
} }
// Returns the user ID of the user that is currently logged in
func (b *BearerAuth) GetUserID() string { func (b *BearerAuth) GetUserID() string {
return b.token return b.token
} }
// Checks if the token is valid
func (b *BearerAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus, 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)
@ -47,6 +53,7 @@ func (b *BearerAuth) Authorized(db database.AppDatabase) (reqcontext.AuthStatus,
return reqcontext.UNAUTHORIZED, nil return reqcontext.UNAUTHORIZED, nil
} }
// Checks if the given user and the currently logged in user are the same user
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 // If uid is not a valid user, return USER_NOT_FOUND
@ -60,6 +67,7 @@ func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcon
} }
if b.token == uid { if b.token == uid {
// If the user is the same as the one in the token, check if the user does actually exist in the database
auth, err := b.Authorized(db) auth, err := b.Authorized(db)
if err != nil { if err != nil {
@ -68,5 +76,6 @@ func (b *BearerAuth) UserAuthorized(db database.AppDatabase, uid string) (reqcon
return auth, nil return auth, nil
} }
// If the user is not the same as the one in the token, return FORBIDDEN
return reqcontext.FORBIDDEN, nil return reqcontext.FORBIDDEN, nil
} }

View file

@ -10,6 +10,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// BuildAuth returns an Authorization implementation for the currently logged in user
func BuildAuth(header string) (reqcontext.Authorization, error) { func BuildAuth(header string) (reqcontext.Authorization, error) {
auth, err := BuildBearer(header) auth, err := BuildBearer(header)
if err != nil { if err != nil {
@ -21,6 +22,8 @@ func BuildAuth(header string) (reqcontext.Authorization, error) {
return auth, nil return auth, nil
} }
// Given a user authorization function, if the function returns some error, it sends the error to the client and return false
// Otherwise it returns true without sending anything to the client
func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error), uid string, db database.AppDatabase, w http.ResponseWriter, l logrus.FieldLogger, notFoundStatus int) bool { func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcontext.AuthStatus, error), uid string, db database.AppDatabase, w http.ResponseWriter, l logrus.FieldLogger, notFoundStatus int) bool {
auth, err := f(db, uid) auth, err := f(db, uid)
if err != nil { if err != nil {
@ -28,21 +31,25 @@ func SendAuthorizationError(f func(db database.AppDatabase, uid string) (reqcont
return false return false
} }
if auth == reqcontext.UNAUTHORIZED { if auth == reqcontext.UNAUTHORIZED {
// The token is not valid
helpers.SendStatus(http.StatusUnauthorized, w, "Unauthorized", l) helpers.SendStatus(http.StatusUnauthorized, w, "Unauthorized", l)
return false return false
} }
if auth == reqcontext.FORBIDDEN { if auth == reqcontext.FORBIDDEN {
// The user is not authorized for this action
helpers.SendStatus(http.StatusForbidden, w, "Forbidden", l) helpers.SendStatus(http.StatusForbidden, w, "Forbidden", l)
return false return false
} }
// requested user is not found -> 404 as the resource is not found
if auth == reqcontext.USER_NOT_FOUND { if auth == reqcontext.USER_NOT_FOUND {
// Attempting to perform an action on a non-existent user
helpers.SendStatus(notFoundStatus, w, "User not found", l) helpers.SendStatus(notFoundStatus, w, "User not found", l)
return false return false
} }
return true return true
} }
// Given a function that validates a token, if the function returns some error, it sends the error to the client and return false
// Otherwise it returns true without sending anything to the client
func SendErrorIfNotLoggedIn(f func(db database.AppDatabase) (reqcontext.AuthStatus, error), db database.AppDatabase, w http.ResponseWriter, l logrus.FieldLogger) bool { func SendErrorIfNotLoggedIn(f func(db database.AppDatabase) (reqcontext.AuthStatus, error), db database.AppDatabase, w http.ResponseWriter, l logrus.FieldLogger) bool {
auth, err := f(db) auth, err := f(db)
@ -53,6 +60,7 @@ func SendErrorIfNotLoggedIn(f func(db database.AppDatabase) (reqcontext.AuthStat
} }
if auth == reqcontext.UNAUTHORIZED { if auth == reqcontext.UNAUTHORIZED {
// The token is not valid
helpers.SendStatus(http.StatusUnauthorized, w, "Unauthorized", l) helpers.SendStatus(http.StatusUnauthorized, w, "Unauthorized", l)
return false return false
} }

View file

@ -66,6 +66,7 @@ func (rt *_router) PutBan(w http.ResponseWriter, r *http.Request, ps httprouter.
return return
} }
// Execute the query
status, err := rt.db.BanUser(uid, banned) status, err := rt.db.BanUser(uid, banned)
if err != nil { if err != nil {
@ -95,6 +96,7 @@ func (rt *_router) DeleteBan(w http.ResponseWriter, r *http.Request, ps httprout
return return
} }
// Execute the query
status, err := rt.db.UnbanUser(uid, banned) status, err := rt.db.UnbanUser(uid, banned)
if err != nil { if err != nil {

View file

@ -55,6 +55,7 @@ func (rt *_router) GetComments(w http.ResponseWriter, r *http.Request, ps httpro
} }
// send the response // send the response
w.Header().Set("Content-Type", "application/json")
err = json.NewEncoder(w).Encode(comments) err = json.NewEncoder(w).Encode(comments)
if err != nil { if err != nil {

View file

@ -79,6 +79,7 @@ func (rt *_router) PutFollow(w http.ResponseWriter, r *http.Request, ps httprout
return return
} }
// Execute the query
status, err := rt.db.FollowUser(follower, uid) status, err := rt.db.FollowUser(follower, uid)
if err != nil { if err != nil {
@ -109,6 +110,7 @@ func (rt *_router) DeleteFollow(w http.ResponseWriter, r *http.Request, ps httpr
return return
} }
// Execute the query
status, err := rt.db.UnfollowUser(follower, uid) status, err := rt.db.UnfollowUser(follower, uid)
if err != nil { if err != nil {

View file

@ -10,6 +10,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// Tries to decode a json, if it fails, it returns Bad Request to the client and the function returns false
// Otherwise it returns true without sending anything to the client
func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l logrus.FieldLogger) bool { func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l logrus.FieldLogger) bool {
err := json.NewDecoder(r).Decode(v) err := json.NewDecoder(r).Decode(v)
@ -20,6 +22,8 @@ func DecodeJsonOrBadRequest(r io.Reader, w http.ResponseWriter, v interface{}, l
return true return true
} }
// Verifies if a user exists, if it doesn't, it returns Not Found to the client and the function returns false
// Otherwise it returns true without sending anything to the client
func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter, l logrus.FieldLogger) bool { func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWriter, l logrus.FieldLogger) bool {
user_exists, err := db.UserExists(uid) user_exists, err := db.UserExists(uid)
@ -36,6 +40,8 @@ func VerifyUserOrNotFound(db database.AppDatabase, uid string, w http.ResponseWr
return true return true
} }
// Sends a generic status response
// The response is a json object with a "status" field desribing the status of a request
func SendStatus(httpStatus int, w http.ResponseWriter, description string, l logrus.FieldLogger) { func SendStatus(httpStatus int, w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(httpStatus) w.WriteHeader(httpStatus)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description}) err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
@ -44,40 +50,29 @@ func SendStatus(httpStatus int, w http.ResponseWriter, description string, l log
} }
} }
// Sends a Not Found error to the client
func SendNotFound(w http.ResponseWriter, description string, l logrus.FieldLogger) { func SendNotFound(w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(http.StatusNotFound) SendStatus(http.StatusNotFound, w, description, l)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
}
} }
// Sends a Bad Request error to the client
func SendBadRequest(w http.ResponseWriter, description string, l logrus.FieldLogger) { func SendBadRequest(w http.ResponseWriter, description string, l logrus.FieldLogger) {
w.WriteHeader(http.StatusBadRequest) SendStatus(http.StatusBadRequest, w, description, l)
err := json.NewEncoder(w).Encode(structures.GenericResponse{Status: description})
if err != nil {
l.WithError(err).Error("Error encoding json")
}
} }
// Sends a Bad Request error to the client and logs the given error
func SendBadRequestError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) { func SendBadRequestError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) {
w.WriteHeader(http.StatusBadRequest)
l.WithError(err).Error(description) l.WithError(err).Error(description)
err = json.NewEncoder(w).Encode(structures.GenericResponse{Status: description}) SendBadRequest(w, description, l)
if err != nil {
l.WithError(err).Error("Error encoding json")
}
} }
// Sends an Internal Server Error to the client and logs the given error
func SendInternalError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) { func SendInternalError(err error, description string, w http.ResponseWriter, l logrus.FieldLogger) {
w.WriteHeader(http.StatusInternalServerError)
l.WithError(err).Error(description) l.WithError(err).Error(description)
err = json.NewEncoder(w).Encode(structures.GenericResponse{Status: description}) SendStatus(http.StatusInternalServerError, w, description, l)
if err != nil {
l.WithError(err).Error("Error encoding json")
}
} }
// Tries to roll back a transaction, if it fails it logs the error
func RollbackOrLogError(tx database.DBTransaction, l logrus.FieldLogger) { func RollbackOrLogError(tx database.DBTransaction, l logrus.FieldLogger) {
err := tx.Rollback() err := tx.Rollback()
if err != nil { if err != nil {
@ -85,6 +80,8 @@ func RollbackOrLogError(tx database.DBTransaction, l logrus.FieldLogger) {
} }
} }
// Checks if a user is banned by another user, then it returns Not Found to the client and the function returns false
// Otherwise it returns true whithout sending anything to the client
func SendNotFoundIfBanned(db database.AppDatabase, uid string, banner string, w http.ResponseWriter, l logrus.FieldLogger) bool { func SendNotFoundIfBanned(db database.AppDatabase, uid string, banner string, w http.ResponseWriter, l logrus.FieldLogger) bool {
banned, err := db.IsBanned(uid, banner) banned, err := db.IsBanned(uid, banner)
if err != nil { if err != nil {

View file

@ -6,10 +6,12 @@ import (
) )
const ( const (
DEFAULT_LIMIT = 15 DEFAULT_LIMIT = 30
DEFAULT_OFFSET = 0 DEFAULT_OFFSET = 0
) )
// Get the start index and limit from the query.
// If they are not present, use the default values.
func GetLimits(query url.Values) (int, int, error) { func GetLimits(query url.Values) (int, int, error) {
limit := DEFAULT_LIMIT limit := DEFAULT_LIMIT

View file

@ -7,6 +7,8 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
) )
// Given a string, a regex and an error description, if the string doesn't match the regex, it sends a bad request error to the client and return false
// Otherwise it returns true without sending anything to the client
func MatchRegexOrBadRequest(str string, regex string, error_description string, w http.ResponseWriter, l logrus.FieldLogger) bool { func MatchRegexOrBadRequest(str string, regex string, error_description string, w http.ResponseWriter, l logrus.FieldLogger) bool {
stat, err := regexp.Match(regex, []byte(str)) stat, err := regexp.Match(regex, []byte(str))
@ -25,6 +27,7 @@ func MatchRegexOrBadRequest(str string, regex string, error_description string,
return true return true
} }
// Validates a username (must be between 3 and 16 characters long and can only contain letters, numbers and underscores)
func MatchUsernameOrBadRequest(username string, w http.ResponseWriter, l logrus.FieldLogger) bool { func MatchUsernameOrBadRequest(username string, w http.ResponseWriter, l logrus.FieldLogger) bool {
return MatchRegexOrBadRequest(username, return MatchRegexOrBadRequest(username,
`^[a-zA-Z0-9_]{3,16}$`, "Username must be between 3 and 16 characters long and can only contain letters, numbers and underscores", `^[a-zA-Z0-9_]{3,16}$`, "Username must be between 3 and 16 characters long and can only contain letters, numbers and underscores",
@ -32,6 +35,7 @@ func MatchUsernameOrBadRequest(username string, w http.ResponseWriter, l logrus.
l) l)
} }
// Validates a comment (must be between 1 and 255 characters long)
func MatchCommentOrBadRequest(comment string, w http.ResponseWriter, l logrus.FieldLogger) bool { func MatchCommentOrBadRequest(comment string, w http.ResponseWriter, l logrus.FieldLogger) bool {
return MatchRegexOrBadRequest(comment, return MatchRegexOrBadRequest(comment,
`^(.){1,255}$`, "Comment must be between 1 and 255 characters long", `^(.){1,255}$`, "Comment must be between 1 and 255 characters long",

View file

@ -1,16 +1,18 @@
package api package api
import ( import (
"github.com/julienschmidt/httprouter"
"net/http" "net/http"
"github.com/julienschmidt/httprouter"
"github.com/notherealmarco/WASAPhoto/service/api/helpers"
) )
// liveness is an HTTP handler that checks the API server status. If the server cannot serve requests (e.g., some // liveness is an HTTP handler that checks the API server status. If the server cannot serve requests (e.g., some
// resources are not ready), this should reply with HTTP Status 500. Otherwise, with HTTP Status 200 // resources are not ready), this should reply with HTTP Status 500. Otherwise, with HTTP Status 200
func (rt *_router) liveness(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { func (rt *_router) liveness(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
/* Example of liveness check: if err := rt.db.Ping(); err != nil {
if err := rt.DB.Ping(); err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)
return return
}*/ }
helpers.SendStatus(200, w, "Server is live!", rt.baseLogger)
} }

View file

@ -33,7 +33,6 @@ func (rt *_router) PostPhoto(w http.ResponseWriter, r *http.Request, ps httprout
} }
path := rt.dataPath + "/photos/" + uid + "/" + strconv.FormatInt(photo_id, 10) + ".jpg" 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 if err = os.MkdirAll(filepath.Dir(path), os.ModePerm); err != nil { // perms = 511
helpers.SendInternalError(err, "Error creating directory", w, rt.baseLogger) helpers.SendInternalError(err, "Error creating directory", w, rt.baseLogger)
@ -78,7 +77,6 @@ func (rt *_router) PostPhoto(w http.ResponseWriter, r *http.Request, ps httprout
if err != nil { if err != nil {
helpers.SendInternalError(err, "Error committing transaction", w, rt.baseLogger) helpers.SendInternalError(err, "Error committing transaction", w, rt.baseLogger)
//todo: should I roll back?
return return
} }
@ -156,7 +154,7 @@ func (rt *_router) DeletePhoto(w http.ResponseWriter, r *http.Request, ps httpro
if err != nil { if err != nil {
helpers.SendInternalError(err, "Error deleting photo from database", w, rt.baseLogger) helpers.SendInternalError(err, "Error deleting photo from database", w, rt.baseLogger)
return return
} // todo: maybe let's use a transaction also here }
if !deleted { if !deleted {
helpers.SendNotFound(w, "Photo not found", rt.baseLogger) helpers.SendNotFound(w, "Photo not found", rt.baseLogger)

View file

@ -11,9 +11,17 @@ const (
USER_NOT_FOUND = 3 USER_NOT_FOUND = 3
) )
// Authorization is the interface for an authorization provider
type Authorization interface { type Authorization interface {
// Returns the type of the authorization provider
GetType() string GetType() string
// Returns the ID of the currently logged in user
GetUserID() string GetUserID() string
// Checks if the token is valid
Authorized(db database.AppDatabase) (AuthStatus, error) Authorized(db database.AppDatabase) (AuthStatus, error)
// Checks if the given user and the currently logged in user are the same user
UserAuthorized(db database.AppDatabase, uid string) (AuthStatus, error) UserAuthorized(db database.AppDatabase, uid string) (AuthStatus, error)
} }

View file

@ -79,8 +79,11 @@ type AppDatabase interface {
Ping() error Ping() error
} }
// DBTransaction is the interface for a generic database transaction
type DBTransaction interface { type DBTransaction interface {
// Commit commits the transaction
Commit() error Commit() error
// Rollback rolls back the transaction
Rollback() error Rollback() error
} }
@ -95,16 +98,17 @@ func New(db *sql.DB) (AppDatabase, error) {
return nil, errors.New("database is required when building a AppDatabase") return nil, errors.New("database is required when building a AppDatabase")
} }
// Check if tables exist. If not, the database is empty, and we need to create the structure // Check if some table exists. If not, the database is empty, and we need to create the structure
var tableName string var tableName string
//todo: check for all the tables, not just users
err := db.QueryRow(`SELECT name FROM sqlite_master WHERE type='table' AND name='users';`).Scan(&tableName) err := db.QueryRow(`SELECT name FROM sqlite_master WHERE type='table' AND name='users';`).Scan(&tableName)
if errors.Is(err, sql.ErrNoRows) { if errors.Is(err, sql.ErrNoRows) {
// Database is empty, let's create the structure
sqlStmt := `CREATE TABLE "users" ( sqlStmt := `CREATE TABLE "users" (
"uid" TEXT NOT NULL, "uid" TEXT NOT NULL,
"name" TEXT NOT NULL UNIQUE, "name" TEXT NOT NULL UNIQUE,
PRIMARY KEY("uid") PRIMARY KEY("uid")
)` // todo: one query is enough! We are we doing a query per table? )`
_, err = db.Exec(sqlStmt) _, err = db.Exec(sqlStmt)
if err != nil { if err != nil {
return nil, fmt.Errorf("error creating database structure: %w", err) return nil, fmt.Errorf("error creating database structure: %w", err)

View file

@ -96,6 +96,7 @@ func (db *appdbimpl) UnlikePhoto(uid string, photo int64, liker_uid string) (Que
// But our DB implementation only requires the photo id. // But our DB implementation only requires the photo id.
exists, err := db.photoExists(uid, photo) exists, err := db.photoExists(uid, photo)
if err != nil || !exists { if err != nil || !exists {
// The photo does not exist, or the user has been banned
return ERR_NOT_FOUND, err return ERR_NOT_FOUND, err
} }
@ -111,6 +112,7 @@ func (db *appdbimpl) UnlikePhoto(uid string, photo int64, liker_uid string) (Que
return ERR_INTERNAL, err return ERR_INTERNAL, err
} }
// The user was not liking the photo
if rows == 0 { if rows == 0 {
return ERR_NOT_FOUND, nil return ERR_NOT_FOUND, nil
} }

View file

@ -18,7 +18,6 @@ func (db *appdbimpl) PostPhoto(uid string) (DBTransaction, int64, error) {
err_rb := tx.Rollback() err_rb := tx.Rollback()
// If rollback fails, we return the original error plus the rollback error // If rollback fails, we return the original error plus the rollback error
if err_rb != nil { if err_rb != nil {
// todo: we are losing track of err_rb here
err = fmt.Errorf("Rollback error. Rollback cause: %w", err) err = fmt.Errorf("Rollback error. Rollback cause: %w", err)
} }
@ -30,7 +29,6 @@ func (db *appdbimpl) PostPhoto(uid string) (DBTransaction, int64, error) {
err_rb := tx.Rollback() err_rb := tx.Rollback()
// If rollback fails, we return the original error plus the rollback error // If rollback fails, we return the original error plus the rollback error
if err_rb != nil { if err_rb != nil {
// todo: we are losing track of err_rb here
err = fmt.Errorf("Rollback error. Rollback cause: %w", err) err = fmt.Errorf("Rollback error. Rollback cause: %w", err)
} }
@ -66,6 +64,7 @@ func (db *appdbimpl) photoExists(uid string, photo int64) (bool, error) {
return cnt > 0, nil return cnt > 0, nil
} }
// Check if a given photo owned by a given user exists, and the requesting user is not banned by the author
func (db *appdbimpl) PhotoExists(uid string, photo int64, requesting_uid string) (bool, error) { func (db *appdbimpl) PhotoExists(uid string, photo int64, requesting_uid string) (bool, error) {
var cnt int64 var cnt int64

View file

@ -2,14 +2,17 @@ package database
import "database/sql" import "database/sql"
// dbtransaction is a struct to represent an SQL transaction, it implements the DBTransaction interface
type dbtransaction struct { type dbtransaction struct {
c *sql.Tx c *sql.Tx
} }
func (tx *dbtransaction) Commit() error { func (tx *dbtransaction) Commit() error {
// Commit the SQL transaction
return tx.c.Commit() return tx.c.Commit()
} }
func (tx *dbtransaction) Rollback() error { func (tx *dbtransaction) Rollback() error {
// Rollback the SQL transaction
return tx.c.Rollback() return tx.c.Rollback()
} }

View file

@ -2,7 +2,7 @@ package db_errors
import "strings" import "strings"
// Returns true if the query result has no rows // Returns true if the error is a "no rows in result set" error
func EmptySet(err error) bool { func EmptySet(err error) bool {
if err == nil { if err == nil {
return false return false
@ -10,6 +10,7 @@ func EmptySet(err error) bool {
return strings.Contains(err.Error(), "no rows in result set") return strings.Contains(err.Error(), "no rows in result set")
} }
// Returns true if the error is a Unique constraint violation error
func UniqueViolation(err error) bool { func UniqueViolation(err error) bool {
if err == nil { if err == nil {
return false return false
@ -17,6 +18,7 @@ func UniqueViolation(err error) bool {
return strings.Contains(err.Error(), "UNIQUE constraint failed") return strings.Contains(err.Error(), "UNIQUE constraint failed")
} }
// Returns true if the error is a Foreign Key constraint violation error
func ForeignKeyViolation(err error) bool { func ForeignKeyViolation(err error) bool {
if err == nil { if err == nil {
return false return false

View file

@ -1,7 +0,0 @@
package database
// SetName is an example that shows you how to execute insert/update
func (db *appdbimpl) SetName(name string) error {
_, err := db.c.Exec("INSERT INTO example_table (id, name) VALUES (1, ?)", name)
return err
}