Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ DB_PORT=5432
DB_Name=messages
DB_TLS_DISABLED=true

MIGRATIONS_PATH="./db/migrations"
MIGRATIONS_PATH="../../db/migrations"
122 changes: 108 additions & 14 deletions api/api.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package api

import (
"database/sql"
"errors"
"net/http"

"github.com/Iknite-Space/sqlc-example-api/db/repo"
"github.com/Iknite-Space/sqlc-example-api/helper"
"github.com/gin-gonic/gin"
)

Expand All @@ -23,29 +26,63 @@ func (h *MessageHandler) WireHttpHandler() http.Handler {
r.Use(gin.CustomRecovery(func(c *gin.Context, _ any) {
c.String(http.StatusInternalServerError, "Internal Server Error: panic")
c.AbortWithStatus(http.StatusInternalServerError)
}))
})) //prevents the server from crashing if an error occurs in any route

r.POST("/thread", h.handleCreateThread)
r.POST("/message", h.handleCreateMessage)
r.GET("/message/:id", h.handleGetMessage)
r.GET("/thread/:id/messages", h.handleGetThreadMessages)
r.GET("/thread/messages/:threadId", h.handleGetThreadMessages)
r.DELETE("/message/:id", h.handleDeleteMessageById)
r.DELETE("/thread/:threadId/messages", h.handleDeleteMessageByThreadId)
r.PATCH("/message", h.handleUpdateMessage)

return r
}

type CreateThreadParams struct {
Title string `json:"title"`
}

func (h *MessageHandler) handleCreateThread(c *gin.Context) {
var req CreateThreadParams
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

thread, err := h.querier.CreateThread(c, req.Title)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, thread)
}

func (h *MessageHandler) handleCreateMessage(c *gin.Context) {
var req repo.CreateMessageParams
err := c.ShouldBindBodyWithJSON(&req)
if err != nil {
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

//first check whether the thread exist
_, err := h.querier.GetThreadById(c, req.ThreadID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
c.JSON(http.StatusBadRequest, gin.H{"error": "Thread not found"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": "Server error"})
return
}

//now we proceed to create the message
message, err := h.querier.CreateMessage(c, req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, message)
}

Expand All @@ -66,21 +103,78 @@ func (h *MessageHandler) handleGetMessage(c *gin.Context) {
}

func (h *MessageHandler) handleGetThreadMessages(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "id is required"})
id := c.Param("threadId")
intVal, err := helper.GetParamAsInt32(id)

if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}

messages, err := h.querier.GetMessagesByThread(c, id)
messages, err := h.querier.GetMessagesByThread(c, intVal)
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,
})
if len(messages) == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "No messages found for this thread"})
}

c.JSON(http.StatusOK, messages)
}

func (h *MessageHandler) handleUpdateMessage(c *gin.Context) {
var req repo.UpdateMessageParams
if err := c.ShouldBindBodyWithJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

if err := h.querier.UpdateMessage(c, req); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"success": "Message updated successfully"})
}

func (h *MessageHandler) handleDeleteMessageById(c *gin.Context) {
id := c.Param("id")

_, err := h.querier.DeleteMessageById(c, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
c.JSON(http.StatusNotFound, gin.H{"error": "Message not found"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "Message deleted successfully"})

}

func (h *MessageHandler) handleDeleteMessageByThreadId(c *gin.Context) {
id := c.Param("threadId")

intId, err := helper.GetParamAsInt32(id)
if err != nil {
c.JSON(http.StatusBadRequest, err.Error())
return
}

_, err = h.querier.DeleteMessageByThreadId(c, intId)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
c.JSON(http.StatusNotFound, gin.H{"error": "Message not found"})
return
}
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

c.JSON(http.StatusOK, gin.H{"message": "Message deleted successfully"})

}
15 changes: 15 additions & 0 deletions db/migrations/000002_init_thread_and_fk.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- create thread table
CREATE TABLE IF NOT EXISTS thread (
id SERIAL PRIMARY KEY,
title VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ DEFAULT now()
);

-- modify table message
ALTER TABLE message DROP COLUMN thread;

ALTER TABLE message ADD COLUMN thread_id INT NOT NULL;

-- add foreign key contrain
ALTER TABLE message ADD CONSTRAINT fk_thread_id
FOREIGN KEY (thread_id) REFERENCES thread(id) ON DELETE CASCADE;
2 changes: 2 additions & 0 deletions db/migrations/000003_init_drop_sender.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- drop the sender column since the thread.title is the sender in question
ALTER TABLE message DROP COLUMN sender;
61 changes: 57 additions & 4 deletions db/query/message.sql
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
-- -- name: CreateMessage :one
-- INSERT INTO message (thread, sender, content)
-- VALUES ($1, $2, $3)
-- RETURNING *;

-- -- name: GetMessageByID :one
-- SELECT * FROM message
-- WHERE id = $1;

-- -- name: GetMessagesByThread :many
-- SELECT * FROM message
-- WHERE thread = $1
-- ORDER BY created_at DESC;

-- -- name: DeleteMessage :exec
-- DELETE FROM message WHERE id = $1;

-- -- name: UpdateMessage :exec
-- UPDATE message
-- SET content = $2
-- WHERE id = $1
-- RETURNING *;

-- -- name: CreateThread :one
-- INSERT INTO thread (title)
-- VALUES ($1)
-- RETURNING *;

-- -- name: DeleteAll :exec
-- DELETE FROM message;

-- name: CreateThread :one
INSERT INTO thread (title)
VALUES ($1)
RETURNING *;

-- name: CreateMessage :one
INSERT INTO message (thread, sender, content)
VALUES ($1, $2, $3)
INSERT INTO message (content,thread_id)
VALUES ($1, $2)
RETURNING *;

-- name: GetMessageByID :one
Expand All @@ -9,5 +45,22 @@ WHERE id = $1;

-- name: GetMessagesByThread :many
SELECT * FROM message
WHERE thread = $1
ORDER BY created_at DESC;
WHERE thread_id = $1
ORDER BY created_at DESC;

-- name: DeleteMessageById :one
DELETE FROM message WHERE id = $1
RETURNING id;

-- name: DeleteMessageByThreadId :one
DELETE FROM message WHERE thread_id = $1
RETURNING thread_id;

-- name: UpdateMessage :exec
UPDATE message
SET content = $2
WHERE id = $1
RETURNING *;

-- name: GetThreadById :one
SELECT * FROM thread WHERE id = $1;
Loading