From a7c58f4037b3fd06ae018f33549f326100f5058b Mon Sep 17 00:00:00 2001 From: Sisi Date: Fri, 3 Apr 2026 20:04:27 +0200 Subject: [PATCH] refactor naming in db, add user delete, update todo --- database.go | 145 ++++++++++++++++++++++++++++------------------------ http.go | 40 +++++++++++---- todo.txt | 8 +-- wsServer.go | 8 +-- 4 files changed, 115 insertions(+), 86 deletions(-) diff --git a/database.go b/database.go index a4a8ad5..ad4eca1 100644 --- a/database.go +++ b/database.go @@ -18,7 +18,7 @@ func DbInit(ctx context.Context) { } _, err = dbConn.Exec(ctx, ` - CREATE TABLE IF NOT EXISTS clients ( + CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, name VARCHAR(20) UNIQUE NOT NULL, pass_hash VARCHAR(60) NOT NULL, @@ -37,8 +37,8 @@ func DbInit(ctx context.Context) { CREATE TABLE IF NOT EXISTS chat_groups ( id SERIAL PRIMARY KEY, name VARCHAR(48) NOT NULL, - creator_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE, - owner_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + creator_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + owner_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, enable_client_colors BOOLEAN NOT NULL DEFAULT true, color_red SMALLINT DEFAULT NULL, color_green SMALLINT DEFAULT NULL, @@ -53,7 +53,7 @@ func DbInit(ctx context.Context) { _, err = dbConn.Exec(ctx, ` CREATE TABLE IF NOT EXISTS chat_group_members ( group_id INTEGER NOT NULL REFERENCES chat_groups(id) ON DELETE CASCADE, - user_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, joined_at TIMESTAMP NOT NULL DEFAULT NOW(), PRIMARY KEY (group_id, user_id) ) @@ -63,11 +63,11 @@ func DbInit(ctx context.Context) { } } -// DbSaveUserWithoutGroups saves user in db without groups and sets its id +// DbUserSaveWithoutGroups saves user in db without groups and sets its id // return: error if not successful -func DbSaveUserWithoutGroups(ctx context.Context, user *User) error { +func DbUserSaveWithoutGroups(ctx context.Context, user *User) error { err := dbConn.QueryRow(ctx, ` - INSERT INTO clients (name, pass_hash, pronouns, color_red, color_green, color_blue, created_at) + INSERT INTO users (name, pass_hash, pronouns, color_red, color_green, color_blue, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id `, user.Name, user.PasswordHash, user.Pronouns, user.Color[0], user.Color[1], user.Color[2], user.CreatedAt). @@ -75,40 +75,81 @@ func DbSaveUserWithoutGroups(ctx context.Context, user *User) error { return err } -// DbSetUserByName sets all fields of given struct with database's data using name +// DbUserDelete deletes given user by id // return: error if not successful -func DbSetUserByName(ctx context.Context, user *User) error { +func DbUserDelete(ctx context.Context, id uint32) error { + _, err := dbConn.Exec(ctx, ` + DELETE FROM users WHERE id = $1 + `, id) + return err +} + +// DbUserSetByName sets all fields of given struct with database's data using name +// return: error if not successful +func DbUserSetByName(ctx context.Context, user *User) error { err := dbConn.QueryRow(ctx, ` - SELECT id, name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM clients WHERE name = $1 + SELECT id, name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM users WHERE name = $1 `, user.Name).Scan(&user.Id, &user.Name, &user.PasswordHash, &user.Pronouns, &user.Color[0], &user.Color[1], &user.Color[2], &user.CreatedAt) if err != nil { return err } - return DbSetUserGroups(ctx, user) + return DbUserSetGroups(ctx, user) } -// DbSetUserByIdWithoutGroups sets all fields of given struct with database's data using id, excluding groups +// DbUserSetByIdWithoutGroups sets all fields of given struct with database's data using id, excluding groups // return: error if not successful -func DbSetUserByIdWithoutGroups(ctx context.Context, user *User) error { +func DbUserSetByIdWithoutGroups(ctx context.Context, user *User) error { err := dbConn.QueryRow(ctx, ` - SELECT name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM clients WHERE id = $1 + SELECT name, pass_hash, pronouns, color_red, color_green, color_blue, created_at FROM users WHERE id = $1 `, user.Id).Scan(&user.Name, &user.PasswordHash, &user.Pronouns, &user.Color[0], &user.Color[1], &user.Color[2], &user.CreatedAt) return err } -// DbSetUserById sets all fields of given struct with database's data using id, including groups +// DbUserSetById sets all fields of given struct with database's data using id, including groups // return: error if not successful -func DbSetUserById(ctx context.Context, user *User) error { - err := DbSetUserByIdWithoutGroups(ctx, user) +func DbUserSetById(ctx context.Context, user *User) error { + err := DbUserSetByIdWithoutGroups(ctx, user) if err != nil { return err } - return DbSetUserGroups(ctx, user) + return DbUserSetGroups(ctx, user) } -// DbSaveGroupWithoutUsers saves group in db and sets its id, also adds the owner as first member +// DbUserSetGroups populates user's Groups map with ids of all groups the user belongs to from database // return: error if not successful -func DbSaveGroupWithoutUsers(ctx context.Context, group *Group) error { +func DbUserSetGroups(ctx context.Context, user *User) error { + rows, err := dbConn.Query(ctx, ` + SELECT group_id FROM chat_group_members WHERE user_id = $1 + `, user.Id) + if err != nil { + return err + } + defer rows.Close() + + user.Groups = make(map[uint32]struct{}) + for rows.Next() { + var groupId uint32 + if err := rows.Scan(&groupId); err != nil { + return err + } + user.Groups[groupId] = struct{}{} + } + return rows.Err() +} + +// DbGroupSetColor set group's color based on id +// return: error if not successful +func DbGroupSetColor(ctx context.Context, group *Group) error { + _, err := dbConn.Exec(ctx, ` + UPDATE chat_groups SET color_red = $1, color_green = $2, color_blue = $3 WHERE id = $4 + `, group.Color[0], group.Color[1], group.Color[2], group.Id) + + return err +} + +// DbGroupSaveWithoutUsers saves group in db and sets its id, also adds the owner as first member +// return: error if not successful +func DbGroupSaveWithoutUsers(ctx context.Context, group *Group) error { err := dbConn.QueryRow(ctx, ` INSERT INTO chat_groups (name, creator_id, owner_id, enable_client_colors, color_red, color_green, color_blue, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) @@ -125,18 +166,18 @@ func DbSaveGroupWithoutUsers(ctx context.Context, group *Group) error { return err } -// DbDeleteGroup deletes given group by id +// DbGroupDelete deletes given group by id // return: error if not successful -func DbDeleteGroup(ctx context.Context, group *Group) error { +func DbGroupDelete(ctx context.Context, group *Group) error { _, err := dbConn.Exec(ctx, ` DELETE FROM chat_groups WHERE id = $1 `, group.Id) return err } -// DbSetGroupByIdWithoutUsers sets all fields of given struct with database's data using id, populates Users map with member ids but not their data +// DbGroupSetWithoutUsersById sets all fields of given struct with database's data using id, populates Users map with member ids but not their data // return: error if not successful -func DbSetGroupByIdWithoutUsers(ctx context.Context, group *Group) error { +func DbGroupSetWithoutUsersById(ctx context.Context, group *Group) error { err := dbConn.QueryRow(ctx, ` SELECT name, creator_id, owner_id, enable_client_colors, color_red, color_green, color_blue, created_at FROM chat_groups WHERE id = $1 `, group.Id).Scan(&group.Name, &group.CreatorId, &group.OwnerId, &group.EnableUserColors, &group.Color[0], &group.Color[1], &group.Color[2], &group.CreatedAt) @@ -163,19 +204,19 @@ func DbSetGroupByIdWithoutUsers(ctx context.Context, group *Group) error { return rows.Err() } -// DbSetGroupById sets all fields of given struct with database's data using id, including full member user data +// DbGroupSetById sets all fields of given struct with database's data using id, including full member user data // return: error if not successful -func DbSetGroupById(ctx context.Context, group *Group) error { - err := DbSetGroupByIdWithoutUsers(ctx, group) +func DbGroupSetById(ctx context.Context, group *Group) error { + err := DbGroupSetWithoutUsersById(ctx, group) if err != nil { return err } - return DbSetGroupMemberUsers(ctx, group) + return DbGroupSetMembers(ctx, group) } -// DbSetGroupMemberUsers populates group's Users map with ids of all members from database +// DbGroupSetMembers populates group's Users map with ids of all members from database // return: error if not successful -func DbSetGroupMemberUsers(ctx context.Context, group *Group) error { +func DbGroupSetMembers(ctx context.Context, group *Group) error { rows, err := dbConn.Query(ctx, ` SELECT user_id FROM chat_group_members WHERE group_id = $1 `, group.Id) @@ -195,9 +236,9 @@ func DbSetGroupMemberUsers(ctx context.Context, group *Group) error { return rows.Err() } -// DbAddUsersToGroup adds given users to group in db, silently ignores already existing members +// DbGroupAddUsers adds given users to group in db, silently ignores already existing members // return: error if not successful -func DbAddUsersToGroup(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) error { +func DbGroupAddUsers(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) error { batch := &pgx.Batch{} now := time.Now() var count int @@ -222,9 +263,9 @@ func DbAddUsersToGroup(ctx context.Context, groupId uint32, userIds *[MaxUsersIn return nil } -// DbRemoveUsersFromGroup removes given users from group in db, silently ignores not existing members +// DbGroupRemoveUsers removes given users from group in db, silently ignores not existing members // return: deleted users count, error if not successful -func DbRemoveUsersFromGroup(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) (int, error) { +func DbGroupRemoveUsers(ctx context.Context, groupId uint32, userIds *[MaxUsersInGroup]uint32) (int, error) { batch := &pgx.Batch{} var count int for _, uid := range userIds { @@ -249,41 +290,9 @@ func DbRemoveUsersFromGroup(ctx context.Context, groupId uint32, userIds *[MaxUs return deleted, nil } -// DbSetUserGroups populates user's Groups map with ids of all groups the user belongs to from database +// DbGroupSetOwnerId set group's owner based on id // return: error if not successful -func DbSetUserGroups(ctx context.Context, user *User) error { - rows, err := dbConn.Query(ctx, ` - SELECT group_id FROM chat_group_members WHERE user_id = $1 - `, user.Id) - if err != nil { - return err - } - defer rows.Close() - - user.Groups = make(map[uint32]struct{}) - for rows.Next() { - var groupId uint32 - if err := rows.Scan(&groupId); err != nil { - return err - } - user.Groups[groupId] = struct{}{} - } - return rows.Err() -} - -// DbSetGroupColor set group's color based on id -// return: error if not successful -func DbSetGroupColor(ctx context.Context, group *Group) error { - _, err := dbConn.Exec(ctx, ` - UPDATE chat_groups SET color_red = $1, color_green = $2, color_blue = $3 WHERE id = $4 - `, group.Color[0], group.Color[1], group.Color[2], group.Id) - - return err -} - -// DbSetGroupOwnerId set group's owner based on id -// return: error if not successful -func DbSetGroupOwnerId(ctx context.Context, group *Group) error { +func DbGroupSetOwnerId(ctx context.Context, group *Group) error { _, err := dbConn.Exec(ctx, ` UPDATE chat_groups SET owner_id = $1 WHERE id = $2 `, group.OwnerId, group.Id) diff --git a/http.go b/http.go index 18fae96..ac3643b 100644 --- a/http.go +++ b/http.go @@ -31,7 +31,7 @@ func getUser(ctx context.Context, token string) (*User, error) { user, err := CacheGetUserById(userId) if err != nil { user = &User{Id: userId} - err = DbSetUserById(ctx, user) + err = DbUserSetById(ctx, user) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func getGroup(ctx context.Context, groupId uint32) (*Group, error) { group, err := CacheGetGroup(groupId) if err != nil { group = &Group{Id: groupId} - err = DbSetGroupById(ctx, group) + err = DbGroupSetById(ctx, group) if err != nil { return nil, err } @@ -125,7 +125,7 @@ func HttpHandleNewUser(response http.ResponseWriter, request *http.Request) { ctx := request.Context() - err = DbSaveUserWithoutGroups(ctx, newUser) + err = DbUserSaveWithoutGroups(ctx, newUser) if err != nil { http.Error(response, "name taken", http.StatusUnauthorized) return @@ -134,6 +134,24 @@ func HttpHandleNewUser(response http.ResponseWriter, request *http.Request) { response.WriteHeader(http.StatusCreated) } +func HttpHandleDeleteUser(response http.ResponseWriter, request *http.Request) { + ctx := request.Context() + + userId, err := TokenValidateGetId(request.FormValue("token")) + if err != nil { + http.Error(response, "invalid token", http.StatusUnauthorized) + return + } + + err = DbUserDelete(ctx, userId) + if err != nil { + http.Error(response, "internal server error", http.StatusInternalServerError) + } + + CacheDeleteUser(userId) + response.WriteHeader(http.StatusAccepted) +} + func HttpHandleNewToken(response http.ResponseWriter, request *http.Request) { if !isMethodAllowed(&response, request) { return @@ -162,7 +180,7 @@ func HttpHandleNewToken(response http.ResponseWriter, request *http.Request) { if err != nil { user = &User{Name: username} - err := DbSetUserByName(ctx, user) + err := DbUserSetByName(ctx, user) if err != nil { http.Error(response, "bad login1", http.StatusUnauthorized) return @@ -221,7 +239,7 @@ func HttpHandeGroupCreate(response http.ResponseWriter, request *http.Request) { group.EnableUserColors = true } - err = DbSaveGroupWithoutUsers(ctx, &group) + err = DbGroupSaveWithoutUsers(ctx, &group) if err != nil { http.Error(response, err.Error(), http.StatusInternalServerError) return @@ -241,7 +259,7 @@ func HttpHandleGroupRemove(response http.ResponseWriter, request *http.Request) return } - err = DbDeleteGroup(ctx, group) + err = DbGroupDelete(ctx, group) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return @@ -293,7 +311,7 @@ func HttpHandleGroupAddUser(response http.ResponseWriter, request *http.Request) return } - err = DbAddUsersToGroup(ctx, group.Id, &ids) + err = DbGroupAddUsers(ctx, group.Id, &ids) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return @@ -340,7 +358,7 @@ func HttpHandleGroupRemoveUser(response http.ResponseWriter, request *http.Reque return } - count, err := DbRemoveUsersFromGroup(ctx, group.Id, &ids) + count, err := DbGroupRemoveUsers(ctx, group.Id, &ids) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return @@ -372,7 +390,7 @@ func HttpHandleGroupChangeColor(response http.ResponseWriter, request *http.Requ } group.Color = color - err = DbSetGroupColor(ctx, group) + err = DbGroupSetColor(ctx, group) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return @@ -398,7 +416,7 @@ func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Requ newOwner, err := CacheGetUserByName(newOwnerName) if err != nil { newOwner = &User{Name: newOwnerName} - err = DbSetUserByName(ctx, newOwner) + err = DbUserSetByName(ctx, newOwner) if err != nil { http.Error(response, "user not in group", http.StatusBadRequest) return @@ -414,7 +432,7 @@ func HttpHandleGroupChangeOwner(response http.ResponseWriter, request *http.Requ } group.OwnerId = newOwner.Id - err = DbSetGroupOwnerId(ctx, group) + err = DbGroupSetOwnerId(ctx, group) if err != nil { http.Error(response, "internal server error", http.StatusInternalServerError) return diff --git a/todo.txt b/todo.txt index a5b7e06..de69ac9 100644 --- a/todo.txt +++ b/todo.txt @@ -1,6 +1,8 @@ -delete client -change client color -make client work with string color names +change user color + +direct messaging +friend list +who can send messages to who chat history diff --git a/wsServer.go b/wsServer.go index 76af0a7..60d4487 100644 --- a/wsServer.go +++ b/wsServer.go @@ -86,7 +86,7 @@ func WsSendToGroup(ctx context.Context, groupId uint32, senderId uint32, message sender, err := CacheGetUserById(senderId) if err != nil { sender = &User{Id: senderId} - err = DbSetUserById(ctx, sender) + err = DbUserSetById(ctx, sender) if err != nil { return errors.New("non existing sender") } @@ -138,7 +138,7 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage * userFromCache, err := CacheGetUserById(userId) if err != nil { dbUser := &User{Id: userId} - err = DbSetUserByIdWithoutGroups(ctx, dbUser) + err = DbUserSetByIdWithoutGroups(ctx, dbUser) if err != nil { var msg = map[string]any{ "from": "server", @@ -147,7 +147,7 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage * sendMessageCloseIfTimeout(user, &msg) return false } - err = DbSetUserGroups(ctx, dbUser) + err = DbUserSetGroups(ctx, dbUser) if err != nil { var msg = map[string]any{ "from": "server", @@ -169,7 +169,7 @@ func handleUnauthenticatedMessage(ctx context.Context, user *User, userMessage * if err != nil { dbGroup := &Group{Id: groupId} - err = DbSetGroupById(ctx, dbGroup) + err = DbGroupSetById(ctx, dbGroup) if err != nil { var msg = map[string]any{ "from": "server",