feat: implement wiki and wiki content management system including database schemas, DTOs, and API endpoints
All checks were successful
Build and Release / release (push) Successful in 1m33s
All checks were successful
Build and Release / release (push) Successful in 1m33s
This commit is contained in:
@@ -5,7 +5,6 @@ CREATE TABLE IF NOT EXISTS wikis (
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
title TEXT,
|
||||
slug TEXT,
|
||||
content TEXT,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
|
||||
1
db/migrations/000016_wiki_content.down.sql
Normal file
1
db/migrations/000016_wiki_content.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS wiki_content;
|
||||
11
db/migrations/000016_wiki_content.up.sql
Normal file
11
db/migrations/000016_wiki_content.up.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE IF NOT EXISTS wiki_content (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||
wiki_id UUID NOT NULL REFERENCES wikis(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX idx_wiki_content_wiki_id ON wiki_content(wiki_id);
|
||||
CREATE INDEX idx_wiki_content_created_at ON wiki_content(created_at DESC);
|
||||
@@ -1,8 +1,8 @@
|
||||
-- name: CreateWiki :one
|
||||
INSERT INTO wikis (
|
||||
id, title, slug, content, project_id
|
||||
id, title, slug, project_id
|
||||
) VALUES (
|
||||
COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3, $4
|
||||
COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
@@ -16,7 +16,6 @@ UPDATE wikis
|
||||
SET
|
||||
title = COALESCE(sqlc.narg('title'), title),
|
||||
slug = COALESCE(sqlc.narg('slug'), slug),
|
||||
content = COALESCE(sqlc.narg('content'), content),
|
||||
project_id = COALESCE(sqlc.narg('project_id'), project_id)
|
||||
WHERE id = sqlc.arg('id') AND is_deleted = false
|
||||
RETURNING *;
|
||||
@@ -93,3 +92,38 @@ WHERE slug = $1 AND is_deleted = false;
|
||||
|
||||
-- name: GetWikisBySlugs :many
|
||||
SELECT * FROM wikis WHERE slug = ANY($1::text[]) AND is_deleted = false;
|
||||
|
||||
-- name: CreateWikiContent :one
|
||||
INSERT INTO wiki_content (
|
||||
id, wiki_id, title, content
|
||||
) VALUES (
|
||||
COALESCE(sqlc.narg('id')::uuid, uuidv7()), $1, $2, $3
|
||||
)
|
||||
RETURNING *;
|
||||
|
||||
-- name: GetWikiContentCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = $1;
|
||||
|
||||
-- name: GetWikiContentById :one
|
||||
SELECT *
|
||||
FROM wiki_content
|
||||
WHERE id = $1 AND is_deleted = false;
|
||||
|
||||
-- name: GetWikiContentByIDs :many
|
||||
SELECT *
|
||||
FROM wiki_content
|
||||
WHERE id = ANY($1::uuid[]) AND is_deleted = false;
|
||||
|
||||
-- name: GetWikiContentByWikiID :many
|
||||
SELECT id, title, created_at
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = $1 AND is_deleted = false
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
-- name: GetWikiContentByWikiIDs :many
|
||||
SELECT id, wiki_id, title, created_at
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = ANY($1::uuid[]) AND is_deleted = false
|
||||
ORDER BY created_at DESC;
|
||||
|
||||
@@ -102,12 +102,20 @@ CREATE TABLE IF NOT EXISTS wikis (
|
||||
project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
title TEXT,
|
||||
slug TEXT UNIQUE,
|
||||
content TEXT,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS wiki_content (
|
||||
id UUID PRIMARY KEY DEFAULT uuidv7(),
|
||||
wiki_id UUID NOT NULL REFERENCES wikis(id) ON DELETE CASCADE,
|
||||
title TEXT NOT NULL,
|
||||
content TEXT,
|
||||
is_deleted BOOLEAN NOT NULL DEFAULT false,
|
||||
created_at TIMESTAMPTZ DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS entity_wikis (
|
||||
entity_id UUID REFERENCES entities(id) ON DELETE CASCADE,
|
||||
wiki_id UUID REFERENCES wikis(id) ON DELETE CASCADE,
|
||||
|
||||
@@ -136,3 +136,30 @@ func (h *WikiController) SearchWikis(c fiber.Ctx) error {
|
||||
})
|
||||
}
|
||||
|
||||
// GetWikiContentById handles fetching a single wiki content by ID.
|
||||
// @Summary Get wiki content by ID
|
||||
// @Description Get detailed information about a specific wiki content version
|
||||
// @Tags Wikis
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Wiki Content ID"
|
||||
// @Success 200 {object} response.CommonResponse
|
||||
// @Failure 500 {object} response.CommonResponse
|
||||
// @Router /wikis/content/{id} [get]
|
||||
func (h *WikiController) GetWikiContentById(c fiber.Ctx) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
id := c.Params("id")
|
||||
res, err := h.service.GetWikiContentByID(ctx, id)
|
||||
if err != nil {
|
||||
return c.Status(err.Code).JSON(response.CommonResponse{
|
||||
Status: false,
|
||||
Message: err.Message,
|
||||
})
|
||||
}
|
||||
return c.Status(fiber.StatusOK).JSON(response.CommonResponse{
|
||||
Status: true,
|
||||
Data: res,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,13 +4,27 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type WikiResponse struct {
|
||||
type WikiContentSample struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
ProjectID string `json:"project_id"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
Title string `json:"title"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
type WikiResponse struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Slug string `json:"slug,omitempty"`
|
||||
ContentSample []WikiContentSample `json:"content_sample,omitempty"`
|
||||
ProjectID string `json:"project_id"`
|
||||
IsDeleted bool `json:"is_deleted,omitempty"`
|
||||
CreatedAt *time.Time `json:"created_at,omitempty"`
|
||||
UpdatedAt *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
type WikiContentResponse struct {
|
||||
ID string `json:"id"`
|
||||
WikiID string `json:"wiki_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -234,8 +234,16 @@ type Wiki struct {
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
Title pgtype.Text `json:"title"`
|
||||
Slug pgtype.Text `json:"slug"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
UpdatedAt pgtype.Timestamptz `json:"updated_at"`
|
||||
}
|
||||
|
||||
type WikiContent struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WikiID pgtype.UUID `json:"wiki_id"`
|
||||
Title string `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -68,17 +68,16 @@ func (q *Queries) CreateEntityWikis(ctx context.Context, arg CreateEntityWikisPa
|
||||
|
||||
const createWiki = `-- name: CreateWiki :one
|
||||
INSERT INTO wikis (
|
||||
id, title, slug, content, project_id
|
||||
id, title, slug, project_id
|
||||
) VALUES (
|
||||
COALESCE($5::uuid, uuidv7()), $1, $2, $3, $4
|
||||
COALESCE($4::uuid, uuidv7()), $1, $2, $3
|
||||
)
|
||||
RETURNING id, project_id, title, slug, content, is_deleted, created_at, updated_at
|
||||
RETURNING id, project_id, title, slug, is_deleted, created_at, updated_at
|
||||
`
|
||||
|
||||
type CreateWikiParams struct {
|
||||
Title pgtype.Text `json:"title"`
|
||||
Slug pgtype.Text `json:"slug"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
}
|
||||
@@ -87,7 +86,6 @@ func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, e
|
||||
row := q.db.QueryRow(ctx, createWiki,
|
||||
arg.Title,
|
||||
arg.Slug,
|
||||
arg.Content,
|
||||
arg.ProjectID,
|
||||
arg.ID,
|
||||
)
|
||||
@@ -97,7 +95,6 @@ func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, e
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -105,6 +102,41 @@ func (q *Queries) CreateWiki(ctx context.Context, arg CreateWikiParams) (Wiki, e
|
||||
return i, err
|
||||
}
|
||||
|
||||
const createWikiContent = `-- name: CreateWikiContent :one
|
||||
INSERT INTO wiki_content (
|
||||
id, wiki_id, title, content
|
||||
) VALUES (
|
||||
COALESCE($4::uuid, uuidv7()), $1, $2, $3
|
||||
)
|
||||
RETURNING id, wiki_id, title, content, is_deleted, created_at
|
||||
`
|
||||
|
||||
type CreateWikiContentParams struct {
|
||||
WikiID pgtype.UUID `json:"wiki_id"`
|
||||
Title string `json:"title"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateWikiContent(ctx context.Context, arg CreateWikiContentParams) (WikiContent, error) {
|
||||
row := q.db.QueryRow(ctx, createWikiContent,
|
||||
arg.WikiID,
|
||||
arg.Title,
|
||||
arg.Content,
|
||||
arg.ID,
|
||||
)
|
||||
var i WikiContent
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.WikiID,
|
||||
&i.Title,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteEntityWiki = `-- name: DeleteEntityWiki :exec
|
||||
DELETE FROM entity_wikis
|
||||
WHERE entity_id = $1 AND wiki_id = $2
|
||||
@@ -154,7 +186,7 @@ func (q *Queries) DeleteWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID)
|
||||
}
|
||||
|
||||
const getWikiById = `-- name: GetWikiById :one
|
||||
SELECT id, project_id, title, slug, content, is_deleted, created_at, updated_at
|
||||
SELECT id, project_id, title, slug, is_deleted, created_at, updated_at
|
||||
FROM wikis
|
||||
WHERE id = $1 AND is_deleted = false
|
||||
`
|
||||
@@ -167,7 +199,6 @@ func (q *Queries) GetWikiById(ctx context.Context, id pgtype.UUID) (Wiki, error)
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -176,7 +207,7 @@ func (q *Queries) GetWikiById(ctx context.Context, id pgtype.UUID) (Wiki, error)
|
||||
}
|
||||
|
||||
const getWikiBySlug = `-- name: GetWikiBySlug :one
|
||||
SELECT id, project_id, title, slug, content, is_deleted, created_at, updated_at
|
||||
SELECT id, project_id, title, slug, is_deleted, created_at, updated_at
|
||||
FROM wikis
|
||||
WHERE slug = $1 AND is_deleted = false
|
||||
`
|
||||
@@ -189,7 +220,6 @@ func (q *Queries) GetWikiBySlug(ctx context.Context, slug pgtype.Text) (Wiki, er
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -197,8 +227,146 @@ func (q *Queries) GetWikiBySlug(ctx context.Context, slug pgtype.Text) (Wiki, er
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWikiContentByIDs = `-- name: GetWikiContentByIDs :many
|
||||
SELECT id, wiki_id, title, content, is_deleted, created_at
|
||||
FROM wiki_content
|
||||
WHERE id = ANY($1::uuid[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikiContentByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]WikiContent, error) {
|
||||
rows, err := q.db.Query(ctx, getWikiContentByIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []WikiContent{}
|
||||
for rows.Next() {
|
||||
var i WikiContent
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WikiID,
|
||||
&i.Title,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWikiContentById = `-- name: GetWikiContentById :one
|
||||
SELECT id, wiki_id, title, content, is_deleted, created_at
|
||||
FROM wiki_content
|
||||
WHERE id = $1 AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikiContentById(ctx context.Context, id pgtype.UUID) (WikiContent, error) {
|
||||
row := q.db.QueryRow(ctx, getWikiContentById, id)
|
||||
var i WikiContent
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.WikiID,
|
||||
&i.Title,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getWikiContentByWikiID = `-- name: GetWikiContentByWikiID :many
|
||||
SELECT id, title, created_at
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = $1 AND is_deleted = false
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
type GetWikiContentByWikiIDRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetWikiContentByWikiID(ctx context.Context, wikiID pgtype.UUID) ([]GetWikiContentByWikiIDRow, error) {
|
||||
rows, err := q.db.Query(ctx, getWikiContentByWikiID, wikiID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetWikiContentByWikiIDRow{}
|
||||
for rows.Next() {
|
||||
var i GetWikiContentByWikiIDRow
|
||||
if err := rows.Scan(&i.ID, &i.Title, &i.CreatedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWikiContentByWikiIDs = `-- name: GetWikiContentByWikiIDs :many
|
||||
SELECT id, wiki_id, title, created_at
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = ANY($1::uuid[]) AND is_deleted = false
|
||||
ORDER BY created_at DESC
|
||||
`
|
||||
|
||||
type GetWikiContentByWikiIDsRow struct {
|
||||
ID pgtype.UUID `json:"id"`
|
||||
WikiID pgtype.UUID `json:"wiki_id"`
|
||||
Title string `json:"title"`
|
||||
CreatedAt pgtype.Timestamptz `json:"created_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetWikiContentByWikiIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]GetWikiContentByWikiIDsRow, error) {
|
||||
rows, err := q.db.Query(ctx, getWikiContentByWikiIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetWikiContentByWikiIDsRow{}
|
||||
for rows.Next() {
|
||||
var i GetWikiContentByWikiIDsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.WikiID,
|
||||
&i.Title,
|
||||
&i.CreatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getWikiContentCount = `-- name: GetWikiContentCount :one
|
||||
SELECT COUNT(*)
|
||||
FROM wiki_content
|
||||
WHERE wiki_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikiContentCount(ctx context.Context, wikiID pgtype.UUID) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, getWikiContentCount, wikiID)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const getWikisByIDs = `-- name: GetWikisByIDs :many
|
||||
SELECT id, project_id, title, slug, content, is_deleted, created_at, updated_at FROM wikis WHERE id = ANY($1::uuid[]) AND is_deleted = false
|
||||
SELECT id, project_id, title, slug, is_deleted, created_at, updated_at FROM wikis WHERE id = ANY($1::uuid[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]Wiki, error) {
|
||||
@@ -215,7 +383,6 @@ func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -231,7 +398,7 @@ func (q *Queries) GetWikisByIDs(ctx context.Context, dollar_1 []pgtype.UUID) ([]
|
||||
}
|
||||
|
||||
const getWikisByProjectId = `-- name: GetWikisByProjectId :many
|
||||
SELECT id, project_id, title, slug, content, is_deleted, created_at, updated_at
|
||||
SELECT id, project_id, title, slug, is_deleted, created_at, updated_at
|
||||
FROM wikis
|
||||
WHERE project_id = $1 AND is_deleted = false
|
||||
`
|
||||
@@ -250,7 +417,6 @@ func (q *Queries) GetWikisByProjectId(ctx context.Context, projectID pgtype.UUID
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -266,7 +432,7 @@ func (q *Queries) GetWikisByProjectId(ctx context.Context, projectID pgtype.UUID
|
||||
}
|
||||
|
||||
const getWikisBySlugs = `-- name: GetWikisBySlugs :many
|
||||
SELECT id, project_id, title, slug, content, is_deleted, created_at, updated_at FROM wikis WHERE slug = ANY($1::text[]) AND is_deleted = false
|
||||
SELECT id, project_id, title, slug, is_deleted, created_at, updated_at FROM wikis WHERE slug = ANY($1::text[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikisBySlugs(ctx context.Context, dollar_1 []string) ([]Wiki, error) {
|
||||
@@ -283,7 +449,6 @@ func (q *Queries) GetWikisBySlugs(ctx context.Context, dollar_1 []string) ([]Wik
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -299,7 +464,7 @@ func (q *Queries) GetWikisBySlugs(ctx context.Context, dollar_1 []string) ([]Wik
|
||||
}
|
||||
|
||||
const searchWikis = `-- name: SearchWikis :many
|
||||
SELECT w.id, w.project_id, w.title, w.slug, w.content, w.is_deleted, w.created_at, w.updated_at
|
||||
SELECT w.id, w.project_id, w.title, w.slug, w.is_deleted, w.created_at, w.updated_at
|
||||
FROM wikis w
|
||||
WHERE w.is_deleted = false
|
||||
AND ($1::uuid IS NULL OR w.project_id = $1::uuid)
|
||||
@@ -347,7 +512,6 @@ func (q *Queries) SearchWikis(ctx context.Context, arg SearchWikisParams) ([]Wik
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
@@ -367,16 +531,14 @@ UPDATE wikis
|
||||
SET
|
||||
title = COALESCE($1, title),
|
||||
slug = COALESCE($2, slug),
|
||||
content = COALESCE($3, content),
|
||||
project_id = COALESCE($4, project_id)
|
||||
WHERE id = $5 AND is_deleted = false
|
||||
RETURNING id, project_id, title, slug, content, is_deleted, created_at, updated_at
|
||||
project_id = COALESCE($3, project_id)
|
||||
WHERE id = $4 AND is_deleted = false
|
||||
RETURNING id, project_id, title, slug, is_deleted, created_at, updated_at
|
||||
`
|
||||
|
||||
type UpdateWikiParams struct {
|
||||
Title pgtype.Text `json:"title"`
|
||||
Slug pgtype.Text `json:"slug"`
|
||||
Content pgtype.Text `json:"content"`
|
||||
ProjectID pgtype.UUID `json:"project_id"`
|
||||
ID pgtype.UUID `json:"id"`
|
||||
}
|
||||
@@ -385,7 +547,6 @@ func (q *Queries) UpdateWiki(ctx context.Context, arg UpdateWikiParams) (Wiki, e
|
||||
row := q.db.QueryRow(ctx, updateWiki,
|
||||
arg.Title,
|
||||
arg.Slug,
|
||||
arg.Content,
|
||||
arg.ProjectID,
|
||||
arg.ID,
|
||||
)
|
||||
@@ -395,7 +556,6 @@ func (q *Queries) UpdateWiki(ctx context.Context, arg UpdateWikiParams) (Wiki, e
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
|
||||
@@ -5,30 +5,46 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type WikiEntity struct {
|
||||
type WikiContentSample struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
Content string `json:"content"`
|
||||
ProjectID string `json:"project_id"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type WikiEntity struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Slug string `json:"slug"`
|
||||
ContentSample []WikiContentSample `json:"content_sample"`
|
||||
ProjectID string `json:"project_id"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (w *WikiEntity) ToResponse() *response.WikiResponse {
|
||||
if w == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var contentSample []response.WikiContentSample
|
||||
for _, c := range w.ContentSample {
|
||||
contentSample = append(contentSample, response.WikiContentSample{
|
||||
ID: c.ID,
|
||||
Title: c.Title,
|
||||
CreatedAt: c.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return &response.WikiResponse{
|
||||
ID: w.ID,
|
||||
Title: w.Title,
|
||||
Slug: w.Slug,
|
||||
Content: w.Content,
|
||||
ProjectID: w.ProjectID,
|
||||
IsDeleted: w.IsDeleted,
|
||||
CreatedAt: w.CreatedAt,
|
||||
UpdatedAt: w.UpdatedAt,
|
||||
ID: w.ID,
|
||||
Title: w.Title,
|
||||
Slug: w.Slug,
|
||||
ContentSample: contentSample,
|
||||
ProjectID: w.ProjectID,
|
||||
IsDeleted: w.IsDeleted,
|
||||
CreatedAt: w.CreatedAt,
|
||||
UpdatedAt: w.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,3 +61,12 @@ func WikisEntityToResponse(ws []*WikiEntity) []*response.WikiResponse {
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
type WikiContentEntity struct {
|
||||
ID string `json:"id"`
|
||||
WikiID string `json:"wiki_id"`
|
||||
Title string `json:"title"`
|
||||
Content string `json:"content"`
|
||||
IsDeleted bool `json:"is_deleted"`
|
||||
CreatedAt *time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ type WikiRepository interface {
|
||||
BulkDeleteEntityWikisByWikiID(ctx context.Context, wikiID pgtype.UUID) error
|
||||
DeleteEntityWiki(ctx context.Context, entityID pgtype.UUID, wikiID pgtype.UUID) error
|
||||
DeleteEntityWikisByProjectID(ctx context.Context, projectID pgtype.UUID) error
|
||||
CreateContent(ctx context.Context, params sqlc.CreateWikiContentParams) (*models.WikiContentEntity, error)
|
||||
GetContentCountByWikiID(ctx context.Context, wikiID pgtype.UUID) (int64, error)
|
||||
GetContentByID(ctx context.Context, id pgtype.UUID) (*models.WikiContentEntity, error)
|
||||
GetContentByIDs(ctx context.Context, ids []string) ([]*models.WikiContentEntity, error)
|
||||
WithTx(tx pgx.Tx) WikiRepository
|
||||
}
|
||||
|
||||
@@ -93,7 +97,6 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string)
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -101,6 +104,20 @@ func (r *wikiRepository) getByIDsWithFallback(ctx context.Context, ids []string)
|
||||
}
|
||||
dbMap[item.ID] = &item
|
||||
}
|
||||
|
||||
samples, err := r.q.GetWikiContentByWikiIDs(ctx, missingPgIds)
|
||||
if err == nil {
|
||||
for _, sample := range samples {
|
||||
wikiID := convert.UUIDToString(sample.WikiID)
|
||||
if item, ok := dbMap[wikiID]; ok {
|
||||
item.ContentSample = append(item.ContentSample, models.WikiContentSample{
|
||||
ID: convert.UUIDToString(sample.ID),
|
||||
Title: sample.Title,
|
||||
CreatedAt: convert.TimeToPtr(sample.CreatedAt),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,12 +164,24 @@ func (r *wikiRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.W
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||
}
|
||||
|
||||
// Fetch content samples
|
||||
samples, err := r.q.GetWikiContentByWikiID(ctx, row.ID)
|
||||
if err == nil {
|
||||
for _, sample := range samples {
|
||||
wiki.ContentSample = append(wiki.ContentSample, models.WikiContentSample{
|
||||
ID: convert.UUIDToString(sample.ID),
|
||||
Title: sample.Title,
|
||||
CreatedAt: convert.TimeToPtr(sample.CreatedAt),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
_ = r.c.Set(ctx, cacheId, wiki, constants.NormalCacheDuration)
|
||||
|
||||
return &wiki, nil
|
||||
@@ -178,7 +207,6 @@ func (r *wikiRepository) Search(ctx context.Context, params sqlc.SearchWikisPara
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -209,7 +237,6 @@ func (r *wikiRepository) Create(ctx context.Context, params sqlc.CreateWikiParam
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -228,7 +255,6 @@ func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParam
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -285,7 +311,6 @@ func (r *wikiRepository) GetByProjectID(ctx context.Context, projectID pgtype.UU
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -350,7 +375,6 @@ func (r *wikiRepository) GetBySlug(ctx context.Context, slug string) (*models.Wi
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -390,7 +414,6 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Title: convert.TextToString(row.Title),
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
@@ -421,3 +444,117 @@ func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*mod
|
||||
|
||||
return wikis, nil
|
||||
}
|
||||
|
||||
func (r *wikiRepository) CreateContent(ctx context.Context, params sqlc.CreateWikiContentParams) (*models.WikiContentEntity, error) {
|
||||
row, err := r.q.CreateWikiContent(ctx, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &models.WikiContentEntity{
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
WikiID: convert.UUIDToString(row.WikiID),
|
||||
Title: row.Title,
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *wikiRepository) GetContentCountByWikiID(ctx context.Context, wikiID pgtype.UUID) (int64, error) {
|
||||
return r.q.GetWikiContentCount(ctx, wikiID)
|
||||
}
|
||||
|
||||
func (r *wikiRepository) getContentByIDsWithFallback(ctx context.Context, ids []string) ([]*models.WikiContentEntity, error) {
|
||||
if len(ids) == 0 {
|
||||
return []*models.WikiContentEntity{}, nil
|
||||
}
|
||||
keys := make([]string, len(ids))
|
||||
for i, id := range ids {
|
||||
keys[i] = fmt.Sprintf("wiki_content:id:%s", id)
|
||||
}
|
||||
raws := r.c.MGet(ctx, keys...)
|
||||
|
||||
var contents []*models.WikiContentEntity
|
||||
missingToCache := make(map[string]any)
|
||||
|
||||
var missingPgIds []pgtype.UUID
|
||||
for i, b := range raws {
|
||||
if len(b) == 0 {
|
||||
pgId := pgtype.UUID{}
|
||||
err := pgId.Scan(ids[i])
|
||||
if err == nil {
|
||||
missingPgIds = append(missingPgIds, pgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbMap := make(map[string]*models.WikiContentEntity)
|
||||
if len(missingPgIds) > 0 {
|
||||
dbRows, err := r.q.GetWikiContentByIDs(ctx, missingPgIds)
|
||||
if err == nil {
|
||||
for _, row := range dbRows {
|
||||
item := models.WikiContentEntity{
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
WikiID: convert.UUIDToString(row.WikiID),
|
||||
Title: row.Title,
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
}
|
||||
dbMap[item.ID] = &item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) > 0 {
|
||||
var u models.WikiContentEntity
|
||||
if err := json.Unmarshal(b, &u); err == nil {
|
||||
contents = append(contents, &u)
|
||||
}
|
||||
} else {
|
||||
if item, ok := dbMap[ids[i]]; ok {
|
||||
contents = append(contents, item)
|
||||
missingToCache[keys[i]] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
|
||||
return contents, nil
|
||||
}
|
||||
|
||||
func (r *wikiRepository) GetContentByID(ctx context.Context, id pgtype.UUID) (*models.WikiContentEntity, error) {
|
||||
cacheId := fmt.Sprintf("wiki_content:id:%s", convert.UUIDToString(id))
|
||||
var content models.WikiContentEntity
|
||||
err := r.c.Get(ctx, cacheId, &content)
|
||||
if err == nil {
|
||||
_ = r.c.Set(ctx, cacheId, content, constants.NormalCacheDuration)
|
||||
return &content, nil
|
||||
}
|
||||
|
||||
row, err := r.q.GetWikiContentById(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content = models.WikiContentEntity{
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
WikiID: convert.UUIDToString(row.WikiID),
|
||||
Title: row.Title,
|
||||
Content: convert.TextToString(row.Content),
|
||||
IsDeleted: row.IsDeleted,
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
}
|
||||
_ = r.c.Set(ctx, cacheId, content, constants.NormalCacheDuration)
|
||||
|
||||
return &content, nil
|
||||
}
|
||||
|
||||
func (r *wikiRepository) GetContentByIDs(ctx context.Context, ids []string) ([]*models.WikiContentEntity, error) {
|
||||
return r.getContentByIDsWithFallback(ctx, ids)
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ func WikiRoutes(router fiber.Router, wikiController *controllers.WikiController)
|
||||
wiki.Get("/", wikiController.SearchWikis)
|
||||
wiki.Get("/slug/exists", wikiController.IsExistWikiSlug)
|
||||
wiki.Get("/slug/:slug", wikiController.GetWikiBySlug)
|
||||
wiki.Get("/content/:id", wikiController.GetWikiContentById)
|
||||
wiki.Get("/:id", wikiController.GetWikiById)
|
||||
}
|
||||
|
||||
|
||||
@@ -512,12 +512,29 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
ID: wikiUUID,
|
||||
Title: convert.StringToText(wiki.Title),
|
||||
Slug: convert.PtrToText(wiki.Slug),
|
||||
Content: convert.StringToText(wiki.Doc),
|
||||
ProjectID: projectUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update wiki: "+err.Error())
|
||||
}
|
||||
|
||||
count, err := s.wikiRepo.GetContentCountByWikiID(ctx, wikiUUID)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wiki content count: "+err.Error())
|
||||
}
|
||||
versionTitle := fmt.Sprintf("Version %d", count+1)
|
||||
|
||||
_, err = wikiRepo.CreateContent(ctx, sqlc.CreateWikiContentParams{
|
||||
WikiID: wikiUUID,
|
||||
Title: versionTitle,
|
||||
Content: convert.StringToText(wiki.Doc),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error())
|
||||
}
|
||||
|
||||
_ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug))
|
||||
|
||||
newWikis = append(newWikis, snapshotData.Wikis[i])
|
||||
|
||||
} else if wiki.Source == "inline" {
|
||||
@@ -525,12 +542,23 @@ func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewer
|
||||
ID: wikiUUID,
|
||||
Title: convert.StringToText(wiki.Title),
|
||||
Slug: convert.PtrToText(wiki.Slug),
|
||||
Content: convert.StringToText(wiki.Doc),
|
||||
ProjectID: projectUUID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki: "+err.Error())
|
||||
}
|
||||
|
||||
_, err = wikiRepo.CreateContent(ctx, sqlc.CreateWikiContentParams{
|
||||
WikiID: wikiUUID,
|
||||
Title: "Version 1",
|
||||
Content: convert.StringToText(wiki.Doc),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error())
|
||||
}
|
||||
|
||||
_ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug))
|
||||
|
||||
newWikis = append(newWikis, snapshotData.Wikis[i])
|
||||
|
||||
} else if wiki.Source == "ref" {
|
||||
|
||||
@@ -19,6 +19,7 @@ type WikiService interface {
|
||||
GetWikiBySlug(ctx context.Context, slug string) (*response.WikiResponse, *fiber.Error)
|
||||
IsExistWikiSlug(ctx context.Context, slug string) (bool, *fiber.Error)
|
||||
SearchWikis(ctx context.Context, req *request.SearchWikiDto) ([]*response.WikiResponse, *fiber.Error)
|
||||
GetWikiContentByID(ctx context.Context, id string) (*response.WikiContentResponse, *fiber.Error)
|
||||
}
|
||||
|
||||
type wikiService struct {
|
||||
@@ -107,3 +108,22 @@ func (s *wikiService) SearchWikis(ctx context.Context, req *request.SearchWikiDt
|
||||
|
||||
return models.WikisEntityToResponse(wikis), nil
|
||||
}
|
||||
|
||||
func (s *wikiService) GetWikiContentByID(ctx context.Context, id string) (*response.WikiContentResponse, *fiber.Error) {
|
||||
contentId, err := convert.StringToUUID(id)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid content ID format")
|
||||
}
|
||||
content, err := s.wikiRepo.GetContentByID(ctx, contentId)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusNotFound, "Wiki content not found")
|
||||
}
|
||||
|
||||
return &response.WikiContentResponse{
|
||||
ID: content.ID,
|
||||
WikiID: content.WikiID,
|
||||
Title: content.Title,
|
||||
Content: content.Content,
|
||||
CreatedAt: content.CreatedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user