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

This commit is contained in:
2026-05-11 11:02:57 +07:00
parent 8cee6b6622
commit 2873e42eab
14 changed files with 536 additions and 63 deletions

View File

@@ -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)
}