From dc784da1213a872637356e14282790a36a677166 Mon Sep 17 00:00:00 2001 From: VERNA990 Date: Mon, 1 Dec 2025 13:58:03 +0100 Subject: [PATCH] improment to code by adding users and giving them the ability to start dms, threads and create groups --- .postman/config.json | 14 + README.md | 8 +- api/api.go | 269 ++++++++- db/migrations/000001_init_schema.down.sql | 15 +- db/migrations/000001_init_schema.up.sql | 61 +- db/query/message.sql | 153 ++++- db/repo/db.go | 2 +- db/repo/message.sql.go | 562 +++++++++++++++++- db/repo/models.go | 181 +++++- db/repo/querier.go | 20 +- go.mod | 3 +- go.sum | 75 ++- ..._db endpoint tests.postman_collection.json | 383 ++++++++++++ .../globals/workspace.postman_globals.json | 7 + 14 files changed, 1662 insertions(+), 91 deletions(-) create mode 100644 .postman/config.json create mode 100644 postman/collections/message_db endpoint tests.postman_collection.json create mode 100644 postman/globals/workspace.postman_globals.json diff --git a/.postman/config.json b/.postman/config.json new file mode 100644 index 0000000..2ab5e0a --- /dev/null +++ b/.postman/config.json @@ -0,0 +1,14 @@ +{ + "workspace": { + "id": "2bd7d4b3-6122-42d7-8bc2-ec10cc8929fd" + }, + "entities": { + "collections": [ + "../postman/collections/message_db endpoint tests.postman_collection.json" + ], + "environments": [], + "specs": [], + "flows": [], + "globals": [] + } +} \ No newline at end of file diff --git a/README.md b/README.md index 6e57521..0182d00 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ # sqlc-example-api -This repository provides starter code for creating a API using sqlc and the Gin web framework in Go. This is part of the Relational Database course as part of the Iknite Space training. +This repository provides code for creating a API using sqlc and the Gin web framework in Go. This is an improvement for the started code that was provided. +This code support the creation of users, +starting of direct messages(chat between two users), +creation of group and adding of members, +threads(within a group which can be left as public or set to restricted to a few users) and adding of members, +messages(which can be dm, group or thread messages) +link to api documentation: https://documenter.getpostman.com/view/50168458/2sB3dLVXe9 Project Structure diff --git a/api/api.go b/api/api.go index ada2548..3b79ba0 100644 --- a/api/api.go +++ b/api/api.go @@ -26,14 +26,30 @@ func (h *MessageHandler) WireHttpHandler() http.Handler { })) r.POST("/message", h.handleCreateMessage) - r.GET("/message/:id", h.handleGetMessage) - r.GET("/thread/:id/messages", h.handleGetThreadMessages) + r.POST("/user", h.handleAddUser) + r.POST("/group", h.handleCreateGroup) + r.POST("/thread", h.handleStartThread) + r.POST("/group/member", h.handleAddGroupMember) + r.POST("/thread/member", h.handleAddThreadMember) + r.POST("/dm", h.handleStartDm) + + r.GET("/message/:message_id", h.handleGetMessage) + r.GET("/message/dm/:dm_id", h.handleGetDmMessage) + r.GET("/message/group/:group_id", h.handleGetGroupMessage) + r.GET("/message/thread/:thread_id", h.handleGetThreadMessage) + r.GET("/user/group/:user_id", h.handleGetUserGroup) + r.GET("/user/thread/:user_id", h.handleGetUserThread) + r.GET("/user/group/creator/:user_id", h.handleGetGroupsUserCreated) + r.GET("/user/thread/creator/:user_id", h.handleGetThreadsUserStarted) + r.GET("/group/thread/:group_id", h.handleGetGroupThread) + r.GET("/group/group-list", h.handleGetListOfGroups) return r } func (h *MessageHandler) handleCreateMessage(c *gin.Context) { var req repo.CreateMessageParams + err := c.ShouldBindBodyWithJSON(&req) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) @@ -49,10 +65,118 @@ func (h *MessageHandler) handleCreateMessage(c *gin.Context) { c.JSON(http.StatusOK, message) } +func (h *MessageHandler) handleAddUser(c *gin.Context) { + var req repo.AddUserParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.AddUser(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleCreateGroup(c *gin.Context) { + var req repo.CreateGroupParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.CreateGroup(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleStartThread(c *gin.Context) { + var req repo.StartThreadParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.StartThread(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleAddGroupMember(c *gin.Context) { + var req repo.AddGroupMemberParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.AddGroupMember(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleAddThreadMember(c *gin.Context) { + var req repo.AddThreadMemberParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.AddThreadMember(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleStartDm(c *gin.Context) { + var req repo.StartDmParams + + err := c.ShouldBindBodyWithJSON(&req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + message, err := h.querier.StartDm(c, req) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + func (h *MessageHandler) handleGetMessage(c *gin.Context) { - id := c.Param("id") + id := c.Param("message_id") if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "message id is required"}) return } @@ -65,22 +189,141 @@ func (h *MessageHandler) handleGetMessage(c *gin.Context) { c.JSON(http.StatusOK, message) } -func (h *MessageHandler) handleGetThreadMessages(c *gin.Context) { - id := c.Param("id") +func (h *MessageHandler) handleGetDmMessage(c *gin.Context) { + id := c.Param("dm_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "dm id is required"}) + return + } + + message, err := h.querier.GetDmMessages(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetGroupMessage(c *gin.Context) { + id := c.Param("group_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "group id is required"}) + return + } + + message, err := h.querier.GetGroupMessages(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetThreadMessage(c *gin.Context) { + id := c.Param("thread_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "thread id is required"}) + return + } + + message, err := h.querier.GetThreadMessages(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetUserGroup(c *gin.Context) { + id := c.Param("user_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "user id is required"}) + return + } + + message, err := h.querier.GetUserGroups(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetUserThread(c *gin.Context) { + id := c.Param("user_id") if id == "" { - c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"}) + c.JSON(http.StatusBadRequest, gin.H{"error": "user id is required"}) return } - messages, err := h.querier.GetMessagesByThread(c, id) + message, err := h.querier.GetUserThreads(c, id) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{ - "thread": id, - "topic": "example", - "messages": messages, - }) + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetGroupsUserCreated(c *gin.Context) { + id := c.Param("user_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "user id is required"}) + return + } + + message, err := h.querier.GetGroupsUserCreated(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetThreadsUserStarted(c *gin.Context) { + id := c.Param("user_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "user id is required"}) + return + } + + message, err := h.querier.GetThreadsUserStarted(c, &id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetGroupThread(c *gin.Context) { + id := c.Param("group_id") + if id == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "group id is required"}) + return + } + + message, err := h.querier.GetGroupThreads(c, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) +} + +func (h *MessageHandler) handleGetListOfGroups(c *gin.Context) { + + message, err := h.querier.GetListOfGroups(c) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, message) } diff --git a/db/migrations/000001_init_schema.down.sql b/db/migrations/000001_init_schema.down.sql index 6782590..5eed2f9 100644 --- a/db/migrations/000001_init_schema.down.sql +++ b/db/migrations/000001_init_schema.down.sql @@ -1,3 +1,14 @@ -DROP TABLE users; +DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS groups; +DROP TABLE IF EXISTS threads; +DROP TABLE IF EXISTS thread_members; +DROP TABLE IF EXISTS gp_members; +DROP TABLE IF EXISTS dms; +DROP TABLE IF EXISTS messages; +DROP TABLE IF EXISTS schema_migrations; + DROP TYPE user_status; -DROP TYPE identity_document; \ No newline at end of file +DROP TYPE identity_document; +Drop TYPE IF EXISTS role_type; +Drop TYPE IF EXISTS visibility_enum; +Drop TYPE IF EXISTS chattype_enum; \ No newline at end of file diff --git a/db/migrations/000001_init_schema.up.sql b/db/migrations/000001_init_schema.up.sql index ee92b1e..2f56a97 100644 --- a/db/migrations/000001_init_schema.up.sql +++ b/db/migrations/000001_init_schema.up.sql @@ -1,12 +1,59 @@ +CREATE TABLE IF NOT EXISTS "users"( +"user_id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), +"fullname" VARCHAR(50) NOT NULL, +"username" VARCHAR(30) UNIQUE NOT NULL, +"email" VARCHAR(100) UNIQUE NOT NULL, +"created_at" TIMESTAMP DEFAULT now() +); +CREATE TABLE IF NOT EXISTS "dms"( +"dm_id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), +"user1_id" VARCHAR(36) NOT NULL REFERENCES users(user_id), +"user2_id" VARCHAR(36) NOT NULL REFERENCES users(user_id), +"created_at" TIMESTAMP DEFAULT now(), +UNIQUE (user1_id, user2_id) +); +CREATE TABLE IF NOT EXISTS "groups"( +"gp_id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), +"gp_name" VARCHAR(50) NOT NULL, +"created_by" VARCHAR(36) NOT NULL REFERENCES users(user_id), +"created_at" TIMESTAMP DEFAULT now() +); +CREATE TYPE role_type AS ENUM ('admin', 'member'); -CREATE TABLE "message" ( - "id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), - "thread" VARCHAR(36) NOT NULL, - "sender" VARCHAR(100) NOT NULL, - "content" TEXT NOT NULL, - "created_at" TIMESTAMP DEFAULT now() -); +CREATE TABLE IF NOT EXISTS "gp_members"( +"gp_members_id" VARCHAR(36) NOT NULL REFERENCES groups(gp_id) ON DELETE CASCADE, +"gp_members_user_id" VARCHAR(36) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, +"role" role_type DEFAULT 'member', +PRIMARY KEY (gp_members_id, gp_members_user_id) +); +CREATE TYPE visibility_enum AS ENUM ('public', 'restricted'); + +CREATE TABLE IF NOT EXISTS "threads"( +"thread_id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), +"created_by" VARCHAR(36) REFERENCES users(user_id) ON DELETE SET NULL, +"gp_id" VARCHAR(36) NOT NULL REFERENCES groups(gp_id) ON DELETE CASCADE, +"thread_name" VARCHAR(50) NOT NULL, +"visibility" visibility_enum DEFAULT 'public', +"created_at" TIMESTAMP DEFAULT now() +); + +CREATE TABLE IF NOT EXISTS "thread_members"( +"thread_members_id" VARCHAR(36) NOT NULL REFERENCES threads(thread_id) ON DELETE CASCADE, +"thread_members_user_id" VARCHAR(36) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE, +PRIMARY KEY (thread_members_id, thread_members_user_id) +); + +CREATE TYPE chattype_enum AS ENUM ('dms', 'groups', 'threads'); + +CREATE TABLE IF NOT EXISTS "messages" ( +"message_id" VARCHAR(36) PRIMARY KEY DEFAULT gen_random_uuid()::varchar(36), +"sender"VARCHAR(36) REFERENCES users(user_id) ON DELETE SET NULL, +"content" TEXT NOT NULL, +"created_at" TIMESTAMP DEFAULT now(), +"chat_id" VARCHAR(36) NOT NULL, +"chat_type" chattype_enum NOT NULL +); \ No newline at end of file diff --git a/db/query/message.sql b/db/query/message.sql index 1d34c69..3246b9d 100644 --- a/db/query/message.sql +++ b/db/query/message.sql @@ -1,13 +1,146 @@ --- name: CreateMessage :one -INSERT INTO message (thread, sender, content) -VALUES ($1, $2, $3) +/* adduser, + create group, + add group members, + start thread, + add thread members, + get dms messages(dm messages for a particular dm chat) , + get group messages(messages for a particular group excluding thread messages), + get thread messages(oder by group and thread name.show all messages for a particular thread, + the name of the thread and the group to which it belongs), + get user groups(groups user belongs to), get user threads(threads user is part of), + get list of groups and users who created the groups, + get list of threads and users who started the threads, + get number of thread belonging to a group, + get list of thread belonging to a group, + create a message, + list all groups */ + +-- name: AddUser :one +INSERT INTO users (fullname, username, email) +VALUES ($1, $2, $3) RETURNING *; + +-- name: CreateGroup :one +INSERT INTO groups (gp_name, created_by) +VALUES ($1, $2) +RETURNING *; + +-- name: AddGroupMember :one +INSERT INTO gp_members (gp_members_id, gp_members_user_id, role) +VALUES ($1, $2, COALESCE(NULLIF($3, '')::role_type, 'member'::role_type)) RETURNING *; --- name: GetMessageByID :one -SELECT * FROM message -WHERE id = $1; +-- name: StartThread :one +INSERT INTO threads (created_by, gp_id, thread_name, visibility) +VALUES ($1, $2, $3, COALESCE(NULLIF($4, '')::visibility_enum, 'public'::visibility_enum)) +RETURNING *; + +-- name: AddThreadMember :one +INSERT INTO thread_members (thread_members_id, thread_members_user_id) +VALUES ($1, $2) +RETURNING *; + +-- name: StartDm :one +INSERT INTO dms (user1_id, user2_id) +VALUES (LEAST($1, $2), GREATEST($1, $2)) +RETURNING *; + +-- name: GetDmMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +m.content As message, TO_CHAR(m.created_at, 'HH12:MI AM') As time_sent, DATE(m.created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'dms' AND m.chat_id = $1 +ORDER BY m.created_at DESC; + +-- name: GetGroupMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +m.content AS message, TO_CHAR(m.created_at, 'HH12:MI AM') As time_sent, DATE(m.created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'groups' AND m.chat_id = $1 +ORDER BY m.created_at DESC; + +-- name: GetThreadMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT gp_name FROM groups +WHERE gp_id = (SELECT gp_id FROM threads +WHERE thread_id = m.chat_id) +) AS group_name, + +(SELECT thread_name FROM threads +WHERE thread_id = m.chat_id +) AS thread_name, + +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +content As message, TO_CHAR(created_at, 'HH12:MI AM') As time_sent, DATE(created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'threads' AND m.chat_id = $1 +ORDER BY m.created_at DESC; + +-- name: GetUserGroups :many +SELECT gp_name FROM groups +WHERE gp_id IN (SELECT gp_members_id FROM gp_members + WHERE gp_members_user_id = $1); + +-- name: GetUserThreads :many +SELECT thread_name FROM threads +WHERE thread_id IN (SELECT thread_members_id FROM thread_members + WHERE thread_members_user_id = $1); + +-- name: GetGroupsUserCreated :many +SELECT g.gp_name As group_name, +(SELECT fullname FROM users +WHERE user_id = g.created_by +) As creators_name + +FROM groups g +WHERE g.created_by = $1 +ORDER BY creators_name; + +-- name: GetThreadsUserStarted :many +SELECT +(SELECT fullname FROM users +WHERE user_id = t.created_by +) As creators_name, + +t.thread_name FROM threads t +WHERE t.created_by = $1 +ORDER BY creators_name; + +-- name: GetGroupThreads :many +SELECT +(SELECT gp_name FROM groups +WHERE gp_id = t.gp_id +) As group_name, + +t.thread_name FROM threads t +WHERE t.gp_id = $1 +ORDER BY group_name, t.created_at; + +-- name: GetListOfGroups :many +SELECT gp_id, gp_name FROM groups +ORDER BY created_at DESC; + +-- name: CreateMessage :one +INSERT INTO messages (sender, content, chat_id, chat_type) +VALUES ($1, $2, $3, $4) +RETURNING *; + +-- name: GetMessageByID :one +SELECT m.message_id, m.chat_type AS message_from, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, --- name: GetMessagesByThread :many -SELECT * FROM message -WHERE thread = $1 -ORDER BY created_at DESC; \ No newline at end of file +m.content As message, TO_CHAR(created_at, 'HH12:MI AM') As time_sent, DATE(created_at) AS day_sent +FROM messages m +WHERE m.message_id = $1; \ No newline at end of file diff --git a/db/repo/db.go b/db/repo/db.go index 19f8234..71f6cab 100644 --- a/db/repo/db.go +++ b/db/repo/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package repo diff --git a/db/repo/message.sql.go b/db/repo/message.sql.go index ce74c13..d386507 100644 --- a/db/repo/message.sql.go +++ b/db/repo/message.sql.go @@ -1,78 +1,431 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 // source: message.sql package repo import ( "context" + + "github.com/jackc/pgx/v5/pgtype" ) +const addGroupMember = `-- name: AddGroupMember :one +INSERT INTO gp_members (gp_members_id, gp_members_user_id, role) +VALUES ($1, $2, COALESCE(NULLIF($3, '')::role_type, 'member'::role_type)) +RETURNING gp_members_id, gp_members_user_id, role +` + +type AddGroupMemberParams struct { + GpMembersID string `json:"gp_members_id"` + GpMembersUserID string `json:"gp_members_user_id"` + Column3 interface{} `json:"column_3"` +} + +func (q *Queries) AddGroupMember(ctx context.Context, arg AddGroupMemberParams) (GpMember, error) { + row := q.db.QueryRow(ctx, addGroupMember, arg.GpMembersID, arg.GpMembersUserID, arg.Column3) + var i GpMember + err := row.Scan(&i.GpMembersID, &i.GpMembersUserID, &i.Role) + return i, err +} + +const addThreadMember = `-- name: AddThreadMember :one +INSERT INTO thread_members (thread_members_id, thread_members_user_id) +VALUES ($1, $2) +RETURNING thread_members_id, thread_members_user_id +` + +type AddThreadMemberParams struct { + ThreadMembersID string `json:"thread_members_id"` + ThreadMembersUserID string `json:"thread_members_user_id"` +} + +func (q *Queries) AddThreadMember(ctx context.Context, arg AddThreadMemberParams) (ThreadMember, error) { + row := q.db.QueryRow(ctx, addThreadMember, arg.ThreadMembersID, arg.ThreadMembersUserID) + var i ThreadMember + err := row.Scan(&i.ThreadMembersID, &i.ThreadMembersUserID) + return i, err +} + +const addUser = `-- name: AddUser :one +/* adduser, + create group, + add group members, + start thread, + add thread members, + get dms messages(dm messages for a particular dm chat) , + get group messages(messages for a particular group excluding thread messages), + get thread messages(oder by group and thread name.show all messages for a particular thread, + the name of the thread and the group to which it belongs), + get user groups(groups user belongs to), get user threads(threads user is part of), + get list of groups and users who created the groups, + get list of threads and users who started the threads, + get number of thread belonging to a group, + get list of thread belonging to a group, + create a message, + list all groups */ + +INSERT INTO users (fullname, username, email) +VALUES ($1, $2, $3) RETURNING user_id, fullname, username, email, created_at +` + +type AddUserParams struct { + Fullname string `json:"fullname"` + Username string `json:"username"` + Email string `json:"email"` +} + +func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (User, error) { + row := q.db.QueryRow(ctx, addUser, arg.Fullname, arg.Username, arg.Email) + var i User + err := row.Scan( + &i.UserID, + &i.Fullname, + &i.Username, + &i.Email, + &i.CreatedAt, + ) + return i, err +} + +const createGroup = `-- name: CreateGroup :one +INSERT INTO groups (gp_name, created_by) +VALUES ($1, $2) +RETURNING gp_id, gp_name, created_by, created_at +` + +type CreateGroupParams struct { + GpName string `json:"gp_name"` + CreatedBy string `json:"created_by"` +} + +func (q *Queries) CreateGroup(ctx context.Context, arg CreateGroupParams) (Group, error) { + row := q.db.QueryRow(ctx, createGroup, arg.GpName, arg.CreatedBy) + var i Group + err := row.Scan( + &i.GpID, + &i.GpName, + &i.CreatedBy, + &i.CreatedAt, + ) + return i, err +} + const createMessage = `-- name: CreateMessage :one -INSERT INTO message (thread, sender, content) -VALUES ($1, $2, $3) -RETURNING id, thread, sender, content, created_at +INSERT INTO messages (sender, content, chat_id, chat_type) +VALUES ($1, $2, $3, $4) +RETURNING message_id, sender, content, created_at, chat_id, chat_type ` type CreateMessageParams struct { - Thread string `json:"thread"` - Sender string `json:"sender"` - Content string `json:"content"` + Sender *string `json:"sender"` + Content string `json:"content"` + ChatID string `json:"chat_id"` + ChatType ChattypeEnum `json:"chat_type"` } func (q *Queries) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) { - row := q.db.QueryRow(ctx, createMessage, arg.Thread, arg.Sender, arg.Content) + row := q.db.QueryRow(ctx, createMessage, + arg.Sender, + arg.Content, + arg.ChatID, + arg.ChatType, + ) var i Message err := row.Scan( - &i.ID, - &i.Thread, + &i.MessageID, &i.Sender, &i.Content, &i.CreatedAt, + &i.ChatID, + &i.ChatType, ) return i, err } +const getDmMessages = `-- name: GetDmMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +m.content As message, TO_CHAR(m.created_at, 'HH12:MI AM') As time_sent, DATE(m.created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'dms' AND m.chat_id = $1 +ORDER BY m.created_at DESC +` + +type GetDmMessagesRow struct { + ChatID string `json:"chat_id"` + ChatType ChattypeEnum `json:"chat_type"` + SenderName string `json:"sender_name"` + Message string `json:"message"` + TimeSent string `json:"time_sent"` + DaySent pgtype.Date `json:"day_sent"` +} + +func (q *Queries) GetDmMessages(ctx context.Context, chatID string) ([]GetDmMessagesRow, error) { + rows, err := q.db.Query(ctx, getDmMessages, chatID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetDmMessagesRow{} + for rows.Next() { + var i GetDmMessagesRow + if err := rows.Scan( + &i.ChatID, + &i.ChatType, + &i.SenderName, + &i.Message, + &i.TimeSent, + &i.DaySent, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getGroupMessages = `-- name: GetGroupMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +m.content AS message, TO_CHAR(m.created_at, 'HH12:MI AM') As time_sent, DATE(m.created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'groups' AND m.chat_id = $1 +ORDER BY m.created_at DESC +` + +type GetGroupMessagesRow struct { + ChatID string `json:"chat_id"` + ChatType ChattypeEnum `json:"chat_type"` + SenderName string `json:"sender_name"` + Message string `json:"message"` + TimeSent string `json:"time_sent"` + DaySent pgtype.Date `json:"day_sent"` +} + +func (q *Queries) GetGroupMessages(ctx context.Context, chatID string) ([]GetGroupMessagesRow, error) { + rows, err := q.db.Query(ctx, getGroupMessages, chatID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetGroupMessagesRow{} + for rows.Next() { + var i GetGroupMessagesRow + if err := rows.Scan( + &i.ChatID, + &i.ChatType, + &i.SenderName, + &i.Message, + &i.TimeSent, + &i.DaySent, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getGroupThreads = `-- name: GetGroupThreads :many +SELECT +(SELECT gp_name FROM groups +WHERE gp_id = t.gp_id +) As group_name, + +t.thread_name FROM threads t +WHERE t.gp_id = $1 +ORDER BY group_name, t.created_at +` + +type GetGroupThreadsRow struct { + GroupName string `json:"group_name"` + ThreadName string `json:"thread_name"` +} + +func (q *Queries) GetGroupThreads(ctx context.Context, gpID string) ([]GetGroupThreadsRow, error) { + rows, err := q.db.Query(ctx, getGroupThreads, gpID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetGroupThreadsRow{} + for rows.Next() { + var i GetGroupThreadsRow + if err := rows.Scan(&i.GroupName, &i.ThreadName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getGroupsUserCreated = `-- name: GetGroupsUserCreated :many +SELECT g.gp_name As group_name, +(SELECT fullname FROM users +WHERE user_id = g.created_by +) As creators_name + +FROM groups g +WHERE g.created_by = $1 +ORDER BY creators_name +` + +type GetGroupsUserCreatedRow struct { + GroupName string `json:"group_name"` + CreatorsName string `json:"creators_name"` +} + +func (q *Queries) GetGroupsUserCreated(ctx context.Context, createdBy string) ([]GetGroupsUserCreatedRow, error) { + rows, err := q.db.Query(ctx, getGroupsUserCreated, createdBy) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetGroupsUserCreatedRow{} + for rows.Next() { + var i GetGroupsUserCreatedRow + if err := rows.Scan(&i.GroupName, &i.CreatorsName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getListOfGroups = `-- name: GetListOfGroups :many +SELECT gp_id, gp_name FROM groups +ORDER BY created_at DESC +` + +type GetListOfGroupsRow struct { + GpID string `json:"gp_id"` + GpName string `json:"gp_name"` +} + +func (q *Queries) GetListOfGroups(ctx context.Context) ([]GetListOfGroupsRow, error) { + rows, err := q.db.Query(ctx, getListOfGroups) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetListOfGroupsRow{} + for rows.Next() { + var i GetListOfGroupsRow + if err := rows.Scan(&i.GpID, &i.GpName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getMessageByID = `-- name: GetMessageByID :one -SELECT id, thread, sender, content, created_at FROM message -WHERE id = $1 +SELECT m.message_id, m.chat_type AS message_from, +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +m.content As message, TO_CHAR(created_at, 'HH12:MI AM') As time_sent, DATE(created_at) AS day_sent +FROM messages m +WHERE m.message_id = $1 ` -func (q *Queries) GetMessageByID(ctx context.Context, id string) (Message, error) { - row := q.db.QueryRow(ctx, getMessageByID, id) - var i Message +type GetMessageByIDRow struct { + MessageID string `json:"message_id"` + MessageFrom ChattypeEnum `json:"message_from"` + SenderName string `json:"sender_name"` + Message string `json:"message"` + TimeSent string `json:"time_sent"` + DaySent pgtype.Date `json:"day_sent"` +} + +func (q *Queries) GetMessageByID(ctx context.Context, messageID string) (GetMessageByIDRow, error) { + row := q.db.QueryRow(ctx, getMessageByID, messageID) + var i GetMessageByIDRow err := row.Scan( - &i.ID, - &i.Thread, - &i.Sender, - &i.Content, - &i.CreatedAt, + &i.MessageID, + &i.MessageFrom, + &i.SenderName, + &i.Message, + &i.TimeSent, + &i.DaySent, ) return i, err } -const getMessagesByThread = `-- name: GetMessagesByThread :many -SELECT id, thread, sender, content, created_at FROM message -WHERE thread = $1 -ORDER BY created_at DESC +const getThreadMessages = `-- name: GetThreadMessages :many +SELECT m.chat_id, m.chat_type, +(SELECT gp_name FROM groups +WHERE gp_id = (SELECT gp_id FROM threads +WHERE thread_id = m.chat_id) +) AS group_name, + +(SELECT thread_name FROM threads +WHERE thread_id = m.chat_id +) AS thread_name, + +(SELECT fullname FROM users +WHERE user_id = m.sender +) AS sender_name, + +content As message, TO_CHAR(created_at, 'HH12:MI AM') As time_sent, DATE(created_at) AS day_sent +FROM messages m +WHERE m.chat_type = 'threads' AND m.chat_id = $1 +ORDER BY m.created_at DESC ` -func (q *Queries) GetMessagesByThread(ctx context.Context, thread string) ([]Message, error) { - rows, err := q.db.Query(ctx, getMessagesByThread, thread) +type GetThreadMessagesRow struct { + ChatID string `json:"chat_id"` + ChatType ChattypeEnum `json:"chat_type"` + GroupName string `json:"group_name"` + ThreadName string `json:"thread_name"` + SenderName string `json:"sender_name"` + Message string `json:"message"` + TimeSent string `json:"time_sent"` + DaySent pgtype.Date `json:"day_sent"` +} + +func (q *Queries) GetThreadMessages(ctx context.Context, chatID string) ([]GetThreadMessagesRow, error) { + rows, err := q.db.Query(ctx, getThreadMessages, chatID) if err != nil { return nil, err } defer rows.Close() - items := []Message{} + items := []GetThreadMessagesRow{} for rows.Next() { - var i Message + var i GetThreadMessagesRow if err := rows.Scan( - &i.ID, - &i.Thread, - &i.Sender, - &i.Content, - &i.CreatedAt, + &i.ChatID, + &i.ChatType, + &i.GroupName, + &i.ThreadName, + &i.SenderName, + &i.Message, + &i.TimeSent, + &i.DaySent, ); err != nil { return nil, err } @@ -83,3 +436,146 @@ func (q *Queries) GetMessagesByThread(ctx context.Context, thread string) ([]Mes } return items, nil } + +const getThreadsUserStarted = `-- name: GetThreadsUserStarted :many +SELECT +(SELECT fullname FROM users +WHERE user_id = t.created_by +) As creators_name, + +t.thread_name FROM threads t +WHERE t.created_by = $1 +ORDER BY creators_name +` + +type GetThreadsUserStartedRow struct { + CreatorsName string `json:"creators_name"` + ThreadName string `json:"thread_name"` +} + +func (q *Queries) GetThreadsUserStarted(ctx context.Context, createdBy *string) ([]GetThreadsUserStartedRow, error) { + rows, err := q.db.Query(ctx, getThreadsUserStarted, createdBy) + if err != nil { + return nil, err + } + defer rows.Close() + items := []GetThreadsUserStartedRow{} + for rows.Next() { + var i GetThreadsUserStartedRow + if err := rows.Scan(&i.CreatorsName, &i.ThreadName); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getUserGroups = `-- name: GetUserGroups :many +SELECT gp_name FROM groups +WHERE gp_id IN (SELECT gp_members_id FROM gp_members + WHERE gp_members_user_id = $1) +` + +func (q *Queries) GetUserGroups(ctx context.Context, gpMembersUserID string) ([]string, error) { + rows, err := q.db.Query(ctx, getUserGroups, gpMembersUserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []string{} + for rows.Next() { + var gp_name string + if err := rows.Scan(&gp_name); err != nil { + return nil, err + } + items = append(items, gp_name) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const getUserThreads = `-- name: GetUserThreads :many +SELECT thread_name FROM threads +WHERE thread_id IN (SELECT thread_members_id FROM thread_members + WHERE thread_members_user_id = $1) +` + +func (q *Queries) GetUserThreads(ctx context.Context, threadMembersUserID string) ([]string, error) { + rows, err := q.db.Query(ctx, getUserThreads, threadMembersUserID) + if err != nil { + return nil, err + } + defer rows.Close() + items := []string{} + for rows.Next() { + var thread_name string + if err := rows.Scan(&thread_name); err != nil { + return nil, err + } + items = append(items, thread_name) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const startDm = `-- name: StartDm :one +INSERT INTO dms (user1_id, user2_id) +VALUES (LEAST($1, $2), GREATEST($1, $2)) +RETURNING dm_id, user1_id, user2_id, created_at +` + +type StartDmParams struct { + Column1 interface{} `json:"column_1"` + Column2 interface{} `json:"column_2"` +} + +func (q *Queries) StartDm(ctx context.Context, arg StartDmParams) (Dm, error) { + row := q.db.QueryRow(ctx, startDm, arg.Column1, arg.Column2) + var i Dm + err := row.Scan( + &i.DmID, + &i.User1ID, + &i.User2ID, + &i.CreatedAt, + ) + return i, err +} + +const startThread = `-- name: StartThread :one +INSERT INTO threads (created_by, gp_id, thread_name, visibility) +VALUES ($1, $2, $3, COALESCE(NULLIF($4, '')::visibility_enum, 'public'::visibility_enum)) +RETURNING thread_id, created_by, gp_id, thread_name, visibility, created_at +` + +type StartThreadParams struct { + CreatedBy *string `json:"created_by"` + GpID string `json:"gp_id"` + ThreadName string `json:"thread_name"` + Column4 interface{} `json:"column_4"` +} + +func (q *Queries) StartThread(ctx context.Context, arg StartThreadParams) (Thread, error) { + row := q.db.QueryRow(ctx, startThread, + arg.CreatedBy, + arg.GpID, + arg.ThreadName, + arg.Column4, + ) + var i Thread + err := row.Scan( + &i.ThreadID, + &i.CreatedBy, + &i.GpID, + &i.ThreadName, + &i.Visibility, + &i.CreatedAt, + ) + return i, err +} diff --git a/db/repo/models.go b/db/repo/models.go index 92c72c8..e8ea52f 100644 --- a/db/repo/models.go +++ b/db/repo/models.go @@ -1,17 +1,190 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package repo import ( + "database/sql/driver" + "fmt" + "github.com/jackc/pgx/v5/pgtype" ) +type ChattypeEnum string + +const ( + ChattypeEnumDms ChattypeEnum = "dms" + ChattypeEnumGroups ChattypeEnum = "groups" + ChattypeEnumThreads ChattypeEnum = "threads" +) + +func (e *ChattypeEnum) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = ChattypeEnum(s) + case string: + *e = ChattypeEnum(s) + default: + return fmt.Errorf("unsupported scan type for ChattypeEnum: %T", src) + } + return nil +} + +type NullChattypeEnum struct { + ChattypeEnum ChattypeEnum `json:"chattype_enum"` + Valid bool `json:"valid"` // Valid is true if ChattypeEnum is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullChattypeEnum) Scan(value interface{}) error { + if value == nil { + ns.ChattypeEnum, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.ChattypeEnum.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullChattypeEnum) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.ChattypeEnum), nil +} + +type RoleType string + +const ( + RoleTypeAdmin RoleType = "admin" + RoleTypeMember RoleType = "member" +) + +func (e *RoleType) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = RoleType(s) + case string: + *e = RoleType(s) + default: + return fmt.Errorf("unsupported scan type for RoleType: %T", src) + } + return nil +} + +type NullRoleType struct { + RoleType RoleType `json:"role_type"` + Valid bool `json:"valid"` // Valid is true if RoleType is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullRoleType) Scan(value interface{}) error { + if value == nil { + ns.RoleType, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.RoleType.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullRoleType) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.RoleType), nil +} + +type VisibilityEnum string + +const ( + VisibilityEnumPublic VisibilityEnum = "public" + VisibilityEnumRestricted VisibilityEnum = "restricted" +) + +func (e *VisibilityEnum) Scan(src interface{}) error { + switch s := src.(type) { + case []byte: + *e = VisibilityEnum(s) + case string: + *e = VisibilityEnum(s) + default: + return fmt.Errorf("unsupported scan type for VisibilityEnum: %T", src) + } + return nil +} + +type NullVisibilityEnum struct { + VisibilityEnum VisibilityEnum `json:"visibility_enum"` + Valid bool `json:"valid"` // Valid is true if VisibilityEnum is not NULL +} + +// Scan implements the Scanner interface. +func (ns *NullVisibilityEnum) Scan(value interface{}) error { + if value == nil { + ns.VisibilityEnum, ns.Valid = "", false + return nil + } + ns.Valid = true + return ns.VisibilityEnum.Scan(value) +} + +// Value implements the driver Valuer interface. +func (ns NullVisibilityEnum) Value() (driver.Value, error) { + if !ns.Valid { + return nil, nil + } + return string(ns.VisibilityEnum), nil +} + +type Dm struct { + DmID string `json:"dm_id"` + User1ID string `json:"user1_id"` + User2ID string `json:"user2_id"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type GpMember struct { + GpMembersID string `json:"gp_members_id"` + GpMembersUserID string `json:"gp_members_user_id"` + Role NullRoleType `json:"role"` +} + +type Group struct { + GpID string `json:"gp_id"` + GpName string `json:"gp_name"` + CreatedBy string `json:"created_by"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + type Message struct { - ID string `json:"id"` - Thread string `json:"thread"` - Sender string `json:"sender"` + MessageID string `json:"message_id"` + Sender *string `json:"sender"` Content string `json:"content"` CreatedAt pgtype.Timestamp `json:"created_at"` + ChatID string `json:"chat_id"` + ChatType ChattypeEnum `json:"chat_type"` +} + +type Thread struct { + ThreadID string `json:"thread_id"` + CreatedBy *string `json:"created_by"` + GpID string `json:"gp_id"` + ThreadName string `json:"thread_name"` + Visibility NullVisibilityEnum `json:"visibility"` + CreatedAt pgtype.Timestamp `json:"created_at"` +} + +type ThreadMember struct { + ThreadMembersID string `json:"thread_members_id"` + ThreadMembersUserID string `json:"thread_members_user_id"` +} + +type User struct { + UserID string `json:"user_id"` + Fullname string `json:"fullname"` + Username string `json:"username"` + Email string `json:"email"` + CreatedAt pgtype.Timestamp `json:"created_at"` } diff --git a/db/repo/querier.go b/db/repo/querier.go index 5b0504e..345d18b 100644 --- a/db/repo/querier.go +++ b/db/repo/querier.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.27.0 +// sqlc v1.30.0 package repo @@ -9,9 +9,23 @@ import ( ) type Querier interface { + AddGroupMember(ctx context.Context, arg AddGroupMemberParams) (GpMember, error) + AddThreadMember(ctx context.Context, arg AddThreadMemberParams) (ThreadMember, error) + AddUser(ctx context.Context, arg AddUserParams) (User, error) + CreateGroup(ctx context.Context, arg CreateGroupParams) (Group, error) CreateMessage(ctx context.Context, arg CreateMessageParams) (Message, error) - GetMessageByID(ctx context.Context, id string) (Message, error) - GetMessagesByThread(ctx context.Context, thread string) ([]Message, error) + GetDmMessages(ctx context.Context, chatID string) ([]GetDmMessagesRow, error) + GetGroupMessages(ctx context.Context, chatID string) ([]GetGroupMessagesRow, error) + GetGroupThreads(ctx context.Context, gpID string) ([]GetGroupThreadsRow, error) + GetGroupsUserCreated(ctx context.Context, createdBy string) ([]GetGroupsUserCreatedRow, error) + GetListOfGroups(ctx context.Context) ([]GetListOfGroupsRow, error) + GetMessageByID(ctx context.Context, messageID string) (GetMessageByIDRow, error) + GetThreadMessages(ctx context.Context, chatID string) ([]GetThreadMessagesRow, error) + GetThreadsUserStarted(ctx context.Context, createdBy *string) ([]GetThreadsUserStartedRow, error) + GetUserGroups(ctx context.Context, gpMembersUserID string) ([]string, error) + GetUserThreads(ctx context.Context, threadMembersUserID string) ([]string, error) + StartDm(ctx context.Context, arg StartDmParams) (Dm, error) + StartThread(ctx context.Context, arg StartThreadParams) (Thread, error) } var _ Querier = (*Queries)(nil) diff --git a/go.mod b/go.mod index 044585d..cd1a85f 100644 --- a/go.mod +++ b/go.mod @@ -6,9 +6,9 @@ require ( github.com/ardanlabs/conf/v3 v3.4.0 github.com/gin-gonic/gin v1.10.0 github.com/golang-migrate/migrate/v4 v4.18.2 + github.com/google/uuid v1.6.0 github.com/jackc/pgx/v5 v5.7.4 github.com/joho/godotenv v1.5.1 - github.com/rs/zerolog v1.34.0 ) require ( @@ -31,7 +31,6 @@ require ( github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lib/pq v1.10.9 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/go.sum b/go.sum index cfed3a0..1008e29 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,7 @@ +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/ardanlabs/conf/v3 v3.4.0 h1:Qy7/doJjhsv7Lvzqd9tbvH8fAZ9jzqKtwnwcmZ+sxGs= github.com/ardanlabs/conf/v3 v3.4.0/go.mod h1:OIi6NK95fj8jKFPdZ/UmcPlY37JBg99hdP9o5XmNK9c= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= @@ -8,15 +12,33 @@ github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dhui/dktest v0.4.4 h1:+I4s6JRE1yGuqflzwqG+aIaMdgXIorCf5P98JnaAWa8= +github.com/dhui/dktest v0.4.4/go.mod h1:4+22R4lgsdAXrDyaH4Nqx2JEz2hLp49MqQmm9HLCQhM= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/docker/docker v27.2.0+incompatible h1:Rk9nIVdfH3+Vz4cyI/uhbINhEZ/oLmc+CBXmH6fbNk4= +github.com/docker/docker v27.2.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= +github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= @@ -25,10 +47,15 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-migrate/migrate/v4 v4.18.2 h1:2VSCMz7x7mjyTXx3m2zPokOY82LTRgxK1yQYKo6wWQ8= github.com/golang-migrate/migrate/v4 v4.18.2/go.mod h1:2CM6tJvn2kqPXwnXO/d3rAQYiyoIm180VsO8PRX6Rpk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -50,28 +77,39 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02 github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug= +github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM= github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0= -github.com/rs/zerolog v1.34.0 h1:k43nTLIwcTVQAncfCw4KZ2VY6ukYoZaBPNOE8txlOeY= -github.com/rs/zerolog v1.34.0/go.mod h1:bJsvje4Z08ROH4Nhs5iH600c3IkWhwp44iRc54W6wYQ= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -82,11 +120,20 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.29.0 h1:PdomN/Al4q/lN6iBJEN3AwPvUiHPMlt93c8bqTG5Llw= +go.opentelemetry.io/otel v1.29.0/go.mod h1:N/WtXPs1CNCUEx+Agz5uouwCba+i+bJGFicT8SR4NP8= +go.opentelemetry.io/otel/metric v1.29.0 h1:vPf/HFWTNkPu1aYeIsc98l4ktOQaL6LeSoeV2g+8YLc= +go.opentelemetry.io/otel/metric v1.29.0/go.mod h1:auu/QWieFVWx+DmQOUMgj0F8LHWdgalxXqvp7BII/W8= +go.opentelemetry.io/otel/trace v1.29.0 h1:J/8ZNK4XgR7a21DZUAsbF8pZ5Jcw1VhACmnYt39JTi4= +go.opentelemetry.io/otel/trace v1.29.0/go.mod h1:eHl3w0sp3paPkYstJOmAimxhiFXPg+MMTlEh3nsQgWQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= @@ -94,25 +141,23 @@ golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= -golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= -golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= -google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= -google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/postman/collections/message_db endpoint tests.postman_collection.json b/postman/collections/message_db endpoint tests.postman_collection.json new file mode 100644 index 0000000..a57aa2d --- /dev/null +++ b/postman/collections/message_db endpoint tests.postman_collection.json @@ -0,0 +1,383 @@ +{ + "info": { + "_postman_id": "56a4bdfd-f417-4724-abb9-a83fcaf40582", + "name": "message_db endpoint tests", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "create message", + "id": "18b6f3eb-c48c-4067-af75-994250721ef9", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"sender\": \"340c4b80-9f1b-4a03-a0ae-1934b74fc6ef\",\n \"content\": \"Hey Bob, how are you?\",\n \"chat_id\": \"d81ada4e-4d36-4d6b-b1c1-40aea6fcbeaa\",\n \"chat_type\": \"dms\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/message", + "host": [ + "{{baseURL}}" + ], + "path": [ + "message" + ] + }, + "description": "Create Message\n\nSummary \nCreates a new message in a direct message (DM) chat.\n\nMethod & URL \nPOST [http://localhost:8085/message](http://localhost:8085/message)\n\nRequest Body (application/json) \n{ \n\"sender\": \"string (UUID)\" – Required. The unique ID of the user sending the message. \n\"content\": \"string\" – Required. The message text content. \n\"chat_id\": \"string (UUID)\" – Required. The unique chat/conversation ID the message belongs to. \n\"chat_type\": \"string\" – Required. The chat type; for DMs use \"dms\", for GROUPS use \"groups\" and for THREADS use \"threads\". \n}" + } + }, + { + "name": "create users", + "id": "bfdf26b2-252a-4d34-9aa6-e3b6cb681088", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"fullname\": \"Alice Johnson\",\n \"username\": \"alice\",\n \"email\": \"alice@example.com\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/user", + "host": [ + "{{baseURL}}" + ], + "path": [ + "user" + ] + }, + "description": "Purpose \nCreate a new user in the local user service.\n\nEndpoint\n\n- Method: POST\n \n- URL: [http://localhost:8085/use](http://localhost:8085/user)\n \n\nRequest Body \nProvide JSON with the following fields:\n\n- fullname (string): Full legal name of the user. Example: \"Alice Johnson\"\n \n- username (string): Unique username (slug-safe). Example: \"alice\"\n \n- email (string): Valid email address. Example: \"[alice@example.com](https://mailto:alice@example.com)\"\n \n\nExample\n\n``` json\n{\n \"fullname\": \"Alice Johnson\",\n \"username\": \"alice\",\n \"email\": \"alice@example.com\"\n}\n\n ```\n\nSample Success Response (Illustrative)\n\n``` json\n{\n \"id\": \"b123f5c0-9d2e-4c61-9a6a-8a2d1c0f3a4e\",\n \"fullname\": \"Alice Johnson\",\n \"username\": \"alice\",\n \"email\": \"alice@example.com\",\n \"createdAt\": \"2025-01-01T12:34:56.000Z\"\n}\n\n ```" + } + }, + { + "name": "create group", + "id": "88c2af6a-0a71-499d-bc6d-6b42aa20d3f0", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"gp_name\": \"Developers\",\n \"created_by\": \"340c4b80-9f1b-4a03-a0ae-1934b74fc6ef\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/group", + "host": [ + "{{baseURL}}" + ], + "path": [ + "group" + ] + }, + "description": "Purpose \nCreate a new group.\n\nMethod and URL \nPOST [http://localhost:8085/group](http://localhost:8085/group) \nSchema: \n{ \n\"gp_name\": string, // Required. Human-readable group name. 1-64 chars. \n\"created_by\": string, // Required. UUID (v4) of the user creating the group. \n}\n\nValidation Rules\n\n- gp_name: required, non-empty, max 64 characters.\n \n- created_by: required, must be a valid UUID string." + } + }, + { + "name": "add members to group by id", + "id": "344d398d-3373-4c8f-bded-cc64a34fbc52", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"gp_members_id\": \"2a2370d1-d733-4f1f-8488-c2fd290deb75\",\n \"gp_members_user_id\": \"19628a12-b749-490b-8895-ecf3e5e580b8\",\n \"column_3\": \"member\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/group/member", + "host": [ + "{{baseURL}}" + ], + "path": [ + "group", + "member" + ] + }, + "description": "Summary \nAdd a user as a member of a group. Creates or updates the membership record identified by gp_members_id and associates it with a user (gp_members_user_id) with the specified role.\n\nEndpoint \nPOST [http://localhost:8085/group/member](http://localhost:8085/group/member)\n\nMethod \nPOST\n\nSchema \n{ \n\"gp_members_id\": \"string (UUID)\", \n\"gp_members_user_id\": \"string (UUID)\", \n\"role\": \"string\" \n}\n\nField descriptions\n\n- gp_members_id (required): Unique identifier of the group membership record. Typically a UUID generated by the server or client to ensure idempotency.\n \n- gp_members_user_id (required): Unique identifier of the user to add to the group.\n \n- role (required): Role to assign to the user within the group. Common values: \"member\", \"admin\"\n \n\ncolumn_3 in request body represents user role in a group" + } + }, + { + "name": "start thread", + "id": "ff4708c4-53cf-4cb9-8fd3-3a5434131720", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"thread_name\": \"Backend Discussion\",\n \"created_by\": \"340c4b80-9f1b-4a03-a0ae-1934b74fc6ef\",\n \"gp_id\": \"2a2370d1-d733-4f1f-8488-c2fd290deb75\",\n \"column_4\": \"public\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/thread", + "host": [ + "{{baseURL}}" + ], + "path": [ + "thread" + ] + }, + "description": "Summary \nCreate a new discussion thread within a group.\n\nEndpoint \nPOST http://localhost:8085/thread\n\nDescription \nCreates a thread resource that users can post messages to. The request requires a valid group ID and the user ID of the creator.\n\nPath and Variables\n\n- baseURL (variable): Base API URL provided by your selected environment.\n \n\nRequest Body \nContent-Type: application/json \nSchema \n{ \n\"thread_name\": string, // Required. Human‑readable title of the thread. \n\"created_by\": UUID string, // Required. The user ID creating the thread. \n\"gp_id\": UUID string, // Required. The group ID the thread belongs to. \n\"column_4\": string // Optional. Default may be public. thread can be either public or restricted \n}\n\ncolumn_4 represents the visibility of the thread" + } + }, + { + "name": "Add member to thread", + "id": "d964b48a-69b8-4963-bd5a-1586b96e977d", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"thread_members_id\": \"7e037f31-bfbd-4026-80d8-ba441eb5e033\",\n \"thread_members_user_id\": \"19628a12-b749-490b-8895-ecf3e5e580b8\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/thread/member", + "host": [ + "{{baseURL}}" + ], + "path": [ + "thread", + "member" + ] + }, + "description": "Purpose \nAdds a user as a member to an existing thread.\n\nEndpoint \nPOST {{baseURL}}/thread/member\n\nMethod \nPOST\n\nPath Params \nNone. The endpoint does not take path parameters.\n\nRequest Body \nContent-Type: application/json (raw) \nSchema: \n{ \n\"thread_members_id\": \"string (UUID)\", // Unique identifier for the thread membership record to create or upsert \n\"thread_members_user_id\": \"string (UUID)\" // The user to be added to the thread \n} \nField details:\n\n- thread_members_id (required): UUID for the new membership entry. If your backend auto-generates IDs, provide rules accordingly.\n \n- thread_members_user_id (required): UUID of the user to add as a member of the thread." + } + }, + { + "name": "start dm", + "id": "ea82f792-e64f-47b2-877d-f11cd9005069", + "request": { + "method": "POST", + "body": { + "mode": "raw", + "raw": "{\n \"column_1\": \"340c4b80-9f1b-4a03-a0ae-1934b74fc6ef\",\n \"column_2\": \"19628a12-b749-490b-8895-ecf3e5e580b8\"\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{baseURL}}/dm", + "host": [ + "{{baseURL}}" + ], + "path": [ + "dm" + ] + }, + "description": "Summary: Initiates a direct message (DM) thread between two users.\n\nPurpose: Use this endpoint to create a new DM conversation for a user pair.\n\nMethod & URL: POST [http://localhost:8085/dm](http://localhost:8085/dm)\n\nRequest Body Schema (JSON): \n{ \n\"column_1\": string, // UUID of the first participant \n\"column_2\": string // UUID of the second participant \n}\n\ncolumn_1 and columnS_2 represent user1_id and user2a_id respectively\n\nValidation Notes:\n\n- user1_id and user2_id must be valid UUIDs.\n \n- They should represent two distinct users." + } + }, + { + "name": "get message by id", + "id": "423f22f6-cbf8-402d-a028-f5d52bbca9f1", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/message/9e4de47b-b719-45c7-a0c7-c1b6e4d1c694", + "host": [ + "{{baseURL}}" + ], + "path": [ + "message", + "9e4de47b-b719-45c7-a0c7-c1b6e4d1c694" + ] + }, + "description": "Purpose \nRetrieve a single message resource by its unique message_id.\n\nHTTP\n\n- Method: GET\n \n- URL: {{baseURL}}/message/:message_id\n \n\nPath parameters\n\n- message_id (string, UUID): The unique identifier of the message to fetch. Example: 5f3618ce-56f3-4751-8f30-c76ff0a5f256" + } + }, + { + "name": "get dm messages", + "id": "d930540c-f037-440f-8c2a-61d88ea67e4e", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/message/dm/d81ada4e-4d36-4d6b-b1c1-40aea6fcbeaa", + "host": [ + "{{baseURL}}" + ], + "path": [ + "message", + "dm", + "d81ada4e-4d36-4d6b-b1c1-40aea6fcbeaa" + ] + }, + "description": "Overview \nFetches all direct messages (DMs) for a specific chat (conversation) identified by chat_id.\n\nMethod & URL \nGET [http://localhost:8085/message/dm/:chat_id](http://localhost:8085/message/dm/:chat_id)\n\nPath variables\n\n- chat_id (string, required): The unique identifier of the DM conversation to retrieve messages for.\n \n\nUsage notes\n\n- Ensure chat_id belongs to a DM conversation; other conversation types may be unsupported by this endpoint." + } + }, + { + "name": "get thread messages", + "id": "6d98c304-249b-451f-bb22-776eafd4fdbb", + "request": { + "method": "GET", + "url": { + "raw": "" + }, + "description": "Purpose \nRetrieve all messages in a specific thread within a group.\n\nEndpoint \nGET {{baseURL}}/message/thread/:threadId\n\nPath Parameters\n\n- threadId (UUID, required): The unique identifier of the message thread. Example: 7016044e-20ab-4edd-afa0-84b7a8de2370" + } + }, + { + "name": "get group messages", + "id": "6cc23236-889e-4382-99ed-c36df5c9f95b", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/message/group/2a2370d1-d733-4f1f-8488-c2fd290deb75", + "host": [ + "{{baseURL}}" + ], + "path": [ + "message", + "group", + "2a2370d1-d733-4f1f-8488-c2fd290deb75" + ] + }, + "description": "Retrieves all messages for a specific group chat.\n\n- Method: GET\n \n- URL: {{baseURL}}/message/group/:chat_id\n \n- Path params:\n \n - chat_id (UUID): The unique identifier of the group chat.\n \n\nSuccessful response (200): An array of message objects. Example:\n\n``` json\n[{\n \"chat_id\": \"2a2370d1-d733-4f1f-8488-c2fd290deb75\",\n \"chat_type\": \"groups\",\n \"sender_name\": \"Bob Smith\",\n \"message\": \"Welcome to the Developers group!\",\n \"time_sent\": \"04:26 PM\",\n \"day_sent\": \"2025-11-29\"\n}]\n\n ```\n\nNotes:\n\n- Ensure chat_id corresponds to an existing group." + } + }, + { + "name": "get groups user is part of", + "id": "5402d1c3-012c-449f-8a5d-a35122706e6e", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/user/group/19628a12-b749-490b-8895-ecf3e5e580b8", + "host": [ + "{{baseURL}}" + ], + "path": [ + "user", + "group", + "19628a12-b749-490b-8895-ecf3e5e580b8" + ] + }, + "description": "Purpose \nRetrieves the list of group names that a specific user is a member of.\n\nHTTP\n\n- Method: GET\n \n- URL: [http://localhost:8085/user/group/:userId](http://localhost:8085/user/group/:userId)\n \n\nPath parameters\n\n- userId (required): UUID of the user whose group memberships you want to fetch. Example: 19628a12-b749-490b-8895-ecf3e5e580b8" + } + }, + { + "name": "get threads user started", + "id": "37368a5a-cd4e-4705-8e7a-49ae8a8b02c2", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/user/thread/creator/340c4b80-9f1b-4a03-a0ae-1934b74fc6ef", + "host": [ + "{{baseURL}}" + ], + "path": [ + "user", + "thread", + "creator", + "340c4b80-9f1b-4a03-a0ae-1934b74fc6ef" + ] + }, + "description": "Purpose: Retrieve threads created by a specific user.\n\nEndpoint & Method:\n\nGET http://localhost:8085/user/thread/creator/:creatorId\n\nPath params:\n\n- creatorId (UUID) — The user ID of the thread creator. Example: 340c4b80-9f1b-4a03-a0ae-1934b74fc6ef" + } + }, + { + "name": "get groups user created", + "id": "1a68c191-efaa-4c33-b540-21b5869b0c77", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/user/group/creator/19628a12-b749-490b-8895-ecf3e5e580b8", + "host": [ + "{{baseURL}}" + ], + "path": [ + "user", + "group", + "creator", + "19628a12-b749-490b-8895-ecf3e5e580b8" + ] + }, + "description": "Summary \nRetrieves the list of groups created by a specific user (creator).\n\nHTTP \nGET [http://localhost:8085/user/group/creator/:creatorId](http://localhost:8085/user/group/creator/:creatorId)\n\nPath parameters\n\n- creatorId (UUID, required): The unique identifier of the user whose created groups are being requested. In the current example, the value is 19628a12-b749-490b-8895-ecf3e5e580b8." + } + }, + { + "name": "get threads user is part of", + "id": "0501f4f6-a195-4830-bc75-0ba070857ffa", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/user/thread/3cf6feb5-346f-43fa-81c8-d264e8cd73c3", + "host": [ + "{{baseURL}}" + ], + "path": [ + "user", + "thread", + "3cf6feb5-346f-43fa-81c8-d264e8cd73c3" + ] + }, + "description": "Title: Get threads a user is part of\n\nOverview: \nReturns the list of thread names that the specified user is a member of.\n\nEndpoint: \nGET http://localhost:8085/user/thread/:userId\n\n- Path parameter userId: UUID of the user whose threads you want to retrieve. Example: 19628a12-b749-490b-8895-ecf3e5e580b8" + } + }, + { + "name": "get threads that belong to a group", + "id": "6458cb46-cdca-4969-91c2-d4d513b125bf", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/group/thread/2a2370d1-d733-4f1f-8488-c2fd290deb75", + "host": [ + "{{baseURL}}" + ], + "path": [ + "group", + "thread", + "2a2370d1-d733-4f1f-8488-c2fd290deb75" + ] + }, + "description": "Summary \nRetrieve all threads that belong to a specific group.\n\nPurpose \nUse this endpoint to list discussion threads for a given group so clients can display available conversations before fetching individual thread messages.\n\nHTTP \nGET http://localhost:8085/group/thread/:group_id\n\nPath parameters\n\n- group_id (UUID, required): The unique identifier of the group whose threads you want to list. Example: 2a2370d1-d733-4f1f-8488-c2fd290deb75" + } + }, + { + "name": "get list of groups", + "id": "d3c8ca28-7837-46e7-8e6f-66925b45cace", + "request": { + "method": "GET", + "url": { + "raw": "{{baseURL}}/group/group-list", + "host": [ + "{{baseURL}}" + ], + "path": [ + "group", + "group-list" + ] + }, + "description": "purpose: Gets list of all groups that exist\n\nGET [http://lcalhost:8085/group/group-list](http://lcalhost:8085/group/group-list)" + } + } + ], + "variable": [ + { + "id": "ec160b9d-136e-48b7-89eb-301fc4bbe300", + "key": "baseURL", + "value": "", + "type": "default" + } + ] +} \ No newline at end of file diff --git a/postman/globals/workspace.postman_globals.json b/postman/globals/workspace.postman_globals.json new file mode 100644 index 0000000..616ceef --- /dev/null +++ b/postman/globals/workspace.postman_globals.json @@ -0,0 +1,7 @@ +{ + "id": "9c95207c-cffd-49e3-860a-6a04c8001b9c", + "name": "Globals", + "values": [], + "_postman_variable_scope": "globals", + "_postman_exported_at": "2025-12-01T10:36:58.669Z" +} \ No newline at end of file