Module project, commit, submission
All checks were successful
Build and Release / release (push) Successful in 1m15s

This commit is contained in:
2026-04-26 16:31:03 +07:00
parent ac90236022
commit 6918a100fc
60 changed files with 5957 additions and 1020 deletions

View File

@@ -0,0 +1,230 @@
package repositories
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/pkg/cache"
"history-api/pkg/constants"
"history-api/pkg/convert"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
type CommitRepository interface {
Create(ctx context.Context, params sqlc.CreateCommitParams) (*models.CommitEntity, error)
GetByID(ctx context.Context, id pgtype.UUID) (*models.CommitEntity, error)
GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.CommitEntity, error)
Search(ctx context.Context, params sqlc.SearchCommitsParams) ([]*models.CommitEntity, error)
WithTx(tx pgx.Tx) CommitRepository
}
type commitRepository struct {
q *sqlc.Queries
c cache.Cache
}
func NewCommitRepository(db sqlc.DBTX, c cache.Cache) CommitRepository {
return &commitRepository{
q: sqlc.New(db),
c: c,
}
}
func (r *commitRepository) WithTx(tx pgx.Tx) CommitRepository {
return &commitRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *commitRepository) Create(ctx context.Context, params sqlc.CreateCommitParams) (*models.CommitEntity, error) {
row, err := r.q.CreateCommit(ctx, params)
if err != nil {
return nil, err
}
return &models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
}, nil
}
func (r *commitRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.CommitEntity, error) {
cacheId := fmt.Sprintf("commit:id:%s", convert.UUIDToString(id))
var commit models.CommitEntity
err := r.c.Get(ctx, cacheId, &commit)
if err == nil {
_ = r.c.Set(ctx, cacheId, commit, constants.NormalCacheDuration)
return &commit, nil
}
row, err := r.q.GetCommitById(ctx, id)
if err != nil {
return nil, err
}
commit = models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
}
_ = r.c.Set(ctx, cacheId, commit, constants.NormalCacheDuration)
return &commit, nil
}
func (r *commitRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:query:%s", prefix, hash)
}
func (r *commitRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.CommitEntity, error) {
if len(ids) == 0 {
return []*models.CommitEntity{}, nil
}
keys := make([]string, len(ids))
for i, id := range ids {
keys[i] = fmt.Sprintf("commit:id:%s", id)
}
raws := r.c.MGet(ctx, keys...)
var commits []*models.CommitEntity
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.CommitEntity)
if len(missingPgIds) > 0 {
dbRows, err := r.q.GetCommitsByIDs(ctx, missingPgIds)
if err == nil {
for _, row := range dbRows {
item := models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
}
dbMap[item.ID] = &item
}
}
}
for i, b := range raws {
if len(b) > 0 {
var c models.CommitEntity
if err := json.Unmarshal(b, &c); err == nil {
commits = append(commits, &c)
}
} else {
if item, ok := dbMap[ids[i]]; ok {
commits = append(commits, item)
missingToCache[keys[i]] = item
}
}
}
if len(missingToCache) > 0 {
_ = r.c.MSet(ctx, missingToCache, constants.NormalCacheDuration)
}
return commits, nil
}
func (r *commitRepository) GetByProjectID(ctx context.Context, projectID pgtype.UUID) ([]*models.CommitEntity, error) {
queryKey := fmt.Sprintf("commit:project:%s", convert.UUIDToString(projectID))
var cachedIDs []string
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
return r.getByIDsWithFallback(ctx, cachedIDs)
}
rows, err := r.q.GetCommitsByProjectID(ctx, projectID)
if err != nil {
return nil, err
}
entities := make([]*models.CommitEntity, 0, len(rows))
for _, row := range rows {
entities = append(entities, &models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
})
}
return entities, nil
}
func (r *commitRepository) Search(ctx context.Context, params sqlc.SearchCommitsParams) ([]*models.CommitEntity, error) {
queryKey := r.generateQueryKey("commit:search", params)
var cachedIDs []string
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
return r.getByIDsWithFallback(ctx, cachedIDs)
}
rows, err := r.q.SearchCommits(ctx, params)
if err != nil {
return nil, err
}
var commits []*models.CommitEntity
var ids []string
commitToCache := make(map[string]any)
for _, row := range rows {
commit := &models.CommitEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
SnapshotJson: row.SnapshotJson,
SnapshotHash: convert.TextToString(row.SnapshotHash),
UserID: convert.UUIDToString(row.UserID),
EditSummary: convert.TextToString(row.EditSummary),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
}
ids = append(ids, commit.ID)
commits = append(commits, commit)
commitToCache[fmt.Sprintf("commit:id:%s", commit.ID)] = commit
}
if len(commitToCache) > 0 {
_ = r.c.MSet(ctx, commitToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, queryKey, ids, constants.ListCacheDuration)
}
return commits, nil
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/gen/sqlc"
@@ -22,6 +23,7 @@ type EntityRepository interface {
Create(ctx context.Context, params sqlc.CreateEntityParams) (*models.EntityEntity, error)
Update(ctx context.Context, params sqlc.UpdateEntityParams) (*models.EntityEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
WithTx(tx pgx.Tx) EntityRepository
}
type entityRepository struct {
@@ -36,6 +38,13 @@ func NewEntityRepository(db sqlc.DBTX, c cache.Cache) EntityRepository {
}
}
func (r *entityRepository) WithTx(tx pgx.Tx) EntityRepository {
return &entityRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *entityRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -193,8 +202,6 @@ func (r *entityRepository) Create(ctx context.Context, params sqlc.CreateEntityP
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("entity:id:%s", entity.ID), entity, constants.NormalCacheDuration)
go func() {
_ = r.c.DelByPattern(context.Background(), "entity:search*")
}()
@@ -215,7 +222,7 @@ func (r *entityRepository) Update(ctx context.Context, params sqlc.UpdateEntityP
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("entity:id:%s", entity.ID), entity, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("entity:id:%s", entity.ID))
return &entity, nil
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/dtos/response"
@@ -25,6 +26,7 @@ type GeometryRepository interface {
Delete(ctx context.Context, id pgtype.UUID) error
CreateEntityGeometries(ctx context.Context, params sqlc.CreateEntityGeometriesParams) error
BulkDeleteEntityGeometriesByEntityId(ctx context.Context, entityId pgtype.UUID) error
WithTx(tx pgx.Tx) GeometryRepository
}
type geometryRepository struct {
@@ -39,6 +41,13 @@ func NewGeometryRepository(db sqlc.DBTX, c cache.Cache) GeometryRepository {
}
}
func (r *geometryRepository) WithTx(tx pgx.Tx) GeometryRepository {
return &geometryRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *geometryRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -87,9 +96,9 @@ func (r *geometryRepository) getByIDsWithFallback(ctx context.Context, ids []str
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
dbMap[item.ID] = &item
}
@@ -148,9 +157,9 @@ func (r *geometryRepository) GetByID(ctx context.Context, id pgtype.UUID) (*mode
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, cacheId, geometry, constants.NormalCacheDuration)
@@ -186,9 +195,9 @@ func (r *geometryRepository) Search(ctx context.Context, params sqlc.SearchGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
ids = append(ids, geometry.ID)
geometries = append(geometries, geometry)
@@ -224,16 +233,15 @@ func (r *geometryRepository) Create(ctx context.Context, params sqlc.CreateGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID), geometry, constants.NormalCacheDuration)
go func() {
bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "geometry:search*")
_ = r.c.DelByPattern(context.Background(), "geometry:search*")
}()
return &geometry, nil
}
@@ -255,11 +263,11 @@ func (r *geometryRepository) Update(ctx context.Context, params sqlc.UpdateGeome
MaxLng: row.MaxLng,
MaxLat: row.MaxLat,
},
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
IsDeleted: row.IsDeleted,
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID), geometry, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("geometry:id:%s", geometry.ID))
return &geometry, nil
}

View File

@@ -11,6 +11,7 @@ import (
"history-api/pkg/constants"
"history-api/pkg/convert"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
@@ -23,6 +24,7 @@ type MediaRepository interface {
Delete(ctx context.Context, id pgtype.UUID) error
BulkDelete(ctx context.Context, ids []pgtype.UUID) error
Create(ctx context.Context, params sqlc.CreateMediaParams) (*models.MediaEntity, error)
WithTx(tx pgx.Tx) MediaRepository
}
type mediaRepository struct {
@@ -37,6 +39,13 @@ func NewMediaRepository(db sqlc.DBTX, c cache.Cache) MediaRepository {
}
}
func (r *mediaRepository) WithTx(tx pgx.Tx) MediaRepository {
return &mediaRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *mediaRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -167,8 +176,7 @@ func (r *mediaRepository) Create(ctx context.Context, params sqlc.CreateMediaPar
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("media:id:%s", media.ID), media, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("media:userId:%s", convert.UUIDToString(params.UserID)))
return &media, nil
}
@@ -181,8 +189,7 @@ func (r *mediaRepository) Delete(ctx context.Context, id pgtype.UUID) error {
cacheId := fmt.Sprintf("media:id:%s", convert.UUIDToString(id))
_ = r.c.Del(ctx, cacheId)
go func() {
bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "media:count*")
_ = r.c.DelByPattern(context.Background(), "media:count*")
}()
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/gen/sqlc"
@@ -24,6 +25,13 @@ type ProjectRepository interface {
Create(ctx context.Context, params sqlc.CreateProjectParams) (*models.ProjectEntity, error)
Update(ctx context.Context, params sqlc.UpdateProjectParams) (*models.ProjectEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
AddMember(ctx context.Context, params sqlc.AddProjectMemberParams) error
UpdateMemberRole(ctx context.Context, params sqlc.UpdateProjectMemberRoleParams) error
RemoveMember(ctx context.Context, params sqlc.RemoveProjectMemberParams) error
CheckPermission(ctx context.Context, params sqlc.CheckProjectPermissionParams) (int16, error)
ChangeOwner(ctx context.Context, params sqlc.ChangeProjectOwnerParams) error
UpdateLatestCommit(ctx context.Context, params sqlc.UpdateLatestCommitParams) error
WithTx(tx pgx.Tx) ProjectRepository
}
type projectRepository struct {
@@ -38,13 +46,18 @@ func NewProjectRepository(db sqlc.DBTX, c cache.Cache) ProjectRepository {
}
}
func (r *projectRepository) WithTx(tx pgx.Tx) ProjectRepository {
return &projectRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *projectRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
}
func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.ProjectEntity, error) {
if len(ids) == 0 {
return []*models.ProjectEntity{}, nil
@@ -75,21 +88,21 @@ func (r *projectRepository) getByIDsWithFallback(ctx context.Context, ids []stri
if err == nil {
for _, row := range dbRows {
item := models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = item.ParseUser(row.User)
_ = item.ParseCommits(row.Commits)
_ = item.ParseMembers(row.Members)
dbMap[item.ID] = &item
}
}
@@ -135,21 +148,21 @@ func (r *projectRepository) GetByID(ctx context.Context, id pgtype.UUID) (*model
}
project = models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseMembers(row.Members)
_ = r.c.Set(ctx, cacheId, project, constants.NormalCacheDuration)
@@ -167,28 +180,28 @@ func (r *projectRepository) GetByUserID(ctx context.Context, params sqlc.GetProj
if err != nil {
return nil, err
}
var projects []*models.ProjectEntity
var ids []string
projectToCache := make(map[string]any)
for _, row := range rows {
project := &models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseMembers(row.Members)
ids = append(ids, project.ID)
projects = append(projects, project)
@@ -222,21 +235,21 @@ func (r *projectRepository) Search(ctx context.Context, params sqlc.SearchProjec
for _, row := range rows {
project := &models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseMembers(row.Members)
ids = append(ids, project.ID)
projects = append(projects, project)
@@ -276,23 +289,21 @@ func (r *projectRepository) Create(ctx context.Context, params sqlc.CreateProjec
}
project := models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = r.c.Set(ctx, fmt.Sprintf("project:id:%s", project.ID), project, constants.NormalCacheDuration)
_ = project.ParseCommits(row.Commits)
_ = project.ParseMembers(row.Members)
go func() {
bgCtx := context.Background()
@@ -309,23 +320,23 @@ func (r *projectRepository) Update(ctx context.Context, params sqlc.UpdateProjec
return nil, err
}
project := models.ProjectEntity{
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestRevisionID: convert.UUIDToStringPtr(row.LatestRevisionID),
VersionCount: row.VersionCount,
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
CommitIds: convert.ListUUIDToString(row.CommitIds),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
ID: convert.UUIDToString(row.ID),
Title: row.Title,
Description: convert.TextToString(row.Description),
LatestCommitID: convert.UUIDToStringPtr(row.LatestCommitID),
ProjectStatus: constants.ParseProjectStatusType(row.ProjectStatus),
LockedBy: convert.UUIDToStringPtr(row.LockedBy),
IsDeleted: row.IsDeleted,
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
SubmissionIds: convert.ListUUIDToString(row.SubmissionIds),
}
_ = project.ParseUser(row.User)
_ = project.ParseCommits(row.Commits)
_ = project.ParseMembers(row.Members)
_ = r.c.Set(ctx, fmt.Sprintf("project:id:%s", project.ID), project, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", project.ID))
return &project, nil
}
@@ -337,9 +348,75 @@ func (r *projectRepository) Delete(ctx context.Context, id pgtype.UUID) error {
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(id)))
go func() {
bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "project:search*")
_ = r.c.DelByPattern(bgCtx, "project:user*")
_ = r.c.DelByPattern(bgCtx, "project:count*")
}()
return nil
}
func (r *projectRepository) AddMember(ctx context.Context, params sqlc.AddProjectMemberParams) error {
_, err := r.q.AddProjectMember(ctx, params)
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID)))
_ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil
}
func (r *projectRepository) UpdateMemberRole(ctx context.Context, params sqlc.UpdateProjectMemberRoleParams) error {
_, err := r.q.UpdateProjectMemberRole(ctx, params)
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID)))
_ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil
}
func (r *projectRepository) RemoveMember(ctx context.Context, params sqlc.RemoveProjectMemberParams) error {
err := r.q.RemoveProjectMember(ctx, params)
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ProjectID)))
_ = r.c.Del(ctx, fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID)))
return nil
}
func (r *projectRepository) CheckPermission(ctx context.Context, params sqlc.CheckProjectPermissionParams) (int16, error) {
cacheKey := fmt.Sprintf("project:perm:%s:%s", convert.UUIDToString(params.ProjectID), convert.UUIDToString(params.UserID))
var role int16
if err := r.c.Get(ctx, cacheKey, &role); err == nil {
return role, nil
}
role, err := r.q.CheckProjectPermission(ctx, params)
if err != nil {
return 0, err
}
_ = r.c.Set(ctx, cacheKey, role, constants.NormalCacheDuration)
return role, nil
}
func (r *projectRepository) ChangeOwner(ctx context.Context, params sqlc.ChangeProjectOwnerParams) error {
err := r.q.ChangeProjectOwner(ctx, params)
if err != nil {
return err
}
projectID := convert.UUIDToString(params.ID)
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", projectID))
go func() {
_ = r.c.DelByPattern(context.Background(), fmt.Sprintf("project:perm:%s:*", projectID))
}()
return nil
}
func (r *projectRepository) UpdateLatestCommit(ctx context.Context, params sqlc.UpdateLatestCommitParams) error {
err := r.q.UpdateLatestCommit(ctx, params)
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("project:id:%s", convert.UUIDToString(params.ID)))
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/gen/sqlc"
@@ -18,7 +19,7 @@ import (
type RoleRepository interface {
GetByID(ctx context.Context, id pgtype.UUID) (*models.RoleEntity, error)
GetByIDs(ctx context.Context, ids []string) ([]*models.RoleEntity, error)
GetByname(ctx context.Context, name string) (*models.RoleEntity, error)
GetByName(ctx context.Context, name string) (*models.RoleEntity, error)
All(ctx context.Context) ([]*models.RoleEntity, error)
Create(ctx context.Context, name string) (*models.RoleEntity, error)
Update(ctx context.Context, params sqlc.UpdateRoleParams) (*models.RoleEntity, error)
@@ -28,6 +29,7 @@ type RoleRepository interface {
DeleteUserRole(ctx context.Context, params sqlc.DeleteUserRoleParams) error
BulkDeleteRolesFromUser(ctx context.Context, userId pgtype.UUID) error
BulkDeleteUsersFromRole(ctx context.Context, roleId pgtype.UUID) error
WithTx(tx pgx.Tx) RoleRepository
}
type roleRepository struct {
@@ -42,6 +44,13 @@ func NewRoleRepository(db sqlc.DBTX, c cache.Cache) RoleRepository {
}
}
func (r *roleRepository) WithTx(tx pgx.Tx) RoleRepository {
return &roleRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *roleRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -140,7 +149,7 @@ func (r *roleRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.R
return &role, nil
}
func (r *roleRepository) GetByname(ctx context.Context, name string) (*models.RoleEntity, error) {
func (r *roleRepository) GetByName(ctx context.Context, name string) (*models.RoleEntity, error) {
cacheId := fmt.Sprintf("role:name:%s", name)
var role models.RoleEntity
err := r.c.Get(ctx, cacheId, &role)
@@ -183,11 +192,6 @@ func (r *roleRepository) Create(ctx context.Context, name string) (*models.RoleE
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
mapCache := map[string]any{
fmt.Sprintf("role:name:%s", name): role,
fmt.Sprintf("role:id:%s", convert.UUIDToString(row.ID)): role,
}
_ = r.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
return &role, nil
}
@@ -204,11 +208,7 @@ func (r *roleRepository) Update(ctx context.Context, params sqlc.UpdateRoleParam
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
mapCache := map[string]any{
fmt.Sprintf("role:name:%s", row.Name): role,
fmt.Sprintf("role:id:%s", convert.UUIDToString(row.ID)): role,
}
_ = r.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("role:id:%s", convert.UUIDToString(row.ID)), fmt.Sprintf("role:name:%s", row.Name))
return &role, nil
}

View File

@@ -0,0 +1,313 @@
package repositories
import (
"context"
"crypto/md5"
"encoding/json"
"fmt"
"history-api/internal/gen/sqlc"
"history-api/internal/models"
"history-api/pkg/cache"
"history-api/pkg/constants"
"history-api/pkg/convert"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
type SubmissionRepository interface {
GetByID(ctx context.Context, id pgtype.UUID) (*models.SubmissionEntity, error)
GetByIDs(ctx context.Context, ids []string) ([]*models.SubmissionEntity, error)
Search(ctx context.Context, params sqlc.SearchSubmissionsParams) ([]*models.SubmissionEntity, error)
Count(ctx context.Context, params sqlc.CountSubmissionsParams) (int64, error)
Create(ctx context.Context, params sqlc.CreateSubmissionParams) (*models.SubmissionEntity, error)
Update(ctx context.Context, params sqlc.UpdateSubmissionParams) (*models.SubmissionEntity, error)
Delete(ctx context.Context, id pgtype.UUID) error
WithTx(tx pgx.Tx) SubmissionRepository
}
type submissionRepository struct {
q *sqlc.Queries
c cache.Cache
}
func NewSubmissionRepository(db sqlc.DBTX, c cache.Cache) SubmissionRepository {
return &submissionRepository{
q: sqlc.New(db),
c: c,
}
}
func (r *submissionRepository) WithTx(tx pgx.Tx) SubmissionRepository {
return &submissionRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *submissionRepository) GetByID(ctx context.Context, id pgtype.UUID) (*models.SubmissionEntity, error) {
cacheId := fmt.Sprintf("submission:id:%s", convert.UUIDToString(id))
var submission models.SubmissionEntity
err := r.c.Get(ctx, cacheId, &submission)
if err == nil {
return &submission, nil
}
row, err := r.q.GetSubmissionById(ctx, id)
if err != nil {
return nil, err
}
entity := &models.SubmissionEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
CommitID: convert.UUIDToString(row.CommitID),
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
Status: constants.ParseStatusType(row.Status),
ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy),
ReviewedAt: convert.TimeToPtr(row.ReviewedAt),
ReviewNote: convert.TextToPtr(row.ReviewNote),
Content: convert.TextToPtr(row.Content),
IsDeleted: row.IsDeleted,
}
_ = entity.ParseUser(row.User)
_ = entity.ParseReviewer(row.Reviewer)
_ = r.c.Set(ctx, cacheId, entity, constants.NormalCacheDuration)
return entity, nil
}
func (r *submissionRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
return fmt.Sprintf("%s:%s", prefix, hash)
}
func (r *submissionRepository) getByIDsWithFallback(ctx context.Context, ids []string) ([]*models.SubmissionEntity, error) {
if len(ids) == 0 {
return []*models.SubmissionEntity{}, nil
}
keys := make([]string, len(ids))
for i, id := range ids {
keys[i] = fmt.Sprintf("submission:id:%s", id)
}
raws := r.c.MGet(ctx, keys...)
var submission []*models.SubmissionEntity
missingSubmisstionToCache := 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.SubmissionEntity)
if len(missingPgIds) > 0 {
dbRows, err := r.q.GetSubmissionsByIDs(ctx, missingPgIds)
if err == nil {
for _, row := range dbRows {
entity := &models.SubmissionEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
CommitID: convert.UUIDToString(row.CommitID),
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
Status: constants.ParseStatusType(row.Status),
ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy),
ReviewedAt: convert.TimeToPtr(row.ReviewedAt),
ReviewNote: convert.TextToPtr(row.ReviewNote),
Content: convert.TextToPtr(row.Content),
IsDeleted: row.IsDeleted,
}
_ = entity.ParseUser(row.User)
_ = entity.ParseReviewer(row.Reviewer)
dbMap[entity.ID] = entity
}
}
}
for i, b := range raws {
if len(b) > 0 {
var u models.SubmissionEntity
if err := json.Unmarshal(b, &u); err == nil {
submission = append(submission, &u)
}
} else {
if item, ok := dbMap[ids[i]]; ok {
submission = append(submission, item)
missingSubmisstionToCache[keys[i]] = item
}
}
}
if len(missingSubmisstionToCache) > 0 {
_ = r.c.MSet(ctx, missingSubmisstionToCache, constants.NormalCacheDuration)
}
return submission, nil
}
func (r *submissionRepository) GetByIDs(ctx context.Context, ids []string) ([]*models.SubmissionEntity, error) {
return r.getByIDsWithFallback(ctx, ids)
}
func (r *submissionRepository) Search(ctx context.Context, params sqlc.SearchSubmissionsParams) ([]*models.SubmissionEntity, error) {
queryKey := r.generateQueryKey("verification:search", params)
var cachedIDs []string
if err := r.c.Get(ctx, queryKey, &cachedIDs); err == nil && len(cachedIDs) > 0 {
listItem, err := r.getByIDsWithFallback(ctx, cachedIDs)
if err != nil {
return nil, err
}
newCachedIDs := make([]string, len(listItem))
for i, media := range listItem {
newCachedIDs[i] = media.ID
}
_ = r.c.Set(ctx, queryKey, newCachedIDs, constants.ListCacheDuration)
return listItem, err
}
rows, err := r.q.SearchSubmissions(ctx, params)
if err != nil {
return nil, err
}
var items []*models.SubmissionEntity
var ids []string
itemToCache := make(map[string]any)
for _, row := range rows {
entity := &models.SubmissionEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
CommitID: convert.UUIDToString(row.CommitID),
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
Status: constants.ParseStatusType(row.Status),
ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy),
ReviewedAt: convert.TimeToPtr(row.ReviewedAt),
ReviewNote: convert.TextToPtr(row.ReviewNote),
Content: convert.TextToPtr(row.Content),
IsDeleted: row.IsDeleted,
}
_ = entity.ParseUser(row.User)
_ = entity.ParseReviewer(row.Reviewer)
ids = append(ids, entity.ID)
items = append(items, entity)
itemToCache[fmt.Sprintf("submission:id:%s", entity.ID)] = entity
}
if len(itemToCache) > 0 {
_ = r.c.MSet(ctx, itemToCache, constants.NormalCacheDuration)
}
if len(ids) > 0 {
_ = r.c.Set(ctx, queryKey, ids, constants.ListCacheDuration)
}
return items, nil
}
func (r *submissionRepository) Count(ctx context.Context, params sqlc.CountSubmissionsParams) (int64, error) {
queryKey := r.generateQueryKey("submission:count", params)
var count int64
if err := r.c.Get(ctx, queryKey, &count); err == nil {
_ = r.c.Set(ctx, queryKey, count, constants.ListCacheDuration)
return count, nil
}
count, err := r.q.CountSubmissions(ctx, params)
if err != nil {
return 0, err
}
_ = r.c.Set(ctx, queryKey, count, constants.ListCacheDuration)
return count, nil
}
func (r *submissionRepository) Create(ctx context.Context, params sqlc.CreateSubmissionParams) (*models.SubmissionEntity, error) {
row, err := r.q.CreateSubmission(ctx, params)
if err != nil {
return nil, err
}
submission := models.SubmissionEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
CommitID: convert.UUIDToString(row.CommitID),
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
Status: constants.ParseStatusType(row.Status),
ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy),
ReviewedAt: convert.TimeToPtr(row.ReviewedAt),
ReviewNote: convert.TextToPtr(row.ReviewNote),
Content: convert.TextToPtr(row.Content),
IsDeleted: row.IsDeleted,
}
if err := submission.ParseUser(row.User); err != nil {
return nil, err
}
if err := submission.ParseReviewer(row.Reviewer); err != nil {
return nil, err
}
go func() {
bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "submission:search*")
_ = r.c.DelByPattern(bgCtx, "submission:count*")
}()
return &submission, nil
}
func (r *submissionRepository) Update(ctx context.Context, params sqlc.UpdateSubmissionParams) (*models.SubmissionEntity, error) {
row, err := r.q.UpdateSubmission(ctx, params)
if err != nil {
return nil, err
}
submission := models.SubmissionEntity{
ID: convert.UUIDToString(row.ID),
ProjectID: convert.UUIDToString(row.ProjectID),
CommitID: convert.UUIDToString(row.CommitID),
UserID: convert.UUIDToString(row.UserID),
CreatedAt: convert.TimeToPtr(row.CreatedAt),
Status: constants.ParseStatusType(row.Status),
ReviewedBy: convert.UUIDToStringPtr(row.ReviewedBy),
ReviewedAt: convert.TimeToPtr(row.ReviewedAt),
ReviewNote: convert.TextToPtr(row.ReviewNote),
Content: convert.TextToPtr(row.Content),
IsDeleted: row.IsDeleted,
}
if err := submission.ParseUser(row.User); err != nil {
return nil, err
}
if err := submission.ParseReviewer(row.Reviewer); err != nil {
return nil, err
}
_ = r.c.Del(ctx, fmt.Sprintf("submission:id:%s", submission.ID))
return &submission, nil
}
func (r *submissionRepository) Delete(ctx context.Context, id pgtype.UUID) error {
err := r.q.DeleteSubmission(ctx, id)
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("submission:id:%s", convert.UUIDToString(id)))
go func() {
_ = r.c.DelByPattern(context.Background(), "submission:count*")
}()
return nil
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/gen/sqlc"
@@ -30,6 +31,7 @@ type UserRepository interface {
UpdateTokenVersion(ctx context.Context, params sqlc.UpdateTokenVersionParams) error
Delete(ctx context.Context, id pgtype.UUID) error
Restore(ctx context.Context, id pgtype.UUID) error
WithTx(tx pgx.Tx) UserRepository
}
type userRepository struct {
@@ -44,6 +46,13 @@ func NewUserRepository(db sqlc.DBTX, c cache.Cache) UserRepository {
}
}
func (r *userRepository) WithTx(tx pgx.Tx) UserRepository {
return &userRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *userRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -270,11 +279,7 @@ func (r *userRepository) UpdateProfile(ctx context.Context, params sqlc.UpdateUs
}
user.Profile = &profile
mapCache := map[string]any{
fmt.Sprintf("user:email:%s", user.Email): user,
fmt.Sprintf("user:id:%s", user.ID): user,
}
_ = r.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID))
return user, nil
}
@@ -391,11 +396,12 @@ func (r *userRepository) Restore(ctx context.Context, id pgtype.UUID) error {
if err != nil {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("user:id:%s", convert.UUIDToString(id)))
_ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", convert.UUIDToString(id)))
_ = r.c.Del(ctx, fmt.Sprintf("user:deleted:id:%s", convert.UUIDToString(id)))
_ = r.c.Del(
ctx,
fmt.Sprintf("user:deleted:id:%s", convert.UUIDToString(id)),
fmt.Sprintf("user:email:%s", convert.UUIDToString(id)),
fmt.Sprintf("user:id:%s", convert.UUIDToString(id)),
)
return nil
}
@@ -421,9 +427,7 @@ func (r *userRepository) UpdateTokenVersion(ctx context.Context, params sqlc.Upd
if err != nil {
return err
}
cacheId := fmt.Sprintf("user:token:%s", convert.UUIDToString(params.ID))
_ = r.c.Set(ctx, cacheId, params.TokenVersion, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("user:token:%s", convert.UUIDToString(params.ID)))
return nil
}
@@ -436,23 +440,7 @@ func (r *userRepository) UpdatePassword(ctx context.Context, params sqlc.UpdateU
if err != nil {
return err
}
err = r.UpdateTokenVersion(ctx, sqlc.UpdateTokenVersionParams{
ID: params.ID,
TokenVersion: user.TokenVersion + 1,
})
if err != nil {
return err
}
user.PasswordHash = convert.TextToString(params.PasswordHash)
user.TokenVersion += 1
mapCache := map[string]any{
fmt.Sprintf("user:email:%s", user.Email): user,
fmt.Sprintf("user:id:%s", user.ID): user,
fmt.Sprintf("user:token:%s", user.ID): user.TokenVersion,
}
_ = r.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID))
return nil
}
@@ -467,12 +455,7 @@ func (r *userRepository) UpdateRefreshToken(ctx context.Context, params sqlc.Upd
}
user.RefreshToken = convert.TextToString(params.RefreshToken)
mapCache := map[string]any{
fmt.Sprintf("user:email:%s", user.Email): user,
fmt.Sprintf("user:id:%s", user.ID): user,
fmt.Sprintf("user:token:%s", user.ID): user.TokenVersion,
}
_ = r.c.MSet(ctx, mapCache, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("user:email:%s", user.Email), fmt.Sprintf("user:id:%s", user.ID))
return nil
}

View File

@@ -11,6 +11,7 @@ import (
"history-api/pkg/constants"
"history-api/pkg/convert"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
)
@@ -25,6 +26,7 @@ type VerificationRepository interface {
CreateVerificationMedia(ctx context.Context, params sqlc.CreateVerificationMediaParams) error
DeleteVerificationMedia(ctx context.Context, params sqlc.DeleteVerificationMediaParams) error
BulkVerificationMediaByMediaId(ctx context.Context, mediaId pgtype.UUID) error
WithTx(tx pgx.Tx) VerificationRepository
}
type verificationRepository struct {
@@ -39,6 +41,13 @@ func NewVerificationRepository(db sqlc.DBTX, c cache.Cache) VerificationReposito
}
}
func (v *verificationRepository) WithTx(tx pgx.Tx) VerificationRepository {
return &verificationRepository{
q: v.q.WithTx(tx),
c: v.c,
}
}
func (v *verificationRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -174,13 +183,6 @@ func (v *verificationRepository) Create(ctx context.Context, params sqlc.CreateU
if err != nil {
return nil, err
}
go func() {
bgCtx := context.Background()
_ = v.c.DelByPattern(bgCtx, "verification:search*")
_ = v.c.DelByPattern(bgCtx, "verification:count*")
}()
verification := models.UserVerificationEntity{
ID: convert.UUIDToString(row.ID),
VerifyType: constants.ParseVerifyType(row.VerifyType),
@@ -204,9 +206,9 @@ func (v *verificationRepository) Create(ctx context.Context, params sqlc.CreateU
return nil, err
}
_ = v.c.Del(ctx, fmt.Sprintf("verification:userId:%s", convert.UUIDToString(params.UserID)))
go func() {
bgCtx := context.Background()
_ = v.c.DelByPattern(bgCtx, "verification:search*")
_ = v.c.DelByPattern(bgCtx, "verification:count*")
}()
@@ -218,10 +220,7 @@ func (v *verificationRepository) UpdateStatus(ctx context.Context, params sqlc.U
if err != nil {
return err
}
cacheId := fmt.Sprintf("verification:id:%s", convert.UUIDToString(params.ID))
_ = v.c.Del(ctx, cacheId)
_ = v.c.Del(ctx, fmt.Sprintf("verification:id:%s", convert.UUIDToString(params.ID)))
return nil
}
@@ -231,9 +230,10 @@ func (v *verificationRepository) Delete(ctx context.Context, id pgtype.UUID) err
return err
}
cacheId := fmt.Sprintf("verification:id:%s", convert.UUIDToString(id))
_ = v.c.Del(ctx, cacheId)
_ = v.c.Del(ctx, fmt.Sprintf("verification:id:%s", convert.UUIDToString(id)))
go func() {
_ = v.c.DelByPattern(context.Background(), "verification:count*")
}()
return nil
}
@@ -243,6 +243,10 @@ func (v *verificationRepository) BulkVerificationMediaByMediaId(ctx context.Cont
return err
}
if len(ids) == 0 {
return nil
}
listCacheId := make([]string, 0)
for _, it := range ids {
id := convert.UUIDToString(it)

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"fmt"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgtype"
"history-api/internal/gen/sqlc"
@@ -24,6 +25,7 @@ type WikiRepository interface {
Delete(ctx context.Context, id pgtype.UUID) error
CreateEntityWikis(ctx context.Context, params sqlc.CreateEntityWikisParams) error
BulkDeleteEntityWikisByEntityId(ctx context.Context, entityId pgtype.UUID) error
WithTx(tx pgx.Tx) WikiRepository
}
type wikiRepository struct {
@@ -38,6 +40,13 @@ func NewWikiRepository(db sqlc.DBTX, c cache.Cache) WikiRepository {
}
}
func (r *wikiRepository) WithTx(tx pgx.Tx) WikiRepository {
return &wikiRepository{
q: r.q.WithTx(tx),
c: r.c,
}
}
func (r *wikiRepository) generateQueryKey(prefix string, params any) string {
b, _ := json.Marshal(params)
hash := fmt.Sprintf("%x", md5.Sum(b))
@@ -191,11 +200,9 @@ func (r *wikiRepository) Create(ctx context.Context, params sqlc.CreateWikiParam
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID), wiki, constants.NormalCacheDuration)
go func() {
bgCtx := context.Background()
_ = r.c.DelByPattern(bgCtx, "wiki:search*")
_ = r.c.DelByPattern(context.Background(), "wiki:search*")
}()
return &wiki, nil
@@ -214,7 +221,7 @@ func (r *wikiRepository) Update(ctx context.Context, params sqlc.UpdateWikiParam
CreatedAt: convert.TimeToPtr(row.CreatedAt),
UpdatedAt: convert.TimeToPtr(row.UpdatedAt),
}
_ = r.c.Set(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID), wiki, constants.NormalCacheDuration)
_ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wiki.ID))
return &wiki, nil
}
@@ -224,9 +231,6 @@ func (r *wikiRepository) Delete(ctx context.Context, id pgtype.UUID) error {
return err
}
_ = r.c.Del(ctx, fmt.Sprintf("wiki:id:%s", convert.UUIDToString(id)))
go func() {
_ = r.c.DelByPattern(context.Background(), "wiki:search*")
}()
return nil
}