feat: implement wiki and entity repositories and services with corresponding SQL queries and generation
All checks were successful
Build and Release / release (push) Successful in 1m30s
All checks were successful
Build and Release / release (push) Successful in 1m30s
This commit is contained in:
@@ -156,6 +156,42 @@ func (q *Queries) GetEntitiesByProjectId(ctx context.Context, projectID pgtype.U
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getEntitiesBySlugs = `-- name: GetEntitiesBySlugs :many
|
||||
SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at FROM entities WHERE slug = ANY($1::text[]) AND is_deleted = false
|
||||
`
|
||||
|
||||
func (q *Queries) GetEntitiesBySlugs(ctx context.Context, dollar_1 []string) ([]Entity, error) {
|
||||
rows, err := q.db.Query(ctx, getEntitiesBySlugs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Entity{}
|
||||
for rows.Next() {
|
||||
var i Entity
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.Name,
|
||||
&i.Slug,
|
||||
&i.Description,
|
||||
&i.Status,
|
||||
&i.TimeStart,
|
||||
&i.TimeEnd,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getEntityById = `-- name: GetEntityById :one
|
||||
SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at
|
||||
FROM entities
|
||||
|
||||
@@ -265,6 +265,39 @@ func (q *Queries) GetWikisByProjectId(ctx context.Context, projectID pgtype.UUID
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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
|
||||
`
|
||||
|
||||
func (q *Queries) GetWikisBySlugs(ctx context.Context, dollar_1 []string) ([]Wiki, error) {
|
||||
rows, err := q.db.Query(ctx, getWikisBySlugs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Wiki{}
|
||||
for rows.Next() {
|
||||
var i Wiki
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.ProjectID,
|
||||
&i.Title,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.IsDeleted,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
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
|
||||
FROM wikis w
|
||||
|
||||
@@ -20,6 +20,7 @@ type EntityRepository interface {
|
||||
GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error)
|
||||
GetByIDs(ctx context.Context, ids []string) ([]*models.EntityEntity, error)
|
||||
GetBySlug(ctx context.Context, slug string) (*models.EntityEntity, error)
|
||||
GetBySlugs(ctx context.Context, slugs []string) ([]*models.EntityEntity, error)
|
||||
Search(ctx context.Context, params sqlc.SearchEntitiesParams) ([]*models.EntityEntity, error)
|
||||
Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error)
|
||||
Update(ctx context.Context, params sqlc.UpdateEntityParams) (*models.EntityEntity, error)
|
||||
@@ -349,3 +350,67 @@ func (r *entityRepository) GetBySlug(ctx context.Context, slug string) (*models.
|
||||
|
||||
return &entity, nil
|
||||
}
|
||||
|
||||
func (r *entityRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*models.EntityEntity, error) {
|
||||
if len(slugs) == 0 {
|
||||
return []*models.EntityEntity{}, nil
|
||||
}
|
||||
keys := make([]string, len(slugs))
|
||||
for i, slug := range slugs {
|
||||
keys[i] = fmt.Sprintf("entity:slug:%s", slug)
|
||||
}
|
||||
raws := r.c.MGet(ctx, keys...)
|
||||
|
||||
var entities []*models.EntityEntity
|
||||
missingToCache := make(map[string]any)
|
||||
var missingSlugs []string
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) == 0 {
|
||||
missingSlugs = append(missingSlugs, slugs[i])
|
||||
}
|
||||
}
|
||||
|
||||
dbMap := make(map[string]*models.EntityEntity)
|
||||
if len(missingSlugs) > 0 {
|
||||
dbRows, err := r.q.GetEntitiesBySlugs(ctx, missingSlugs)
|
||||
if err == nil {
|
||||
for _, row := range dbRows {
|
||||
item := models.EntityEntity{
|
||||
ID: convert.UUIDToString(row.ID),
|
||||
Name: row.Name,
|
||||
Slug: convert.TextToString(row.Slug),
|
||||
Description: convert.TextToString(row.Description),
|
||||
ProjectID: convert.UUIDToString(row.ProjectID),
|
||||
Status: convert.Int2ToInt16Ptr(row.Status),
|
||||
TimeStart: convert.Int4ToPtr(row.TimeStart),
|
||||
TimeEnd: convert.Int4ToPtr(row.TimeEnd),
|
||||
IsDeleted: row.IsDeleted,
|
||||
CreatedAt: convert.TimeToPtr(row.CreatedAt),
|
||||
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
|
||||
}
|
||||
dbMap[item.Slug] = &item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) > 0 {
|
||||
var u models.EntityEntity
|
||||
if err := json.Unmarshal(b, &u); err == nil {
|
||||
entities = append(entities, &u)
|
||||
}
|
||||
} else {
|
||||
if item, ok := dbMap[slugs[i]]; ok {
|
||||
entities = append(entities, item)
|
||||
missingToCache[keys[i]] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
|
||||
return entities, nil
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ type WikiRepository interface {
|
||||
GetByID(ctx context.Context, id pgtype.UUID) (*models.WikiEntity, error)
|
||||
GetByIDs(ctx context.Context, ids []string) ([]*models.WikiEntity, error)
|
||||
GetBySlug(ctx context.Context, slug string) (*models.WikiEntity, error)
|
||||
GetBySlugs(ctx context.Context, slugs []string) ([]*models.WikiEntity, error)
|
||||
Search(ctx context.Context, params sqlc.SearchWikisParams) ([]*models.WikiEntity, error)
|
||||
Create(ctx context.Context, params sqlc.CreateWikiParams) (*models.WikiEntity, error)
|
||||
Update(ctx context.Context, params sqlc.UpdateWikiParams) (*models.WikiEntity, error)
|
||||
@@ -359,3 +360,64 @@ func (r *wikiRepository) GetBySlug(ctx context.Context, slug string) (*models.Wi
|
||||
|
||||
return &wiki, nil
|
||||
}
|
||||
|
||||
func (r *wikiRepository) GetBySlugs(ctx context.Context, slugs []string) ([]*models.WikiEntity, error) {
|
||||
if len(slugs) == 0 {
|
||||
return []*models.WikiEntity{}, nil
|
||||
}
|
||||
keys := make([]string, len(slugs))
|
||||
for i, slug := range slugs {
|
||||
keys[i] = fmt.Sprintf("wiki:slug:%s", slug)
|
||||
}
|
||||
raws := r.c.MGet(ctx, keys...)
|
||||
|
||||
var wikis []*models.WikiEntity
|
||||
missingToCache := make(map[string]any)
|
||||
var missingSlugs []string
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) == 0 {
|
||||
missingSlugs = append(missingSlugs, slugs[i])
|
||||
}
|
||||
}
|
||||
|
||||
dbMap := make(map[string]*models.WikiEntity)
|
||||
if len(missingSlugs) > 0 {
|
||||
dbRows, err := r.q.GetWikisBySlugs(ctx, missingSlugs)
|
||||
if err == nil {
|
||||
for _, row := range dbRows {
|
||||
item := models.WikiEntity{
|
||||
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),
|
||||
}
|
||||
dbMap[item.Slug] = &item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i, b := range raws {
|
||||
if len(b) > 0 {
|
||||
var u models.WikiEntity
|
||||
if err := json.Unmarshal(b, &u); err == nil {
|
||||
wikis = append(wikis, &u)
|
||||
}
|
||||
} else {
|
||||
if item, ok := dbMap[slugs[i]]; ok {
|
||||
wikis = append(wikis, item)
|
||||
missingToCache[keys[i]] = item
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingToCache) > 0 {
|
||||
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
|
||||
}
|
||||
|
||||
return wikis, nil
|
||||
}
|
||||
|
||||
@@ -2,9 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"history-api/internal/dtos/request"
|
||||
"history-api/internal/dtos/response"
|
||||
@@ -104,26 +102,48 @@ func (s *submissionService) CreateSubmission(ctx context.Context, userID string,
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
|
||||
}
|
||||
|
||||
var entitySlugs []string
|
||||
entitySlugToID := make(map[string]string)
|
||||
for _, entity := range snapshotData.Entities {
|
||||
if entity.Slug != nil {
|
||||
exist, err := s.entityRepo.GetBySlug(ctx, *entity.Slug)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get entity")
|
||||
}
|
||||
if exist != nil {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Entity %s already exists", *entity.Slug))
|
||||
if entity.Source == "inline" && entity.Slug != nil {
|
||||
entitySlugs = append(entitySlugs, *entity.Slug)
|
||||
entitySlugToID[*entity.Slug] = entity.ID
|
||||
}
|
||||
}
|
||||
|
||||
if len(entitySlugs) > 0 {
|
||||
existEntities, err := s.entityRepo.GetBySlugs(ctx, entitySlugs)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get entities")
|
||||
}
|
||||
for _, exist := range existEntities {
|
||||
if snapID, ok := entitySlugToID[exist.Slug]; ok {
|
||||
if exist.ID != snapID {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Entity %s already exists", exist.Slug))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var wikiSlugs []string
|
||||
wikiSlugToID := make(map[string]string)
|
||||
for _, wiki := range snapshotData.Wikis {
|
||||
if wiki.Slug != nil {
|
||||
exist, err := s.wikiRepo.GetBySlug(ctx, *wiki.Slug)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wiki")
|
||||
}
|
||||
if exist != nil {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Wiki %s already exists", *wiki.Slug))
|
||||
if wiki.Source == "inline" && wiki.Slug != nil {
|
||||
wikiSlugs = append(wikiSlugs, *wiki.Slug)
|
||||
wikiSlugToID[*wiki.Slug] = wiki.ID
|
||||
}
|
||||
}
|
||||
|
||||
if len(wikiSlugs) > 0 {
|
||||
existWikis, err := s.wikiRepo.GetBySlugs(ctx, wikiSlugs)
|
||||
if err != nil {
|
||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wikis")
|
||||
}
|
||||
for _, exist := range existWikis {
|
||||
if snapID, ok := wikiSlugToID[exist.Slug]; ok {
|
||||
if exist.ID != snapID {
|
||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Wiki %s already exists", exist.Slug))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user