Compare commits

...

3 Commits

Author SHA1 Message Date
72c0188071 expand logging 2025-05-18 18:25:17 -04:00
028c084cdd start logging -- also clarified env checks 2025-05-18 15:08:17 -04:00
f2b046056b put sessions in db 2025-05-18 13:44:30 -04:00
8 changed files with 269 additions and 42 deletions

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"flag" "flag"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"git.dubyatp.xyz/chat-api-server/db" "git.dubyatp.xyz/chat-api-server/db"
@@ -14,6 +15,16 @@ import (
var routes = flag.Bool("routes", false, "Generate API route documentation") var routes = flag.Bool("routes", false, "Generate API route documentation")
func RequestLog(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Debug("api: request received",
"request_uri", r.RequestURI,
"source_ip", r.RemoteAddr,
"user_agent", r.UserAgent())
next.ServeHTTP(w, r)
})
}
func Start() { func Start() {
db.InitScyllaDB() db.InitScyllaDB()
@@ -24,7 +35,7 @@ func Start() {
r := chi.NewRouter() r := chi.NewRouter()
r.Use(middleware.RequestID) r.Use(middleware.RequestID)
r.Use(middleware.Logger) r.Use(RequestLog)
r.Use(middleware.Recoverer) r.Use(middleware.Recoverer)
r.Use(middleware.URLFormat) r.Use(middleware.URLFormat)
r.Use(render.SetContentType(render.ContentTypeJSON)) r.Use(render.SetContentType(render.ContentTypeJSON))

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"log/slog"
"net/http" "net/http"
"time" "time"
@@ -45,6 +46,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
Secure: false, Secure: false,
}) })
slog.Info("auth: login successful", "user", user.Name)
w.Write([]byte("Login successful")) w.Write([]byte("Login successful"))
} }
@@ -67,28 +69,55 @@ func Logout(w http.ResponseWriter, r *http.Request) {
cookie.Expires = time.Now() cookie.Expires = time.Now()
http.SetCookie(w, cookie) http.SetCookie(w, cookie)
slog.Debug("auth: logout successful", "user", username)
w.Write([]byte(username + " has been logged out")) w.Write([]byte(username + " has been logged out"))
} }
var sessionStore = make(map[string]string) type Session struct {
Token uuid.UUID
Username string
}
func CreateSession(username string) string { func CreateSession(username string) string {
sessionToken := uuid.New().String() session := Session{
sessionStore[sessionToken] = username Token: uuid.New(),
return sessionToken Username: username,
}
dbAddSession(&session)
slog.Debug("auth: new session created", "user", session.Username)
return session.Token.String()
} }
func ValidateSession(sessionToken string) (string, bool) { func ValidateSession(sessionToken string) (string, bool) {
username, exists := sessionStore[sessionToken] tokenUUID, err := uuid.Parse(sessionToken)
return username, exists if err != nil {
return "", false
}
session, err := dbGetSession(tokenUUID)
if err != nil {
return "", false
}
slog.Debug("auth: session validated", "user", session.Username)
return session.Username, true
} }
func DeleteSession(sessionToken string) (string, bool) { func DeleteSession(sessionToken string) (string, bool) {
username, exists := sessionStore[sessionToken] tokenUUID, err := uuid.Parse(sessionToken)
if err != nil {
return "", false
}
delete(sessionStore, username) session, err := dbGetSession(tokenUUID)
return username, exists if err != nil {
return "", false
} else {
dbDeleteSession(session.Token)
}
slog.Debug("auth: session deleted", "user", session.Username)
return session.Username, true
} }
type contextKey string type contextKey string

View File

@@ -3,34 +3,45 @@ package api
import ( import (
"errors" "errors"
"fmt" "fmt"
"log/slog"
"git.dubyatp.xyz/chat-api-server/db" "git.dubyatp.xyz/chat-api-server/db"
"github.com/gocql/gocql" "github.com/gocql/gocql"
"github.com/google/uuid"
) )
func dbGetUser(id string) (*User, error) { func dbGetUser(id string) (*User, error) {
query := `SELECT id, name, password FROM users WHERE id = ?` query := `SELECT id, name, password FROM users WHERE id = ?`
var user User var user User
err := db.Session.Query(query, id).Scan(&user.ID, &user.Name, &user.Password) err := db.Session.Query(query, id).Scan(&user.ID, &user.Name, &user.Password)
if err == gocql.ErrNotFound { if err == gocql.ErrNotFound {
slog.Debug("db: user not found", "userid", id)
return nil, errors.New("User not found") return nil, errors.New("User not found")
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("failed to query user: %v", err) slog.Error("db: failed to query user", "error", err)
return nil, fmt.Errorf("failed to query user")
} }
slog.Debug("db: user found", "userid", user.ID, "username", user.Name)
return &user, nil return &user, nil
} }
func dbGetUserByName(username string) (*User, error) { func dbGetUserByName(username string) (*User, error) {
// This will be deprecated soon after implementing https://git.dubyatp.xyz/williamp/chatservice_concept/issues/1
query := `SELECT id, name, password FROM users WHERE name = ? ALLOW FILTERING` query := `SELECT id, name, password FROM users WHERE name = ? ALLOW FILTERING`
var user User var user User
err := db.Session.Query(query, username).Scan(&user.ID, &user.Name, &user.Password) err := db.Session.Query(query, username).Scan(&user.ID, &user.Name, &user.Password)
if err == gocql.ErrNotFound { if err == gocql.ErrNotFound {
slog.Debug("db: user not found", "username", username)
return nil, errors.New("User not found") return nil, errors.New("User not found")
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("failed to query user: %v", err) slog.Error("db: failed to query user", "error", err)
return nil, fmt.Errorf("failed to query user")
} }
slog.Debug("db: user found", "userid", user.ID, "username", user.Name)
return &user, nil return &user, nil
} }
@@ -49,13 +60,16 @@ func dbGetAllUsers() ([]*User, error) {
} }
if err := iter.Close(); err != nil { if err := iter.Close(); err != nil {
return nil, fmt.Errorf("failed to iterate users: %v", err) slog.Error("db: failed to iterate users", "error", err)
return nil, fmt.Errorf("failed to iterate users")
} }
if len(users) == 0 { if len(users) == 0 {
slog.Debug("db: no users found")
return nil, errors.New("no users found") return nil, errors.New("no users found")
} }
slog.Debug("db: user list returned")
return users, nil return users, nil
} }
@@ -69,11 +83,14 @@ func dbGetMessage(id string) (*Message, error) {
&message.Timestamp, &message.Timestamp,
&message.UserID) &message.UserID)
if err == gocql.ErrNotFound { if err == gocql.ErrNotFound {
slog.Debug("db: message not found", "messageid", id)
return nil, errors.New("Message not found") return nil, errors.New("Message not found")
} else if err != nil { } else if err != nil {
return nil, fmt.Errorf("failed to query message: %v", err) slog.Error("db: failed to query message", "error", err)
return nil, fmt.Errorf("failed to query message")
} }
slog.Debug("db: message found", "messageid", message.ID)
return &message, nil return &message, nil
} }
@@ -97,22 +114,71 @@ func dbGetAllMessages() ([]*Message, error) {
} }
if err := iter.Close(); err != nil { if err := iter.Close(); err != nil {
return nil, fmt.Errorf("failed to iterate messages: %v", err) slog.Error("db: failed to iterate messages", "error", err)
return nil, fmt.Errorf("failed to iterate messages")
} }
if len(messages) == 0 { if len(messages) == 0 {
slog.Debug("db: no messages found")
return nil, errors.New("no messages found") return nil, errors.New("no messages found")
} }
slog.Debug("db: message list returned")
return messages, nil return messages, nil
} }
func dbAddSession(session *Session) error {
query := `INSERT INTO sessions (session_token, username) VALUES (?, ?)`
err := db.Session.Query(query, session.Token, session.Username).Exec()
if err != nil {
slog.Error("db: failed to add session", "error", err)
return fmt.Errorf("failed to add session")
}
slog.Debug("db: session added", "username", session.Username)
return nil
}
func dbGetSession(id uuid.UUID) (*Session, error) {
query := `SELECT session_token, username FROM sessions WHERE session_token = ?`
var session Session
err := db.Session.Query(query, id).Scan(
&session.Token,
&session.Username)
if err == gocql.ErrNotFound {
slog.Debug("db: session not found")
return nil, errors.New("Session not found")
} else if err != nil {
slog.Error("db: failed to query session", "error", err)
return nil, fmt.Errorf("failed to query session")
}
return &session, nil
}
func dbDeleteSession(id uuid.UUID) error {
query := `DELETE FROM sessions WHERE session_token = ?`
err := db.Session.Query(query, id).Exec()
if err != nil {
slog.Error("db: failed to delete session")
return fmt.Errorf("failed to delete session")
}
slog.Debug("db: session deleted")
return nil
}
func dbAddUser(user *User) error { func dbAddUser(user *User) error {
query := `INSERT INTO users (id, name, password) VALUES (?, ?, ?)` query := `INSERT INTO users (id, name, password) VALUES (?, ?, ?)`
err := db.Session.Query(query, user.ID, user.Name, user.Password).Exec() err := db.Session.Query(query, user.ID, user.Name, user.Password).Exec()
if err != nil { if err != nil {
return fmt.Errorf("failed to add user: %v", err) slog.Error("db: failed to add user", "error", err, "userid", user.ID, "username", user.Name)
return fmt.Errorf("failed to add user")
} }
slog.Debug("db: user added", "userid", user.ID, "username", user.Name)
return nil return nil
} }
@@ -126,8 +192,11 @@ func dbAddMessage(message *Message) error {
message.Timestamp, message.Timestamp,
message.UserID).Exec() message.UserID).Exec()
if err != nil { if err != nil {
return fmt.Errorf("failed to add message: %v", err) slog.Error("db: failed to add message", "error", err, "messageid", message.ID)
return fmt.Errorf("failed to add message")
} }
slog.Debug("db: message added", "messageid", message.ID)
return nil return nil
} }
@@ -150,9 +219,11 @@ func dbUpdateMessage(updatedMessage *Message) error {
updatedMessage.ID).Exec() updatedMessage.ID).Exec()
if err != nil { if err != nil {
return fmt.Errorf("failed to update message: %v", err) slog.Error("db: failed to update message", "error", err, "messageid", updatedMessage.ID)
return fmt.Errorf("failed to update message")
} }
slog.Debug("db: message updated", "messageid", updatedMessage.ID)
return nil return nil
} }
@@ -163,8 +234,10 @@ func dbDeleteMessage(id string) error {
err := db.Session.Query(query, id).Exec() err := db.Session.Query(query, id).Exec()
if err != nil { if err != nil {
return fmt.Errorf("failed to delete message: %v", err) slog.Error("db: failed to delete message", "error", err, "messageid", id)
return fmt.Errorf("failed to delete message")
} }
slog.Debug("db: message deleted", "messageid", id)
return nil return nil
} }

View File

@@ -3,6 +3,7 @@ package api
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"log/slog"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -15,91 +16,125 @@ import (
func MessageCtx(next http.Handler) http.Handler { func MessageCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering MessageCtx middleware")
var message *Message var message *Message
var err error var err error
if messageID := chi.URLParam(r, "messageID"); messageID != "" { if messageID := chi.URLParam(r, "messageID"); messageID != "" {
slog.Debug("message: fetching message", "messageID", messageID)
message, err = dbGetMessage(messageID) message, err = dbGetMessage(messageID)
} else { } else {
slog.Error("message: messageID not found in URL parameters")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
if err != nil { if err != nil {
slog.Error("message: failed to fetch message", "messageID", chi.URLParam(r, "messageID"), "error", err)
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
slog.Debug("message: successfully fetched message", "messageID", message.ID)
ctx := context.WithValue(r.Context(), messageKey{}, message) ctx := context.WithValue(r.Context(), messageKey{}, message)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
func GetMessage(w http.ResponseWriter, r *http.Request) { func GetMessage(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering GetMessage handler")
message, ok := r.Context().Value(messageKey{}).(*Message) message, ok := r.Context().Value(messageKey{}).(*Message)
if !ok || message == nil { if !ok || message == nil {
slog.Error("message: message not found in context")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
slog.Debug("message: rendering message", "messageID", message.ID)
if err := render.Render(w, r, NewMessageResponse(message)); err != nil { if err := render.Render(w, r, NewMessageResponse(message)); err != nil {
slog.Error("message: failed to render message response", "messageID", message.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
} }
func EditMessage(w http.ResponseWriter, r *http.Request) { func EditMessage(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering EditMessage handler")
message, ok := r.Context().Value(messageKey{}).(*Message) message, ok := r.Context().Value(messageKey{}).(*Message)
if !ok || message == nil { if !ok || message == nil {
slog.Error("message: message not found in context")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
err := r.ParseMultipartForm(64 << 10) err := r.ParseMultipartForm(64 << 10)
if err != nil { if err != nil {
slog.Error("message: failed to parse multipart form", "error", err)
http.Error(w, "Unable to parse form", http.StatusBadRequest) http.Error(w, "Unable to parse form", http.StatusBadRequest)
return return
} }
body := r.FormValue("body") body := r.FormValue("body")
if body == "" { if body == "" {
slog.Error("message: message body is empty")
http.Error(w, "Message body cannot be empty", http.StatusBadRequest) http.Error(w, "Message body cannot be empty", http.StatusBadRequest)
return return
} }
slog.Debug("message: updating message", "messageID", message.ID)
message.Body = body message.Body = body
editedTime := time.Now() editedTime := time.Now()
message.Edited = &editedTime message.Edited = &editedTime
err = dbUpdateMessage(message) err = dbUpdateMessage(message)
if err != nil { if err != nil {
slog.Error("message: failed to update message", "messageID", message.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
slog.Debug("message: successfully updated message", "messageID", message.ID)
if err := render.Render(w, r, NewMessageResponse(message)); err != nil { if err := render.Render(w, r, NewMessageResponse(message)); err != nil {
slog.Error("message: failed to render updated message response", "messageID", message.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
} }
func DeleteMessage(w http.ResponseWriter, r *http.Request) { func DeleteMessage(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering DeleteMessage handler")
message, ok := r.Context().Value(messageKey{}).(*Message) message, ok := r.Context().Value(messageKey{}).(*Message)
if !ok || message == nil { if !ok || message == nil {
slog.Error("message: message not found in context")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
dbDeleteMessage(message.ID.String())
slog.Debug("message: deleting message", "messageID", message.ID)
err := dbDeleteMessage(message.ID.String())
if err != nil {
slog.Error("message: failed to delete message", "messageID", message.ID, "error", err)
render.Render(w, r, ErrRender(err))
return
}
slog.Debug("message: successfully deleted message", "messageID", message.ID)
if err := render.Render(w, r, NewMessageResponse(message)); err != nil { if err := render.Render(w, r, NewMessageResponse(message)); err != nil {
slog.Error("message: failed to render deleted message response", "messageID", message.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
} }
func ListMessages(w http.ResponseWriter, r *http.Request) { func ListMessages(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering ListMessages handler")
dbMessages, err := dbGetAllMessages() dbMessages, err := dbGetAllMessages()
if err != nil { if err != nil {
slog.Error("message: failed to fetch messages", "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
slog.Debug("message: successfully fetched messages", "count", len(dbMessages))
if err := render.RenderList(w, r, NewMessageListResponse(dbMessages)); err != nil { if err := render.RenderList(w, r, NewMessageListResponse(dbMessages)); err != nil {
slog.Error("message: failed to render message list response", "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
@@ -110,23 +145,19 @@ func newMessageID() uuid.UUID {
} }
func NewMessage(w http.ResponseWriter, r *http.Request) { func NewMessage(w http.ResponseWriter, r *http.Request) {
slog.Debug("message: entering NewMessage handler")
err := r.ParseMultipartForm(64 << 10) err := r.ParseMultipartForm(64 << 10)
if err != nil { if err != nil {
slog.Error("message: failed to parse multipart form", "error", err)
http.Error(w, "Unable to parse form", http.StatusBadRequest) http.Error(w, "Unable to parse form", http.StatusBadRequest)
return return
} }
// userID := r.FormValue("user_id")
//if userID == "" {
// http.Error(w, "Invalid user ID", http.StatusBadRequest)
// return
//}
var user = r.Context().Value(userKey{}).(*User) var user = r.Context().Value(userKey{}).(*User)
body := r.FormValue("body") body := r.FormValue("body")
if body == "" { if body == "" {
slog.Error("message: message body is empty")
http.Error(w, "Invalid body", http.StatusBadRequest) http.Error(w, "Invalid body", http.StatusBadRequest)
return return
} }
@@ -138,12 +169,15 @@ func NewMessage(w http.ResponseWriter, r *http.Request) {
Timestamp: time.Now(), Timestamp: time.Now(),
} }
slog.Debug("message: creating new message", "messageID", msg.ID)
err = dbAddMessage(&msg) err = dbAddMessage(&msg)
if err != nil { if err != nil {
slog.Error("message: failed to add new message", "messageID", msg.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
slog.Debug("message: successfully created new message", "messageID", msg.ID)
render.Render(w, r, NewMessageResponse(&msg)) render.Render(w, r, NewMessageResponse(&msg))
} }

View File

@@ -2,6 +2,7 @@ package api
import ( import (
"context" "context"
"log/slog"
"net/http" "net/http"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
@@ -11,79 +12,96 @@ import (
func UserCtx(next http.Handler) http.Handler { func UserCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
slog.Debug("user: entering UserCtx middleware")
var user *User var user *User
var err error var err error
if userID := chi.URLParam(r, "userID"); userID != "" { if userID := chi.URLParam(r, "userID"); userID != "" {
slog.Debug("user: fetching user", "userID", userID)
user, err = dbGetUser(userID) user, err = dbGetUser(userID)
} else { } else {
slog.Error("user: userID not found in URL parameters")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
if err != nil { if err != nil {
slog.Error("user: failed to fetch user", "userID", user.ID, "error", err)
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
slog.Debug("user: successfully fetched user", "userID", user.ID)
ctx := context.WithValue(r.Context(), userKey{}, user) ctx := context.WithValue(r.Context(), userKey{}, user)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
func Whoami(w http.ResponseWriter, r *http.Request) { func Whoami(w http.ResponseWriter, r *http.Request) {
slog.Debug("user: entering Whoami handler")
user, ok := r.Context().Value(userKey{}).(*User) user, ok := r.Context().Value(userKey{}).(*User)
if !ok || user == nil { if !ok || user == nil {
// Anonymous user slog.Debug("user: anonymous user")
w.Write([]byte("anonymous")) w.Write([]byte("anonymous"))
return return
} }
slog.Debug("user: returning user name", "userID", user.ID, "userName", user.Name)
w.Write([]byte(user.Name)) w.Write([]byte(user.Name))
} }
func LoginCtx(next http.Handler) http.Handler { func LoginCtx(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Try to retrieve username from context slog.Debug("user: entering LoginCtx middleware")
username, ok := r.Context().Value(usernameKey).(string) username, ok := r.Context().Value(usernameKey).(string)
if !ok || username == "" { if !ok || username == "" {
// No username provided, assume it's an anonymous user slog.Debug("user: no username provided, assuming anonymous user")
next.ServeHTTP(w, r) next.ServeHTTP(w, r)
return return
} }
// Lookup user in the database slog.Debug("user: fetching user by username", "username", username)
user, err := dbGetUserByName(username) user, err := dbGetUserByName(username)
if err != nil { if err != nil {
// If user is specified and not found, throw an error slog.Error("user: failed to fetch user by username", "username", username, "error", err)
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
slog.Debug("user: successfully fetched user", "userID", user.ID, "username", user.Name)
ctx := context.WithValue(r.Context(), userKey{}, user) ctx := context.WithValue(r.Context(), userKey{}, user)
next.ServeHTTP(w, r.WithContext(ctx)) next.ServeHTTP(w, r.WithContext(ctx))
}) })
} }
func GetUser(w http.ResponseWriter, r *http.Request) { func GetUser(w http.ResponseWriter, r *http.Request) {
slog.Debug("user: entering GetUser handler")
user, ok := r.Context().Value(userKey{}).(*User) user, ok := r.Context().Value(userKey{}).(*User)
if !ok || user == nil { if !ok || user == nil {
slog.Error("user: user not found in context")
render.Render(w, r, ErrNotFound) render.Render(w, r, ErrNotFound)
return return
} }
slog.Debug("user: rendering user", "userID", user.ID, "userName", user.Name)
if err := render.Render(w, r, NewUserPayloadResponse(user)); err != nil { if err := render.Render(w, r, NewUserPayloadResponse(user)); err != nil {
slog.Error("user: failed to render user response", "userID", user.ID, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
} }
func ListUsers(w http.ResponseWriter, r *http.Request) { func ListUsers(w http.ResponseWriter, r *http.Request) {
slog.Debug("user: entering ListUsers handler")
dbUsers, err := dbGetAllUsers() dbUsers, err := dbGetAllUsers()
if err != nil { if err != nil {
slog.Error("user: failed to fetch users", "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
slog.Debug("user: successfully fetched users", "count", len(dbUsers))
if err := render.RenderList(w, r, NewUserListResponse(dbUsers)); err != nil { if err := render.RenderList(w, r, NewUserListResponse(dbUsers)); err != nil {
slog.Error("user: failed to render user list response", "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
@@ -94,8 +112,10 @@ func newUserID() uuid.UUID {
} }
func NewUser(w http.ResponseWriter, r *http.Request) { func NewUser(w http.ResponseWriter, r *http.Request) {
slog.Debug("user: entering NewUser handler")
err := r.ParseMultipartForm(64 << 10) err := r.ParseMultipartForm(64 << 10)
if err != nil { if err != nil {
slog.Error("user: failed to parse multipart form", "error", err)
http.Error(w, "Unable to parse form", http.StatusBadRequest) http.Error(w, "Unable to parse form", http.StatusBadRequest)
return return
} }
@@ -103,13 +123,17 @@ func NewUser(w http.ResponseWriter, r *http.Request) {
newUserName := r.FormValue("name") newUserName := r.FormValue("name")
password := r.FormValue("password") password := r.FormValue("password")
if newUserName == "" || password == "" { if newUserName == "" || password == "" {
slog.Error("user: username or password is empty")
http.Error(w, "Username and password cannot be empty", http.StatusBadRequest) http.Error(w, "Username and password cannot be empty", http.StatusBadRequest)
return return
} }
slog.Debug("user: hashing password for new user", "userName", newUserName)
hashedPassword, err := hashPassword(password) hashedPassword, err := hashPassword(password)
if err != nil { if err != nil {
slog.Error("user: failed to hash password", "error", err)
http.Error(w, "Unable to hash password", http.StatusInternalServerError) http.Error(w, "Unable to hash password", http.StatusInternalServerError)
return
} }
newUser := User{ newUser := User{
@@ -118,12 +142,15 @@ func NewUser(w http.ResponseWriter, r *http.Request) {
Password: hashedPassword, Password: hashedPassword,
} }
slog.Debug("user: adding new user to database", "userID", newUser.ID, "userName", newUser.Name)
err = dbAddUser(&newUser) err = dbAddUser(&newUser)
if err != nil { if err != nil {
slog.Error("user: failed to add new user", "userID", newUser.ID, "userName", newUser.Name, "error", err)
render.Render(w, r, ErrRender(err)) render.Render(w, r, ErrRender(err))
return return
} }
slog.Debug("user: successfully added new user", "userID", newUser.ID, "userName", newUser.Name)
render.Render(w, r, NewUserPayloadResponse(&newUser)) render.Render(w, r, NewUserPayloadResponse(&newUser))
} }

View File

@@ -1,7 +1,7 @@
package db package db
import ( import (
"log" "log/slog"
"os" "os"
"github.com/gocql/gocql" "github.com/gocql/gocql"
@@ -16,10 +16,11 @@ func InitScyllaDB() {
session, err := cluster.CreateSession() session, err := cluster.CreateSession()
if err != nil { if err != nil {
log.Fatalf("Failed to connect to ScyllaDB: %v", err) slog.Error("Failed to connect to ScyllaDB", "error", err)
os.Exit(1)
} }
Session = session Session = session
log.Println("Connected to ScyllaDB") slog.Info("Connected to ScyllaDB")
} }
func CloseScyllaDB() { func CloseScyllaDB() {

31
log/log.go Normal file
View File

@@ -0,0 +1,31 @@
package log
import (
"flag"
"log/slog"
"os"
)
func Logger() {
// Get logger arguments
var loglevelStr string
flag.StringVar(&loglevelStr, "loglevel", "ERROR", "set log level")
flag.Parse()
loglevel := new(slog.LevelVar)
if loglevelStr == "DEBUG" {
loglevel.Set(slog.LevelDebug)
} else if loglevelStr == "INFO" {
loglevel.Set(slog.LevelInfo)
} else if loglevelStr == "WARN" {
loglevel.Set(slog.LevelWarn)
} else {
loglevel.Set(slog.LevelError)
}
// Start logger
logger := slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: loglevel,
}))
slog.SetDefault(logger)
slog.Debug("Logging started", "level", loglevel)
}

33
main.go
View File

@@ -1,22 +1,43 @@
package main package main
import ( import (
"log" "log/slog"
"os" "os"
"git.dubyatp.xyz/chat-api-server/api" "git.dubyatp.xyz/chat-api-server/api"
"git.dubyatp.xyz/chat-api-server/log"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
func checkEnvVars(keys []string) (bool, []string) {
var missing []string
for _, key := range keys {
if _, ok := os.LookupEnv(key); !ok {
missing = append(missing, key)
}
}
return len(missing) == 0, missing
}
func main() { func main() {
log.Logger() // initialize logger
requiredEnvVars := []string{"SCYLLA_CLUSTER", "SCYLLA_KEYSPACE"}
// Initialize dotenv file // Initialize dotenv file
err := godotenv.Load() err := godotenv.Load()
if err != nil { if err != nil {
// Check if environment variables are defined by OS before erroring out slog.Info("No .env file loaded, will try OS environment variables")
_, exists := os.LookupEnv("SCYLLA_CLUSTER")
if !exists {
log.Fatal("Required environment variables are not added, and no .env file was found")
}
} }
// Check if environment variables were defined by the OS before erroring out
exists, missingVars := checkEnvVars(requiredEnvVars)
if !exists {
slog.Error("Missing environment variables", "missing", missingVars)
os.Exit(1)
}
slog.Info("Starting the API Server...")
api.Start() api.Start()
} }