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:
@@ -65,3 +65,6 @@ WHERE id = ANY($1::uuid[]);
|
|||||||
SELECT *
|
SELECT *
|
||||||
FROM entities
|
FROM entities
|
||||||
WHERE slug = $1 AND is_deleted = false;
|
WHERE slug = $1 AND is_deleted = false;
|
||||||
|
|
||||||
|
-- name: GetEntitiesBySlugs :many
|
||||||
|
SELECT * FROM entities WHERE slug = ANY($1::text[]) AND is_deleted = false;
|
||||||
@@ -90,3 +90,6 @@ WHERE entity_id = $1 AND wiki_id = $2;
|
|||||||
SELECT *
|
SELECT *
|
||||||
FROM wikis
|
FROM wikis
|
||||||
WHERE slug = $1 AND is_deleted = false;
|
WHERE slug = $1 AND is_deleted = false;
|
||||||
|
|
||||||
|
-- name: GetWikisBySlugs :many
|
||||||
|
SELECT * FROM wikis WHERE slug = ANY($1::text[]) AND is_deleted = false;
|
||||||
|
|||||||
@@ -156,6 +156,42 @@ func (q *Queries) GetEntitiesByProjectId(ctx context.Context, projectID pgtype.U
|
|||||||
return items, nil
|
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
|
const getEntityById = `-- name: GetEntityById :one
|
||||||
SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at
|
SELECT id, project_id, name, slug, description, status, time_start, time_end, is_deleted, created_at, updated_at
|
||||||
FROM entities
|
FROM entities
|
||||||
|
|||||||
@@ -265,6 +265,39 @@ func (q *Queries) GetWikisByProjectId(ctx context.Context, projectID pgtype.UUID
|
|||||||
return items, nil
|
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
|
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.content, w.is_deleted, w.created_at, w.updated_at
|
||||||
FROM wikis w
|
FROM wikis w
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ type EntityRepository interface {
|
|||||||
GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error)
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.EntityEntity, error)
|
||||||
GetByIDs(ctx context.Context, ids []string) ([]*models.EntityEntity, error)
|
GetByIDs(ctx context.Context, ids []string) ([]*models.EntityEntity, error)
|
||||||
GetBySlug(ctx context.Context, slug 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)
|
Search(ctx context.Context, params sqlc.SearchEntitiesParams) ([]*models.EntityEntity, error)
|
||||||
Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error)
|
Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error)
|
||||||
Update(ctx context.Context, params sqlc.UpdateEntityParams) (*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
|
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)
|
GetByID(ctx context.Context, id pgtype.UUID) (*models.WikiEntity, error)
|
||||||
GetByIDs(ctx context.Context, ids []string) ([]*models.WikiEntity, error)
|
GetByIDs(ctx context.Context, ids []string) ([]*models.WikiEntity, error)
|
||||||
GetBySlug(ctx context.Context, slug 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)
|
Search(ctx context.Context, params sqlc.SearchWikisParams) ([]*models.WikiEntity, error)
|
||||||
Create(ctx context.Context, params sqlc.CreateWikiParams) (*models.WikiEntity, error)
|
Create(ctx context.Context, params sqlc.CreateWikiParams) (*models.WikiEntity, error)
|
||||||
Update(ctx context.Context, params sqlc.UpdateWikiParams) (*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
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"history-api/internal/dtos/request"
|
"history-api/internal/dtos/request"
|
||||||
"history-api/internal/dtos/response"
|
"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")
|
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var entitySlugs []string
|
||||||
|
entitySlugToID := make(map[string]string)
|
||||||
for _, entity := range snapshotData.Entities {
|
for _, entity := range snapshotData.Entities {
|
||||||
if entity.Slug != nil {
|
if entity.Source == "inline" && entity.Slug != nil {
|
||||||
exist, err := s.entityRepo.GetBySlug(ctx, *entity.Slug)
|
entitySlugs = append(entitySlugs, *entity.Slug)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
entitySlugToID[*entity.Slug] = entity.ID
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get entity")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
if exist != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Entity %s already exists", *entity.Slug))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var wikiSlugs []string
|
||||||
|
wikiSlugToID := make(map[string]string)
|
||||||
for _, wiki := range snapshotData.Wikis {
|
for _, wiki := range snapshotData.Wikis {
|
||||||
if wiki.Slug != nil {
|
if wiki.Source == "inline" && wiki.Slug != nil {
|
||||||
exist, err := s.wikiRepo.GetBySlug(ctx, *wiki.Slug)
|
wikiSlugs = append(wikiSlugs, *wiki.Slug)
|
||||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
wikiSlugToID[*wiki.Slug] = wiki.ID
|
||||||
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wiki")
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
if exist != nil {
|
|
||||||
return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Wiki %s already exists", *wiki.Slug))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user