migrate from github

This commit is contained in:
2026-07-15 22:42:31 +08:00
commit 3ad559f05e
262 changed files with 21637 additions and 0 deletions
@@ -0,0 +1,47 @@
package attachment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type AttachmentRepository struct {
db *gorm.DB
}
func NewAttachmentRepository(db *gorm.DB) *AttachmentRepository {
return &AttachmentRepository{db: db}
}
func (r *AttachmentRepository) Create(attachment *model.Attachment) error {
return r.db.Create(attachment).Error
}
func (r *AttachmentRepository) ListByDocumentID(documentID int64) ([]*model.Attachment, error) {
attachments := make([]*model.Attachment, 0)
if err := r.db.
Where("document_id = ?", documentID).
Order("created_at desc").
Find(&attachments).Error; err != nil {
return nil, err
}
return attachments, nil
}
func (r *AttachmentRepository) GetByExternalIDAndDocumentID(externalID string, documentID int64) (*model.Attachment, error) {
attachment := &model.Attachment{}
if err := r.db.
Where("external_id = ? AND document_id = ?", externalID, documentID).
First(attachment).Error; err != nil {
return nil, err
}
return attachment, nil
}
func (r *AttachmentRepository) DeleteByID(id int64) error {
return r.db.Delete(&model.Attachment{}, id).Error
}
func IsNotFound(err error) bool {
return err == gorm.ErrRecordNotFound
}
@@ -0,0 +1,11 @@
package comment_repo
import "gorm.io/gorm"
type CommentRepository struct {
db *gorm.DB
}
func NewCommentRepository(db *gorm.DB) *CommentRepository {
return &CommentRepository{db: db}
}
@@ -0,0 +1,35 @@
package comment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CommentCreateOperation struct {
repo *CommentRepository
item *model.DocumentComment
tx *gorm.DB
}
func (r *CommentRepository) Create(item *model.DocumentComment) *CommentCreateOperation {
return &CommentCreateOperation{
repo: r,
item: item,
}
}
func (op *CommentCreateOperation) WithTx(tx *gorm.DB) *CommentCreateOperation {
op.tx = tx
return op
}
func (op *CommentCreateOperation) Exec() error {
if op.item == nil {
return nil
}
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Create(op.item).Error
}
@@ -0,0 +1,32 @@
package comment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CommentDeleteOperation struct {
repo *CommentRepository
id int64
tx *gorm.DB
}
func (r *CommentRepository) Delete(id int64) *CommentDeleteOperation {
return &CommentDeleteOperation{
repo: r,
id: id,
}
}
func (op *CommentDeleteOperation) WithTx(tx *gorm.DB) *CommentDeleteOperation {
op.tx = tx
return op
}
func (op *CommentDeleteOperation) Exec() error {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Delete(&model.DocumentComment{}, op.id).Error
}
@@ -0,0 +1,36 @@
package comment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CommentGetByExternalIDOperation struct {
repo *CommentRepository
externalID string
tx *gorm.DB
}
func (r *CommentRepository) GetByExternalID(externalID string) *CommentGetByExternalIDOperation {
return &CommentGetByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *CommentGetByExternalIDOperation) WithTx(tx *gorm.DB) *CommentGetByExternalIDOperation {
op.tx = tx
return op
}
func (op *CommentGetByExternalIDOperation) Exec() (*model.DocumentComment, error) {
var item model.DocumentComment
db := op.repo.db
if op.tx != nil {
db = op.tx
}
if err := db.First(&item, "external_id = ?", op.externalID).Error; err != nil {
return nil, err
}
return &item, nil
}
@@ -0,0 +1,66 @@
package comment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CommentListByDocumentOperation struct {
repo *CommentRepository
documentID int64
page int
pageSize int
tx *gorm.DB
}
func (r *CommentRepository) ListByDocument(documentID int64) *CommentListByDocumentOperation {
return &CommentListByDocumentOperation{
repo: r,
documentID: documentID,
}
}
func (op *CommentListByDocumentOperation) WithTx(tx *gorm.DB) *CommentListByDocumentOperation {
op.tx = tx
return op
}
func (op *CommentListByDocumentOperation) WithPagination(page, pageSize int) *CommentListByDocumentOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *CommentListByDocumentOperation) ExecWithTotal() ([]model.DocumentComment, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.DocumentComment{}).
Where("document_id = ?", op.documentID).
Where("anchor_id IS NOT NULL AND anchor_id <> ''")
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var items []model.DocumentComment
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
@@ -0,0 +1,34 @@
package comment_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CommentUpdateContentOperation struct {
repo *CommentRepository
id int64
content string
tx *gorm.DB
}
func (r *CommentRepository) UpdateContentByID(id int64, content string) *CommentUpdateContentOperation {
return &CommentUpdateContentOperation{
repo: r,
id: id,
content: content,
}
}
func (op *CommentUpdateContentOperation) WithTx(tx *gorm.DB) *CommentUpdateContentOperation {
op.tx = tx
return op
}
func (op *CommentUpdateContentOperation) Exec() error {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Model(&model.DocumentComment{}).Where("id = ?", op.id).Update("content", op.content).Error
}
@@ -0,0 +1,32 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentCreateOperation struct {
repo *DocumentRepository
doc *model.Document
tx *gorm.DB
}
func (r *DocumentRepository) Create(doc *model.Document) *DocumentCreateOperation {
return &DocumentCreateOperation{
repo: r,
doc: doc,
}
}
func (op *DocumentCreateOperation) WithTx(tx *gorm.DB) *DocumentCreateOperation {
op.tx = tx
return op
}
func (op *DocumentCreateOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
return op.tx.Create(op.doc).Error
}
@@ -0,0 +1,36 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type UpsertRecentDocumentOperation struct {
repo *DocumentRepository
m *model.RecentDocument
tx *gorm.DB
}
func (r *DocumentRepository) UpsertRecentDocument(m *model.RecentDocument) *UpsertRecentDocumentOperation {
return &UpsertRecentDocumentOperation{
repo: r,
m: m,
}
}
func (op *UpsertRecentDocumentOperation) WithTx(tx *gorm.DB) *UpsertRecentDocumentOperation {
op.tx = tx
return op
}
func (op *UpsertRecentDocumentOperation) Exec() error {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "document_id"}},
DoUpdates: clause.Assignments(map[string]interface{}{"accessed_at": op.m.AccessedAt}),
}).Create(op.m).Error
}
@@ -0,0 +1,92 @@
package document_repo
import (
"context"
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type DocumentRepository struct {
db *gorm.DB
redis *redis.Client
Loader cache_loader.Loader
}
func NewDocumentRepository(db *gorm.DB, redis *redis.Client) *DocumentRepository {
return &DocumentRepository{
db: db,
redis: redis,
Loader: *cache_loader.NewLoader(redis),
}
}
type DocumentDeleteOperation struct {
repo *DocumentRepository
id int64
tx *gorm.DB
ctx context.Context
}
func (r *DocumentRepository) Delete(id int64) *DocumentDeleteOperation {
return &DocumentDeleteOperation{
repo: r,
id: id,
ctx: context.Background(),
}
}
func (op *DocumentDeleteOperation) WithTx(tx *gorm.DB) *DocumentDeleteOperation {
op.tx = tx
return op
}
func (op *DocumentDeleteOperation) WithContext(ctx context.Context) *DocumentDeleteOperation {
op.ctx = ctx
return op
}
func (op *DocumentDeleteOperation) Exec() error {
var doc model.Document
var db *gorm.DB
var err error
if op.tx != nil {
db = op.tx
} else {
db = op.repo.db
}
if err := db.First(&doc, op.id).Error; err != nil {
return err
}
if op.tx == nil {
db = db.Begin()
defer func() {
if err != nil {
db.Rollback()
} else {
db.Commit()
}
}()
}
if err := db.Delete(&model.Document{}, op.id).Error; err != nil {
return err
}
if op.tx == nil {
docCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, doc.ExternalID)
if err := op.repo.redis.Del(op.ctx, docCacheKey).Err(); err != nil {
}
if err := op.repo.BumpSubtreeVersionsByPath(op.ctx, doc.Path); err != nil {
}
}
return nil
}
@@ -0,0 +1,60 @@
package document_repo
import (
"context"
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type DocumentGetByExternalIDOperation struct {
repo *DocumentRepository
externalID string
tx *gorm.DB
}
func (r *DocumentRepository) GetByExternalID(externalID string) *DocumentGetByExternalIDOperation {
return &DocumentGetByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *DocumentGetByExternalIDOperation) WithTx(tx *gorm.DB) *DocumentGetByExternalIDOperation {
op.tx = tx
return op
}
func (op *DocumentGetByExternalIDOperation) query(db *gorm.DB) (*model.Document, error) {
var document model.Document
var err error
err = db.First(&document, "external_id = ?", op.externalID).Error
return &document, err
}
func (op *DocumentGetByExternalIDOperation) Exec(ctx context.Context) (*model.Document, error) {
var err error
if op.tx != nil {
return op.query(op.tx)
}
documentCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
document, err := cache_loader.NewCacheLoadOperation[model.Document](&op.repo.Loader).
WithDBLoader(func() (*model.Document, error) {
return op.query(op.repo.db)
}).WithDefaultKeyAndParser(documentCacheKey, nil).
Exec(ctx)
if err != nil {
logrus.Errorf("load data failed for DocumentGetByExternalIDOperation: %+v", err)
return nil, err
}
return document, nil
}
@@ -0,0 +1,41 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentGetContentOperation struct {
repo *DocumentRepository
documentID int64
tx *gorm.DB
}
func (r *DocumentRepository) GetContent(documentID int64) *DocumentGetContentOperation {
return &DocumentGetContentOperation{
repo: r,
documentID: documentID,
}
}
func (op *DocumentGetContentOperation) WithTx(tx *gorm.DB) *DocumentGetContentOperation {
op.tx = tx
return op
}
func (op *DocumentGetContentOperation) Exec() (string, error) {
var docMeta model.Document
var err error
db := op.repo.db
if op.tx != nil {
db = op.tx
}
err = db.Where("id = ?", op.documentID).First(&docMeta).Error
if err != nil {
return "", err
}
return docMeta.Content, nil
}
@@ -0,0 +1,66 @@
package document_repo
import (
"context"
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type DocumentGetIDByExternalIDOperation struct {
repo *DocumentRepository
externalID string
tx *gorm.DB
}
func (r *DocumentRepository) GetIDByExternalID(externalID string) *DocumentGetIDByExternalIDOperation {
return &DocumentGetIDByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *DocumentGetIDByExternalIDOperation) WithTx(tx *gorm.DB) *DocumentGetIDByExternalIDOperation {
op.tx = tx
return op
}
func (op DocumentGetIDByExternalIDOperation) query(tx *gorm.DB) (int64, error) {
var id int64
err := tx.Model(&model.Document{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
if err != nil {
return 0, err
}
return id, nil
}
func (op *DocumentGetIDByExternalIDOperation) Exec(ctx context.Context) (int64, error) {
if op.tx != nil {
return op.query(op.tx)
}
extidKey := key.KeyIDByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
modelKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
id, err := cache_loader.NewCacheLoadOperation[int64](&op.repo.Loader).
WithDefaultKeyAndParser(extidKey, cache_loader.ParseInt64).
WithKeyAndParser(modelKey, cache_loader.ParseIDFromDocument).
WithDBLoader(func() (*int64, error) {
id, err := op.query(op.repo.db)
if err != nil {
return nil, err
}
return &id, nil
}).Exec(ctx)
if err != nil {
return 0, err
}
if id == nil {
return 0, nil
}
return *id, nil
}
@@ -0,0 +1,149 @@
package document_repo
import (
"context"
"errors"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type DocumentGetSubtreeOperation struct {
repo *DocumentRepository
rootParentID int64
knowledgeID *int64
depth *int
directoryOnly bool
tx *gorm.DB
}
func (r *DocumentRepository) GetSubtree(rootParentID int64) *DocumentGetSubtreeOperation {
return &DocumentGetSubtreeOperation{
repo: r,
rootParentID: rootParentID,
}
}
func (op *DocumentGetSubtreeOperation) WithTx(tx *gorm.DB) *DocumentGetSubtreeOperation {
op.tx = tx
return op
}
func (op *DocumentGetSubtreeOperation) WithKnowledgeID(knowledgeID *int64) *DocumentGetSubtreeOperation {
op.knowledgeID = knowledgeID
return op
}
func (op *DocumentGetSubtreeOperation) WithDepth(depth *int) *DocumentGetSubtreeOperation {
op.depth = depth
return op
}
func (op *DocumentGetSubtreeOperation) WithDirectoryOnly(with bool) *DocumentGetSubtreeOperation {
op.directoryOnly = with
return op
}
func (op *DocumentGetSubtreeOperation) Exec(ctx context.Context) ([]*model.Document, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
var documents []*model.Document
if op.depth != nil && *op.depth == 0 {
return documents, nil
}
type pathDepth struct {
Path string
Depth int
}
var root pathDepth
err := db.Model(&model.Document{}).
Select("path, depth").
Where("id = ?", op.rootParentID).
Take(&root).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return documents, nil
}
return nil, err
}
// in-transaction query should bypass cache to avoid stale reads
if op.tx != nil || op.repo.redis == nil {
return op.queryWithRoot(db, root.Path, root.Depth)
}
version, err := op.repo.getSubtreeVersion(ctx, root.Path)
if err == nil {
cacheKey := key.KeyDocSubtree(root.Path, version, op.depth)
if cachedIDs, ok, err := getCachedSubtreeIDs(ctx, cacheKey); err == nil && ok {
return op.repo.fetchDocumentsByIDs(cachedIDs)
}
val, err, _ := subtreeCacheSF.Do(cacheKey, func() (interface{}, error) {
dbDocs, err := op.queryWithRoot(db, root.Path, root.Depth)
if err != nil {
return nil, err
}
ids := make([]int64, 0, len(dbDocs))
for _, doc := range dbDocs {
ids = append(ids, doc.ID)
}
setCachedSubtreeIDs(ctx, cacheKey, ids)
return dbDocs, nil
})
if err != nil {
return nil, err
}
if typed, ok := val.([]*model.Document); ok {
return typed, nil
}
}
return op.queryWithRoot(db, root.Path, root.Depth)
}
func (op *DocumentGetSubtreeOperation) queryWithRoot(db *gorm.DB, rootPath string, rootDepth int) ([]*model.Document, error) {
var documents []*model.Document
query := `
SELECT *
FROM document_metas
WHERE deleted_at IS NULL
AND id <> ?
AND path <@ ?
`
if op.directoryOnly {
query = `
SELECT id, external_id, title, parent_id, type
FROM document_metas
WHERE deleted_at IS NULL
AND id <> ?
AND path <@ ?
`
}
args := []interface{}{op.rootParentID, rootPath}
if op.knowledgeID != nil {
query += " AND knowledge_id = ?"
args = append(args, *op.knowledgeID)
}
if op.depth != nil {
maxDepth := rootDepth + *op.depth
query += " AND depth <= ?"
args = append(args, maxDepth)
}
query += " ORDER BY depth, created_at"
if err := db.Raw(query, args...).Find(&documents).Error; err != nil {
return nil, err
}
return documents, nil
}
@@ -0,0 +1,64 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentGetSubtreeByKnowledgeIDOperation struct {
repo *DocumentRepository
knowledgeID int64
depth *int
tx *gorm.DB
}
func (r *DocumentRepository) GetSubtreeByKnowledgeID(knowledgeID int64) *DocumentGetSubtreeByKnowledgeIDOperation {
return &DocumentGetSubtreeByKnowledgeIDOperation{
repo: r,
knowledgeID: knowledgeID,
}
}
func (op *DocumentGetSubtreeByKnowledgeIDOperation) WithTx(tx *gorm.DB) *DocumentGetSubtreeByKnowledgeIDOperation {
op.tx = tx
return op
}
func (op *DocumentGetSubtreeByKnowledgeIDOperation) WithDepth(depth int) *DocumentGetSubtreeByKnowledgeIDOperation {
op.depth = &depth
return op
}
func (op *DocumentGetSubtreeByKnowledgeIDOperation) Exec() ([]model.Document, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
var documents []model.Document
if op.depth != nil && *op.depth == 0 {
return documents, nil
}
query := `
SELECT *
FROM document_metas
WHERE deleted_at IS NULL
AND knowledge_id = ?
`
args := []interface{}{op.knowledgeID}
if op.depth != nil {
query += " AND depth <= ?"
args = append(args, *op.depth)
}
query += " ORDER BY depth, created_at"
err := db.Raw(query, args...).Find(&documents).Error
if err != nil {
return nil, err
}
return documents, nil
}
+246
View File
@@ -0,0 +1,246 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentsListOperation struct {
repo *DocumentRepository
model *model.Document
userID *int64
parentID *int64
knowledgeID *int64
listOwnDoc bool
ids []int64
titleKeyword *string
docType *string
tags []string
createTimeRangeStart *string
createTimeRangeEnd *string
updateTimeRangeStart *string
updateTimeRangeEnd *string
sortField string
sortDesc bool
page int
pageSize int
directoryOnly bool
tx *gorm.DB
}
func (r *DocumentRepository) ListDocuments(documentModel *model.Document) *DocumentsListOperation {
return &DocumentsListOperation{
repo: r,
model: documentModel,
}
}
func (op *DocumentsListOperation) WithUserID(userID *int64) *DocumentsListOperation {
op.userID = userID
return op
}
func (op *DocumentsListOperation) WithParentID(parentID *int64) *DocumentsListOperation {
op.parentID = parentID
return op
}
func (op *DocumentsListOperation) WithKnowledgeID(knowledgeID *int64) *DocumentsListOperation {
op.knowledgeID = knowledgeID
return op
}
func (op *DocumentsListOperation) WithListOwnDoc(listOwnDoc bool) *DocumentsListOperation {
op.listOwnDoc = listOwnDoc
return op
}
func (op *DocumentsListOperation) WithIDs(ids []int64) *DocumentsListOperation {
op.ids = ids
return op
}
func (op *DocumentsListOperation) WithTitleKeyword(titleKeyword *string) *DocumentsListOperation {
op.titleKeyword = titleKeyword
return op
}
func (op *DocumentsListOperation) WithType(docType *string) *DocumentsListOperation {
op.docType = docType
return op
}
func (op *DocumentsListOperation) WithTags(tags []string) *DocumentsListOperation {
op.tags = tags
return op
}
func (op *DocumentsListOperation) WithCreateTimeRange(start, end *string) *DocumentsListOperation {
op.createTimeRangeStart = start
op.createTimeRangeEnd = end
return op
}
func (op *DocumentsListOperation) WithUpdateTimeRange(start, end *string) *DocumentsListOperation {
op.updateTimeRangeStart = start
op.updateTimeRangeEnd = end
return op
}
func (op *DocumentsListOperation) WithPagination(page, pageSize int) *DocumentsListOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *DocumentsListOperation) WithSort(field string, desc bool) *DocumentsListOperation {
op.sortField = field
op.sortDesc = desc
return op
}
func (op *DocumentsListOperation) WithDirectoryOnly(with bool) *DocumentsListOperation {
op.directoryOnly = with
return op
}
func (op *DocumentsListOperation) WithTx(tx *gorm.DB) *DocumentsListOperation {
op.tx = tx
return op
}
func (op *DocumentsListOperation) buildBaseQuery() *gorm.DB {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
dbQuery := db.Model(&model.Document{})
if op.directoryOnly {
dbQuery = dbQuery.Select("id, external_id, title, parent_id, type")
}
if op.model != nil {
dbQuery = dbQuery.Where(op.model)
}
if op.userID != nil {
dbQuery = dbQuery.Where("user_id = ?", *op.userID)
}
if op.parentID != nil {
dbQuery = dbQuery.Where("parent_id = ?", *op.parentID)
}
if op.listOwnDoc {
dbQuery = dbQuery.Where("knowledge_id IS NULL")
}
if op.ids != nil {
if len(op.ids) == 0 {
dbQuery = dbQuery.Where("1 = 0")
} else {
dbQuery = dbQuery.Where("id IN ?", op.ids)
}
}
if op.knowledgeID != nil {
dbQuery = dbQuery.Where("knowledge_id = ?", *op.knowledgeID)
}
if op.titleKeyword != nil && *op.titleKeyword != "" {
like := "%" + *op.titleKeyword + "%"
dbQuery = dbQuery.Where("(title LIKE ? OR content LIKE ?)", like, like)
}
if op.docType != nil && *op.docType != "" {
dbQuery = dbQuery.Where("type = ?", *op.docType)
}
if len(op.tags) > 0 {
for _, tag := range op.tags {
dbQuery = dbQuery.Where("JSON_CONTAINS(tags, ?)", "\""+tag+"\"")
}
}
if op.createTimeRangeStart != nil && *op.createTimeRangeStart != "" {
dbQuery = dbQuery.Where("created_at >= ?", *op.createTimeRangeStart)
}
if op.createTimeRangeEnd != nil && *op.createTimeRangeEnd != "" {
dbQuery = dbQuery.Where("created_at <= ?", *op.createTimeRangeEnd)
}
if op.updateTimeRangeStart != nil && *op.updateTimeRangeStart != "" {
dbQuery = dbQuery.Where("updated_at >= ?", *op.updateTimeRangeStart)
}
if op.updateTimeRangeEnd != nil && *op.updateTimeRangeEnd != "" {
dbQuery = dbQuery.Where("updated_at <= ?", *op.updateTimeRangeEnd)
}
return dbQuery
}
func (op *DocumentsListOperation) appendOtherArgs(db *gorm.DB) *gorm.DB {
dbQuery := db
sortField := op.sortField
if sortField == "" {
sortField = "created_at"
}
orderDirection := "ASC"
if op.sortDesc {
orderDirection = "DESC"
}
dbQuery = dbQuery.Order(sortField + " " + orderDirection)
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
dbQuery = dbQuery.Offset(offset).Limit(pageSize)
return dbQuery
}
func (op *DocumentsListOperation) Exec() ([]model.Document, error) {
dbQuery := op.buildBaseQuery()
dbQuery = op.appendOtherArgs(dbQuery)
var documents []model.Document
err := dbQuery.Find(&documents).Error
if err != nil {
return nil, err
}
return documents, nil
}
func (op *DocumentsListOperation) ExecWithTotal() ([]model.Document, int64, error) {
dbQuery := op.buildBaseQuery()
var total int64
err := dbQuery.Count(&total).Error
if err != nil {
return nil, 0, err
}
dbQuery = op.appendOtherArgs(dbQuery)
var documents []model.Document
err = dbQuery.Find(&documents).Error
if err != nil {
return nil, 0, err
}
return documents, total, nil
}
@@ -0,0 +1,82 @@
package document_repo
import (
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListRecentDocumentsOperation struct {
repo *DocumentRepository
userID int64
startTime *time.Time
endTime *time.Time
page int
pageSize int
tx *gorm.DB
}
func (r *DocumentRepository) ListRecentDocuments(userID int64) *ListRecentDocumentsOperation {
return &ListRecentDocumentsOperation{
repo: r,
userID: userID,
}
}
func (op *ListRecentDocumentsOperation) WithTimeRange(start, end *time.Time) *ListRecentDocumentsOperation {
op.startTime = start
op.endTime = end
return op
}
func (op *ListRecentDocumentsOperation) WithPagination(page, pageSize int) *ListRecentDocumentsOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *ListRecentDocumentsOperation) WithTx(tx *gorm.DB) *ListRecentDocumentsOperation {
op.tx = tx
return op
}
func (op *ListRecentDocumentsOperation) Exec() ([]*model.RecentDocument, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.RecentDocument{}).
Where("user_id = ?", op.userID)
if op.startTime != nil {
query = query.Where("accessed_at >= ?", *op.startTime)
}
if op.endTime != nil {
query = query.Where("accessed_at <= ?", *op.endTime)
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var records []*model.RecentDocument
if err := query.Order("accessed_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
@@ -0,0 +1,29 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
)
func (r *DocumentRepository) MGetByIDs(ids []int64) ([]*model.Document, error) {
if len(ids) == 0 {
return []*model.Document{}, nil
}
var docs []*model.Document
if err := r.db.Model(&model.Document{}).Where("id IN ?", ids).Find(&docs).Error; err != nil {
return nil, err
}
docMap := make(map[int64]*model.Document, len(docs))
for _, doc := range docs {
docMap[doc.ID] = doc
}
ordered := make([]*model.Document, 0, len(ids))
for _, id := range ids {
if doc, ok := docMap[id]; ok {
ordered = append(ordered, doc)
}
}
return ordered, nil
}
@@ -0,0 +1,78 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentMoveSubtreeOperation struct {
repo *DocumentRepository
docID int64
newParentID int64
tx *gorm.DB
}
func (r *DocumentRepository) MoveSubtree(docID, newParentID int64) *DocumentMoveSubtreeOperation {
return &DocumentMoveSubtreeOperation{
repo: r,
docID: docID,
newParentID: newParentID,
}
}
func (op *DocumentMoveSubtreeOperation) WithTx(tx *gorm.DB) *DocumentMoveSubtreeOperation {
op.tx = tx
return op
}
func (op *DocumentMoveSubtreeOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
type pathDepth struct {
Path string
Depth int
}
var old pathDepth
if err := op.tx.Model(&model.Document{}).
Select("path, depth").
Where("id = ? AND deleted_at IS NULL", op.docID).
Take(&old).Error; err != nil {
return err
}
var newPath string
var newDepth int
if op.newParentID == 0 {
newDepth = 0
} else {
var parent pathDepth
if err := op.tx.Model(&model.Document{}).
Select("path, depth").
Where("id = ? AND deleted_at IS NULL", op.newParentID).
Take(&parent).Error; err != nil {
return err
}
newPath = parent.Path
newDepth = parent.Depth + 1
}
depthDelta := newDepth - old.Depth
if op.newParentID == 0 {
return op.tx.Exec(`
UPDATE document_metas
SET path = (?::text)::ltree || COALESCE(subpath(path, nlevel(?::ltree)), ''::ltree),
depth = depth + ?
WHERE path <@ ?::ltree AND deleted_at IS NULL
`, op.docID, old.Path, depthDelta, old.Path).Error
}
return op.tx.Exec(`
UPDATE document_metas
SET path = (?::ltree) || (?::text)::ltree || COALESCE(subpath(path, nlevel(?::ltree)), ''::ltree),
depth = depth + ?
WHERE path <@ ?::ltree AND deleted_at IS NULL
`, newPath, op.docID, old.Path, depthDelta, old.Path).Error
}
@@ -0,0 +1,137 @@
package document_repo
import (
"context"
"strconv"
"strings"
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/cache"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/redis/go-redis/v9"
"github.com/sirupsen/logrus"
"golang.org/x/sync/singleflight"
"gorm.io/gorm"
)
const (
subtreeCacheTTL = 5 * time.Minute
subtreeEmptyTTL = 1 * time.Minute
subtreeCacheMaxDepth = 3
)
var subtreeCacheSF singleflight.Group
func splitPathPrefixes(path string) []string {
parts := strings.Split(path, ".")
out := make([]string, 0, len(parts))
for i := range parts {
out = append(out, strings.Join(parts[:i+1], "."))
}
return out
}
func getDocPathByID(tx *gorm.DB, docID int64) (string, error) {
type docPath struct {
Path string
}
var result docPath
err := tx.Model(&model.Document{}).
Select("path").
Where("id = ?", docID).
Take(&result).Error
if err != nil {
return "", err
}
return result.Path, nil
}
func (r *DocumentRepository) GetPathByID(id int64) (string, error) {
return getDocPathByID(r.db, id)
}
func (r *DocumentRepository) GetPathByIDWithTx(tx *gorm.DB, id int64) (string, error) {
return getDocPathByID(tx, id)
}
func (r *DocumentRepository) getSubtreeVersion(ctx context.Context, path string) (int64, error) {
if r.redis == nil {
return 0, nil
}
val, err := r.redis.Get(ctx, key.KeyDocSubtreeVersion(path)).Result()
if err == redis.Nil {
return 0, nil
}
if err != nil {
return 0, err
}
version, err := strconv.ParseInt(val, 10, 64)
if err != nil {
return 0, nil
}
return version, nil
}
func (r *DocumentRepository) BumpSubtreeVersionsByPath(ctx context.Context, path string) error {
if r.redis == nil {
return nil
}
prefixes := splitPathPrefixes(path)
pipe := r.redis.Pipeline()
for _, prefix := range prefixes {
pipe.Incr(ctx, key.KeyDocSubtreeVersion(prefix))
}
_, err := pipe.Exec(ctx)
if err == redis.Nil {
return nil
}
return err
}
func getCachedSubtreeIDs(ctx context.Context, key string) ([]int64, bool, error) {
var ids []int64
ok, err := cache.GetJSON(ctx, key, &ids)
if err != nil {
return nil, false, err
}
if !ok {
return nil, false, nil
}
return ids, true, nil
}
func setCachedSubtreeIDs(ctx context.Context, key string, ids []int64) {
ttl := subtreeCacheTTL
if len(ids) == 0 {
ttl = subtreeEmptyTTL
}
if err := cache.SetJSON(ctx, key, ids, ttl); err != nil {
logrus.Warnf("set subtree cache failed, key=%s, err=%v", key, err)
}
}
func (r *DocumentRepository) fetchDocumentsByIDs(ids []int64) ([]*model.Document, error) {
if len(ids) == 0 {
return []*model.Document{}, nil
}
var docs []*model.Document
if err := r.db.Model(&model.Document{}).Where("id IN ?", ids).Find(&docs).Error; err != nil {
return nil, err
}
docMap := make(map[int64]*model.Document, len(docs))
for _, doc := range docs {
docMap[doc.ID] = doc
}
ordered := make([]*model.Document, 0, len(ids))
for _, id := range ids {
if doc, ok := docMap[id]; ok {
ordered = append(ordered, doc)
}
}
return ordered, nil
}
@@ -0,0 +1,84 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentUpdateOperation struct {
repo *DocumentRepository
doc *model.Document
updateFields map[string]bool
tx *gorm.DB
}
func (r *DocumentRepository) Update(doc *model.Document) *DocumentUpdateOperation {
return &DocumentUpdateOperation{
repo: r,
doc: doc,
updateFields: make(map[string]bool),
}
}
func (op *DocumentUpdateOperation) UpdateTitle() *DocumentUpdateOperation {
op.updateFields["title"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateSummary() *DocumentUpdateOperation {
op.updateFields["summary"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateContent() *DocumentUpdateOperation {
op.updateFields["content"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateParentID() *DocumentUpdateOperation {
op.updateFields["parent_id"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateKnowledgeID() *DocumentUpdateOperation {
op.updateFields["knowledge_id"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateContainerType() *DocumentUpdateOperation {
op.updateFields["container_type"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateIsPublic() *DocumentUpdateOperation {
op.updateFields["is_public"] = true
return op
}
func (op *DocumentUpdateOperation) UpdateTags() *DocumentUpdateOperation {
op.updateFields["tags"] = true
return op
}
func (op *DocumentUpdateOperation) WithTx(tx *gorm.DB) *DocumentUpdateOperation {
op.tx = tx
return op
}
func (op *DocumentUpdateOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
query := op.tx.Model(op.doc)
if len(op.updateFields) > 0 {
fields := make([]string, 0, len(op.updateFields))
for field := range op.updateFields {
fields = append(fields, field)
}
query = query.Select(fields)
}
return query.Updates(*op.doc).Error
}
@@ -0,0 +1,48 @@
package document_repo
import (
"context"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/cache"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type DocumentUpdateContentByExternalIDOperation struct {
repo *DocumentRepository
externalID string
content string
tx *gorm.DB
}
func (r *DocumentRepository) UpdateContentByExternalID(externalID, content string) *DocumentUpdateContentByExternalIDOperation {
return &DocumentUpdateContentByExternalIDOperation{
repo: r,
externalID: externalID,
content: content,
}
}
func (op *DocumentUpdateContentByExternalIDOperation) WithTx(tx *gorm.DB) *DocumentUpdateContentByExternalIDOperation {
op.tx = tx
return op
}
func (op *DocumentUpdateContentByExternalIDOperation) Exec(ctx context.Context) error {
if op.tx == nil {
op.tx = op.repo.db
}
docModelCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_Doc, op.externalID)
return cache.DoubleDelete(
context.Background(),
func() error {
return op.tx.WithContext(ctx).Model(&model.Document{}).
Where("external_id = ?", op.externalID).
Select("content").
Updates(map[string]interface{}{"content": op.content}).Error
},
docModelCacheKey,
)
}
@@ -0,0 +1,58 @@
package document_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentUpdatePathDepthOperation struct {
repo *DocumentRepository
docID int64
parentID int64
tx *gorm.DB
}
func (r *DocumentRepository) UpdatePathDepth(docID, parentID int64) *DocumentUpdatePathDepthOperation {
return &DocumentUpdatePathDepthOperation{
repo: r,
docID: docID,
parentID: parentID,
}
}
func (op *DocumentUpdatePathDepthOperation) WithTx(tx *gorm.DB) *DocumentUpdatePathDepthOperation {
op.tx = tx
return op
}
func (op *DocumentUpdatePathDepthOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
if op.parentID == 0 {
return op.tx.Exec(`
UPDATE document_metas
SET path = (id::text)::ltree, depth = 0
WHERE id = ? AND deleted_at IS NULL
`, op.docID).Error
}
type pathDepth struct {
Path string
Depth int
}
var parent pathDepth
if err := op.tx.Model(&model.Document{}).
Select("path, depth").
Where("id = ? AND deleted_at IS NULL", op.parentID).
Take(&parent).Error; err != nil {
return err
}
return op.tx.Exec(`
UPDATE document_metas
SET path = (?::ltree) || (id::text)::ltree, depth = ?
WHERE id = ? AND deleted_at IS NULL
`, parent.Path, parent.Depth+1, op.docID).Error
}
@@ -0,0 +1,43 @@
package document_version_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentVersionCreateOperation struct {
repo *DocumentVersionRepository
docVersion *model.DocumentVersion
tx *gorm.DB
}
func (repo *DocumentVersionRepository) Create(docVersion *model.DocumentVersion) *DocumentVersionCreateOperation {
return &DocumentVersionCreateOperation{
repo: repo,
docVersion: docVersion,
}
}
func (op *DocumentVersionCreateOperation) WithTx(tx *gorm.DB) *DocumentVersionCreateOperation {
op.tx = tx
return op
}
func (op *DocumentVersionCreateOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
var maxVersion int
err := op.tx.Model(&model.DocumentVersion{}).
Where("document_id = ?", op.docVersion.DocumentID).
Select("COALESCE(MAX(version), 0)").
Scan(&maxVersion).Error
if err != nil {
return err
}
op.docVersion.Version = maxVersion + 1
return op.tx.Create(op.docVersion).Error
}
@@ -0,0 +1,11 @@
package document_version_repo
import "gorm.io/gorm"
type DocumentVersionRepository struct {
db *gorm.DB
}
func NewDocumentVersionRepository(db *gorm.DB) *DocumentVersionRepository {
return &DocumentVersionRepository{db: db}
}
@@ -0,0 +1,13 @@
package document_version_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
)
func (r *DocumentVersionRepository) GetByDocumentIDAndVersion(documentID int64, version int) (*model.DocumentVersion, error) {
item := &model.DocumentVersion{}
if err := r.db.Where("document_id = ? AND version = ?", documentID, version).First(item).Error; err != nil {
return nil, err
}
return item, nil
}
@@ -0,0 +1,41 @@
package document_version_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DocumentVersionGetLastedOperation struct {
repo *DocumentVersionRepository
docID int64
tx *gorm.DB
}
func (r *DocumentVersionRepository) GetLasted(docID int64) *DocumentVersionGetLastedOperation {
return &DocumentVersionGetLastedOperation{
repo: r,
docID: docID,
}
}
func (op *DocumentVersionGetLastedOperation) WithTx(tx *gorm.DB) *DocumentVersionGetLastedOperation {
op.tx = tx
return op
}
func (op *DocumentVersionGetLastedOperation) Exec() (int64, error) {
if op.tx == nil {
op.tx = op.repo.db
}
var maxVersion int64
err := op.tx.Model(&model.DocumentVersion{}).
Where("document_id = ?", op.docID).
Select("COALESCE(MAX(version), 0)").
Scan(&maxVersion).Error
if err != nil {
return 0, err
}
return maxVersion, nil
}
@@ -0,0 +1,19 @@
package document_version_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
)
func (r *DocumentVersionRepository) ListByDocumentID(documentID int64, offset, limit int) ([]*model.DocumentVersion, int64, error) {
var total int64
query := r.db.Model(&model.DocumentVersion{}).Where("document_id = ?", documentID)
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
items := make([]*model.DocumentVersion, 0)
if err := query.Order("version DESC").Offset(offset).Limit(limit).Find(&items).Error; err != nil {
return nil, 0, err
}
return items, total, nil
}
@@ -0,0 +1,86 @@
package doc_yjs_snapshot_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type DocumentYjsSnapshotRepository struct {
db *gorm.DB
}
func NewDocumentYjsSnapshotRepository(db *gorm.DB) *DocumentYjsSnapshotRepository {
return &DocumentYjsSnapshotRepository{db: db}
}
type DocumentYjsSnapshotGetOperation struct {
repo *DocumentYjsSnapshotRepository
docID int64
tx *gorm.DB
}
func (r *DocumentYjsSnapshotRepository) GetByDocID(docID int64) *DocumentYjsSnapshotGetOperation {
return &DocumentYjsSnapshotGetOperation{
repo: r,
docID: docID,
}
}
func (op *DocumentYjsSnapshotGetOperation) WithTx(tx *gorm.DB) *DocumentYjsSnapshotGetOperation {
op.tx = tx
return op
}
func (op *DocumentYjsSnapshotGetOperation) Exec() (*model.DocumentYjsSnapshot, error) {
if op.tx == nil {
op.tx = op.repo.db
}
var snapshot model.DocumentYjsSnapshot
if err := op.tx.First(&snapshot, "doc_id = ?", op.docID).Error; err != nil {
return nil, err
}
return &snapshot, nil
}
type DocumentYjsSnapshotSaveOperation struct {
repo *DocumentYjsSnapshotRepository
docID int64
state []byte
tx *gorm.DB
}
func (r *DocumentYjsSnapshotRepository) Save(docID int64, state []byte) *DocumentYjsSnapshotSaveOperation {
return &DocumentYjsSnapshotSaveOperation{
repo: r,
docID: docID,
state: state,
}
}
func (op *DocumentYjsSnapshotSaveOperation) WithTx(tx *gorm.DB) *DocumentYjsSnapshotSaveOperation {
op.tx = tx
return op
}
func (op *DocumentYjsSnapshotSaveOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
snapshot := &model.DocumentYjsSnapshot{
DocID: op.docID,
YjsState: op.state,
Version: 1,
}
return op.tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "doc_id"}},
DoUpdates: clause.Assignments(map[string]interface{}{
"yjs_state": op.state,
"version": gorm.Expr("documents_yjs_snapshot.version + 1"),
"updated_at": gorm.Expr("NOW()"),
}),
}).Create(snapshot).Error
}
+51
View File
@@ -0,0 +1,51 @@
package repository
import (
"github.com/XingfenD/yoresee_doc/internal/repository/attachment_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/comment_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/document_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/document_version_repo"
doc_yjs_snapshot_repo "github.com/XingfenD/yoresee_doc/internal/repository/document_yjs_snapshot_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/invitation_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/knowledge_base_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/membership_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/notification_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/template_repo"
"github.com/XingfenD/yoresee_doc/internal/repository/user_repo"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type Repositories struct {
DB *gorm.DB
Redis *redis.Client
Document *document_repo.DocumentRepository
KnowledgeBase *knowledge_base_repo.KnowledgeBaseRepository
User *user_repo.UserRepository
Comment *comment_repo.CommentRepository
Invitation *invitation_repo.InvitationRepository
Membership *membership_repo.MembershipRepository
Template *template_repo.TemplateRepository
Attachment *attachment_repo.AttachmentRepository
DocumentVersion *document_version_repo.DocumentVersionRepository
Notification *notification_repo.NotificationRepository
DocumentYjsSnapshot *doc_yjs_snapshot_repo.DocumentYjsSnapshotRepository
}
func NewRepositories(db *gorm.DB, redis *redis.Client) *Repositories {
return &Repositories{
DB: db,
Redis: redis,
Document: document_repo.NewDocumentRepository(db, redis),
KnowledgeBase: knowledge_base_repo.NewKnowledgeBaseRepository(db, redis),
User: user_repo.NewUserRepository(db, redis),
Comment: comment_repo.NewCommentRepository(db),
Invitation: invitation_repo.NewInvitationRepository(db),
Membership: membership_repo.NewMembershipRepository(db),
Template: template_repo.NewTemplateRepository(db),
Attachment: attachment_repo.NewAttachmentRepository(db),
DocumentVersion: document_version_repo.NewDocumentVersionRepository(db),
Notification: notification_repo.NewNotificationRepository(db),
DocumentYjsSnapshot: doc_yjs_snapshot_repo.NewDocumentYjsSnapshotRepository(db),
}
}
+112
View File
@@ -0,0 +1,112 @@
package invitation_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type InvitationGetByIDOperation struct {
repo *InvitationRepository
id int64
tx *gorm.DB
}
func (r *InvitationRepository) GetByID(id int64) *InvitationGetByIDOperation {
return &InvitationGetByIDOperation{
repo: r,
id: id,
}
}
func (op *InvitationGetByIDOperation) WithTx(tx *gorm.DB) *InvitationGetByIDOperation {
op.tx = tx
return op
}
func (op *InvitationGetByIDOperation) Exec() (*model.Invitation, error) {
var invitation model.Invitation
var err error
if op.tx != nil {
err = op.tx.First(&invitation, op.id).Error
} else {
err = op.repo.db.First(&invitation, op.id).Error
}
return &invitation, err
}
type InvitationCreateOperation struct {
repo *InvitationRepository
invitation *model.Invitation
tx *gorm.DB
}
func (r *InvitationRepository) Create(invitation *model.Invitation) *InvitationCreateOperation {
return &InvitationCreateOperation{
repo: r,
invitation: invitation,
}
}
func (op *InvitationCreateOperation) WithTx(tx *gorm.DB) *InvitationCreateOperation {
op.tx = tx
return op
}
func (op *InvitationCreateOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.invitation).Error
}
return op.repo.db.Create(op.invitation).Error
}
type InvitationUpdateOperation struct {
repo *InvitationRepository
invitation *model.Invitation
tx *gorm.DB
}
func (r *InvitationRepository) Update(invitation *model.Invitation) *InvitationUpdateOperation {
return &InvitationUpdateOperation{
repo: r,
invitation: invitation,
}
}
func (op *InvitationUpdateOperation) WithTx(tx *gorm.DB) *InvitationUpdateOperation {
op.tx = tx
return op
}
func (op *InvitationUpdateOperation) Exec() error {
if op.tx != nil {
return op.tx.Save(op.invitation).Error
}
return op.repo.db.Save(op.invitation).Error
}
type InvitationDeleteOperation struct {
repo *InvitationRepository
id int64
tx *gorm.DB
}
func (r *InvitationRepository) Delete(id int64) *InvitationDeleteOperation {
return &InvitationDeleteOperation{
repo: r,
id: id,
}
}
func (op *InvitationDeleteOperation) WithTx(tx *gorm.DB) *InvitationDeleteOperation {
op.tx = tx
return op
}
func (op *InvitationDeleteOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
return op.tx.Delete(&model.Invitation{}, op.id).Error
}
@@ -0,0 +1,37 @@
package invitation_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type InvitationGetByCodeOperation struct {
repo *InvitationRepository
code string
tx *gorm.DB
}
func (r *InvitationRepository) GetByCode(code string) *InvitationGetByCodeOperation {
return &InvitationGetByCodeOperation{
repo: r,
code: code,
}
}
func (op *InvitationGetByCodeOperation) WithTx(tx *gorm.DB) *InvitationGetByCodeOperation {
op.tx = tx
return op
}
func (op *InvitationGetByCodeOperation) Exec() (*model.Invitation, error) {
var invitation model.Invitation
var err error
if op.tx != nil {
err = op.tx.Where("code = ?", op.code).First(&invitation).Error
} else {
err = op.repo.db.Where("code = ?", op.code).First(&invitation).Error
}
return &invitation, err
}
@@ -0,0 +1,11 @@
package invitation_repo
import "gorm.io/gorm"
type InvitationRepository struct {
db *gorm.DB
}
func NewInvitationRepository(db *gorm.DB) *InvitationRepository {
return &InvitationRepository{db: db}
}
+234
View File
@@ -0,0 +1,234 @@
package invitation_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type InvitationListOperation struct {
repo *InvitationRepository
m *model.Invitation
creatorID *int64
keyword *string
maxUsedCnt *int64
expiresAtStart *string
expiresAtEnd *string
createdAtStart *string
createdAtEnd *string
disabled *bool
sortField string
sortDesc bool
page int
pageSize int
tx *gorm.DB
}
func (r *InvitationRepository) List(m *model.Invitation) *InvitationListOperation {
return &InvitationListOperation{
repo: r,
m: m,
}
}
func (op *InvitationListOperation) WithTx(tx *gorm.DB) *InvitationListOperation {
op.tx = tx
return op
}
func (op *InvitationListOperation) WithCreatorID(creatorID *int64) *InvitationListOperation {
op.creatorID = creatorID
return op
}
func (op *InvitationListOperation) WithKeyword(keyword *string) *InvitationListOperation {
op.keyword = keyword
return op
}
func (op *InvitationListOperation) WithMaxUsedCnt(maxUsedCnt *int64) *InvitationListOperation {
op.maxUsedCnt = maxUsedCnt
return op
}
func (op *InvitationListOperation) WithExpiresAtRange(start, end *string) *InvitationListOperation {
op.expiresAtStart = start
op.expiresAtEnd = end
return op
}
func (op *InvitationListOperation) WithCreatedAtRange(start, end *string) *InvitationListOperation {
op.createdAtStart = start
op.createdAtEnd = end
return op
}
func (op *InvitationListOperation) WithDisabled(disabled *bool) *InvitationListOperation {
op.disabled = disabled
return op
}
func (op *InvitationListOperation) WithSort(field string, desc bool) *InvitationListOperation {
op.sortField = field
op.sortDesc = desc
return op
}
func (op *InvitationListOperation) WithPagination(page, pageSize int) *InvitationListOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *InvitationListOperation) Exec() ([]model.Invitation, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
dbQuery := db.Model(&model.Invitation{})
if op.m != nil {
dbQuery = dbQuery.Where(op.m)
}
if op.creatorID != nil {
dbQuery = dbQuery.Where("created_by = ?", *op.creatorID)
}
if op.keyword != nil && *op.keyword != "" {
dbQuery = dbQuery.Where("code ILIKE ?", "%"+*op.keyword+"%")
}
if op.maxUsedCnt != nil {
dbQuery = dbQuery.Where("max_used_cnt = ?", *op.maxUsedCnt)
}
if op.expiresAtStart != nil && *op.expiresAtStart != "" {
dbQuery = dbQuery.Where("expires_at >= ?", *op.expiresAtStart)
}
if op.expiresAtEnd != nil && *op.expiresAtEnd != "" {
dbQuery = dbQuery.Where("expires_at <= ?", *op.expiresAtEnd)
}
if op.createdAtStart != nil && *op.createdAtStart != "" {
dbQuery = dbQuery.Where("created_at >= ?", *op.createdAtStart)
}
if op.createdAtEnd != nil && *op.createdAtEnd != "" {
dbQuery = dbQuery.Where("created_at <= ?", *op.createdAtEnd)
}
if op.disabled != nil {
dbQuery = dbQuery.Where("disabled = ?", *op.disabled)
}
sortField := op.sortField
if sortField == "" {
sortField = "created_at"
}
orderDirection := "ASC"
if op.sortDesc {
orderDirection = "DESC"
}
dbQuery = dbQuery.Order(sortField + " " + orderDirection)
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
dbQuery = dbQuery.Offset(offset).Limit(pageSize)
var invitations []model.Invitation
err := dbQuery.Find(&invitations).Error
if err != nil {
return nil, err
}
return invitations, nil
}
func (op *InvitationListOperation) ExecWithTotal() ([]model.Invitation, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
dbQuery := db.Model(&model.Invitation{})
if op.m != nil {
dbQuery = dbQuery.Where(op.m)
}
if op.creatorID != nil {
dbQuery = dbQuery.Where("created_by = ?", *op.creatorID)
}
if op.keyword != nil && *op.keyword != "" {
dbQuery = dbQuery.Where("code ILIKE ?", "%"+*op.keyword+"%")
}
if op.maxUsedCnt != nil {
dbQuery = dbQuery.Where("max_used_cnt = ?", *op.maxUsedCnt)
}
if op.expiresAtStart != nil && *op.expiresAtStart != "" {
dbQuery = dbQuery.Where("expires_at >= ?", *op.expiresAtStart)
}
if op.expiresAtEnd != nil && *op.expiresAtEnd != "" {
dbQuery = dbQuery.Where("expires_at <= ?", *op.expiresAtEnd)
}
if op.createdAtStart != nil && *op.createdAtStart != "" {
dbQuery = dbQuery.Where("created_at >= ?", *op.createdAtStart)
}
if op.createdAtEnd != nil && *op.createdAtEnd != "" {
dbQuery = dbQuery.Where("created_at <= ?", *op.createdAtEnd)
}
if op.disabled != nil {
dbQuery = dbQuery.Where("disabled = ?", *op.disabled)
}
var total int64
err := dbQuery.Count(&total).Error
if err != nil {
return nil, 0, err
}
sortField := op.sortField
if sortField == "" {
sortField = "created_at"
}
orderDirection := "ASC"
if op.sortDesc {
orderDirection = "DESC"
}
dbQuery = dbQuery.Order(sortField + " " + orderDirection)
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
dbQuery = dbQuery.Offset(offset).Limit(pageSize)
var invitations []model.Invitation
err = dbQuery.Find(&invitations).Error
if err != nil {
return nil, 0, err
}
return invitations, total, nil
}
@@ -0,0 +1,142 @@
package invitation_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type InvitationRecordCreateOperation struct {
repo *InvitationRepository
record *model.InvitationRecord
tx *gorm.DB
}
func (r *InvitationRepository) CreateRecord(record *model.InvitationRecord) *InvitationRecordCreateOperation {
return &InvitationRecordCreateOperation{
repo: r,
record: record,
}
}
func (op *InvitationRecordCreateOperation) WithTx(tx *gorm.DB) *InvitationRecordCreateOperation {
op.tx = tx
return op
}
func (op *InvitationRecordCreateOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.record).Error
}
return op.repo.db.Create(op.record).Error
}
type InvitationRecordListOperation struct {
repo *InvitationRepository
code *string
status *string
usedAtStart *string
usedAtEnd *string
creatorID *int64
keyword *string
page int
pageSize int
tx *gorm.DB
}
func (r *InvitationRepository) ListRecords() *InvitationRecordListOperation {
return &InvitationRecordListOperation{
repo: r,
}
}
func (op *InvitationRecordListOperation) WithTx(tx *gorm.DB) *InvitationRecordListOperation {
op.tx = tx
return op
}
func (op *InvitationRecordListOperation) WithCode(code *string) *InvitationRecordListOperation {
op.code = code
return op
}
func (op *InvitationRecordListOperation) WithStatus(status *string) *InvitationRecordListOperation {
op.status = status
return op
}
func (op *InvitationRecordListOperation) WithUsedAtRange(start, end *string) *InvitationRecordListOperation {
op.usedAtStart = start
op.usedAtEnd = end
return op
}
func (op *InvitationRecordListOperation) WithCreatorID(creatorID *int64) *InvitationRecordListOperation {
op.creatorID = creatorID
return op
}
func (op *InvitationRecordListOperation) WithKeyword(keyword *string) *InvitationRecordListOperation {
op.keyword = keyword
return op
}
func (op *InvitationRecordListOperation) WithPagination(page, pageSize int) *InvitationRecordListOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *InvitationRecordListOperation) ExecWithTotal() ([]model.InvitationRecord, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.InvitationRecord{})
if op.creatorID != nil {
query = query.Joins("JOIN invitations ON invitations.code = invitation_records.code").
Where("invitations.created_by = ?", *op.creatorID)
}
if op.code != nil && *op.code != "" {
query = query.Where("code = ?", *op.code)
}
if op.keyword != nil && *op.keyword != "" {
keyword := "%" + *op.keyword + "%"
query = query.Where("invitation_records.code ILIKE ? OR invitation_records.used_by ILIKE ?", keyword, keyword)
}
if op.status != nil && *op.status != "" {
query = query.Where("status = ?", *op.status)
}
if op.usedAtStart != nil && *op.usedAtStart != "" {
query = query.Where("used_at >= ?", *op.usedAtStart)
}
if op.usedAtEnd != nil && *op.usedAtEnd != "" {
query = query.Where("used_at <= ?", *op.usedAtEnd)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 200 {
pageSize = 200
}
offset := (page - 1) * pageSize
query = query.Order("used_at DESC").Offset(offset).Limit(pageSize)
var records []model.InvitationRecord
if err := query.Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
@@ -0,0 +1,48 @@
package invitation_repo
import (
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type InvitationValidateAndUseOperation struct {
repo *InvitationRepository
code string
tx *gorm.DB
}
func (r *InvitationRepository) ValidateAndUse(code string) *InvitationValidateAndUseOperation {
return &InvitationValidateAndUseOperation{
repo: r,
code: code,
}
}
func (op *InvitationValidateAndUseOperation) WithTx(tx *gorm.DB) *InvitationValidateAndUseOperation {
op.tx = tx
return op
}
func (op *InvitationValidateAndUseOperation) Exec() (bool, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
now := time.Now()
result := db.Model(&model.Invitation{}).
Where("code = ? AND deleted_at IS NULL AND disabled = ?", op.code, false).
Where("(expires_at IS NULL OR expires_at > ?)", now).
Where("(max_used_cnt IS NULL OR used_cnt < max_used_cnt)").
UpdateColumn("used_cnt", gorm.Expr("used_cnt + ?", 1))
if result.Error != nil {
return false, result.Error
}
if result.RowsAffected == 0 {
return false, nil
}
return true, nil
}
@@ -0,0 +1,32 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CreateKnowledgeBaseOperation struct {
repo *KnowledgeBaseRepository
knowledgeBase *model.KnowledgeBase
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) Create(knowledgeBase *model.KnowledgeBase) (op *CreateKnowledgeBaseOperation) {
return &CreateKnowledgeBaseOperation{
repo: r,
knowledgeBase: knowledgeBase,
}
}
func (op *CreateKnowledgeBaseOperation) WithTx(tx *gorm.DB) *CreateKnowledgeBaseOperation {
op.tx = tx
return op
}
func (op *CreateKnowledgeBaseOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
return op.tx.Create(op.knowledgeBase).Error
}
@@ -0,0 +1,38 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type CreateRecentKnowledgeBaseOperation struct {
repo *KnowledgeBaseRepository
m *model.RecentKnowledgeBase
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) CreateRecentKnowledgeBase(m *model.RecentKnowledgeBase) *CreateRecentKnowledgeBaseOperation {
return &CreateRecentKnowledgeBaseOperation{
repo: r,
m: m,
}
}
func (op *CreateRecentKnowledgeBaseOperation) WithTx(tx *gorm.DB) {
op.tx = tx
}
func (op *CreateRecentKnowledgeBaseOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
return op.tx.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "user_id"},
{Name: "knowledge_base_id"},
},
DoUpdates: clause.AssignmentColumns([]string{"accessed_at"}),
}).Create(op.m).Error
}
@@ -0,0 +1,32 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DeleteKnowledgeBaseOperation struct {
repo *KnowledgeBaseRepository
knowledgeBase *model.KnowledgeBase
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) Delete(knowledgeBase *model.KnowledgeBase) (op *DeleteKnowledgeBaseOperation) {
return &DeleteKnowledgeBaseOperation{
repo: r,
knowledgeBase: knowledgeBase,
}
}
func (op *DeleteKnowledgeBaseOperation) WithTx(tx *gorm.DB) *DeleteKnowledgeBaseOperation {
op.tx = tx
return op
}
func (op *DeleteKnowledgeBaseOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
err := op.tx.Delete(op.knowledgeBase).Error
return err
}
@@ -0,0 +1,56 @@
package knowledge_base_repo
import (
"context"
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type GetKnowledgeBaseByExternalIDOperation struct {
repo *KnowledgeBaseRepository
externalID string
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) GetByExternalID(externalID string) (op *GetKnowledgeBaseByExternalIDOperation) {
return &GetKnowledgeBaseByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *GetKnowledgeBaseByExternalIDOperation) WithTx(tx *gorm.DB) *GetKnowledgeBaseByExternalIDOperation {
op.tx = tx
return op
}
func (op *GetKnowledgeBaseByExternalIDOperation) query(db *gorm.DB) (*model.KnowledgeBase, error) {
var knowledgeBase model.KnowledgeBase
err := db.First(&knowledgeBase, "external_id = ?", op.externalID).Error
return &knowledgeBase, err
}
func (op *GetKnowledgeBaseByExternalIDOperation) Exec() (*model.KnowledgeBase, error) {
if op.tx != nil {
return op.query(op.tx)
}
knowledgeBaseCacheKey := key.KeyModelByExternalID(key.KeyObjectTypeEnum_KnowledgeBase, op.externalID)
knowledgeBase, err := cache_loader.NewCacheLoadOperation[model.KnowledgeBase](&op.repo.Loader).
WithDBLoader(func() (*model.KnowledgeBase, error) {
return op.query(op.repo.db)
}).WithDefaultKeyAndParser(knowledgeBaseCacheKey, nil).
Exec(context.Background())
if err != nil {
logrus.Errorf("load data failed for GetKnowledgeBaseByExternalIDOperation: %+v", err)
return nil, err
}
return knowledgeBase, nil
}
@@ -0,0 +1,32 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type KnowledgeBaseGetByIDOperation struct {
repo *KnowledgeBaseRepository
id int64
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) GetByID(id int64) (op *KnowledgeBaseGetByIDOperation) {
return &KnowledgeBaseGetByIDOperation{
repo: r,
id: id,
}
}
func (op *KnowledgeBaseGetByIDOperation) WithTx(tx *gorm.DB) *KnowledgeBaseGetByIDOperation {
op.tx = tx
return op
}
func (op *KnowledgeBaseGetByIDOperation) Exec() (knowledgeBase *model.KnowledgeBase, err error) {
if op.tx == nil {
op.tx = op.repo.db
}
err = op.tx.First(knowledgeBase, "id = ?", op.id).Error
return
}
@@ -0,0 +1,36 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type KnowledgeBaseGetIDByExternalIDOperation struct {
repo *KnowledgeBaseRepository
externalID string
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) GetIDByExternalID(externalID string) (op *KnowledgeBaseGetIDByExternalIDOperation) {
return &KnowledgeBaseGetIDByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *KnowledgeBaseGetIDByExternalIDOperation) WithTx(tx *gorm.DB) *KnowledgeBaseGetIDByExternalIDOperation {
op.tx = tx
return op
}
func (op *KnowledgeBaseGetIDByExternalIDOperation) Exec() (int64, error) {
var id int64
if op.tx == nil {
op.tx = op.repo.db
}
err := op.tx.Model(&model.KnowledgeBase{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
if err != nil {
return 0, err
}
return id, nil
}
@@ -0,0 +1,19 @@
package knowledge_base_repo
import (
cache_loader "github.com/XingfenD/yoresee_doc/internal/cache"
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type KnowledgeBaseRepository struct {
db *gorm.DB
Loader cache_loader.Loader
}
func NewKnowledgeBaseRepository(db *gorm.DB, redis *redis.Client) *KnowledgeBaseRepository {
return &KnowledgeBaseRepository{
db: db,
Loader: *cache_loader.NewLoader(redis),
}
}
@@ -0,0 +1,154 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListKnowledgeBaseOperation struct {
repo *KnowledgeBaseRepository
model *model.KnowledgeBase
creatorID *int64
isPublic *bool
nameKeyword *string
createAtStart *string
createAtEnd *string
updateAtStart *string
updateAtEnd *string
sortField string
sortDesc bool
page int
pageSize int
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) List(m *model.KnowledgeBase) (op *ListKnowledgeBaseOperation) {
return &ListKnowledgeBaseOperation{
repo: r,
model: m,
}
}
func (op *ListKnowledgeBaseOperation) WithTx(tx *gorm.DB) *ListKnowledgeBaseOperation {
op.tx = tx
return op
}
func (op *ListKnowledgeBaseOperation) WithCreatorID(creatorID *int64) *ListKnowledgeBaseOperation {
op.creatorID = creatorID
return op
}
func (op *ListKnowledgeBaseOperation) WithIsPublic(isPublic *bool) *ListKnowledgeBaseOperation {
op.isPublic = isPublic
return op
}
func (op *ListKnowledgeBaseOperation) WithNameKeyword(nameKeyword *string) *ListKnowledgeBaseOperation {
op.nameKeyword = nameKeyword
return op
}
func (op *ListKnowledgeBaseOperation) WithCreateTimeRange(start, end *string) *ListKnowledgeBaseOperation {
op.createAtStart = start
op.createAtEnd = end
return op
}
func (op *ListKnowledgeBaseOperation) WithUpdateTimeRange(start, end *string) *ListKnowledgeBaseOperation {
op.updateAtStart = start
op.updateAtEnd = end
return op
}
func (op *ListKnowledgeBaseOperation) WithSort(field string, desc bool) *ListKnowledgeBaseOperation {
op.sortField = field
op.sortDesc = desc
return op
}
func (op *ListKnowledgeBaseOperation) WithPagination(page, pageSize int) *ListKnowledgeBaseOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *ListKnowledgeBaseOperation) Exec() (kbs []*model.KnowledgeBase, err error) {
if op.tx == nil {
op.tx = op.repo.db
}
dbQuery := op.tx.Model(op.model)
if op.model != nil {
dbQuery = dbQuery.Where(op.model)
}
if op.creatorID != nil {
dbQuery = dbQuery.Where("creator_user_id = ?", *op.creatorID)
}
if op.isPublic != nil {
dbQuery = dbQuery.Where("is_public = ?", *op.isPublic)
}
orderStr := "created_at DESC"
if op.sortField != "" {
if op.sortDesc {
orderStr = op.sortField + " DESC"
} else {
orderStr = op.sortField + " ASC"
}
}
dbQuery = dbQuery.Order(orderStr)
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
dbQuery = dbQuery.Offset(offset).Limit(op.pageSize)
}
err = dbQuery.Find(&kbs).Error
return
}
func (op *ListKnowledgeBaseOperation) ExecWithTotal() (kbs []*model.KnowledgeBase, total int64, err error) {
if op.tx == nil {
op.tx = op.repo.db
}
dbQuery := op.tx.Model(op.model)
if op.model != nil {
dbQuery = dbQuery.Where(op.model)
}
if op.creatorID != nil {
dbQuery = dbQuery.Where("creator_user_id = ?", *op.creatorID)
}
if op.isPublic != nil {
dbQuery = dbQuery.Where("is_public = ?", *op.isPublic)
}
err = dbQuery.Count(&total).Error
if err != nil {
return nil, 0, err
}
orderStr := "created_at DESC"
if op.sortField != "" {
if op.sortDesc {
orderStr = op.sortField + " DESC"
} else {
orderStr = op.sortField + " ASC"
}
}
dbQuery = dbQuery.Order(orderStr)
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
dbQuery = dbQuery.Offset(offset).Limit(op.pageSize)
}
err = dbQuery.Find(&kbs).Error
return
}
@@ -0,0 +1,83 @@
package knowledge_base_repo
import (
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListRecentKnowledgeBasesOperation struct {
repo *KnowledgeBaseRepository
userID int64
startTime *time.Time
endTime *time.Time
page int
pageSize int
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) ListRecentKnowledgeBases(userID int64) *ListRecentKnowledgeBasesOperation {
return &ListRecentKnowledgeBasesOperation{
repo: r,
userID: userID,
}
}
func (op *ListRecentKnowledgeBasesOperation) WithTimeRange(start, end *time.Time) *ListRecentKnowledgeBasesOperation {
op.startTime = start
op.endTime = end
return op
}
func (op *ListRecentKnowledgeBasesOperation) WithPagination(page, pageSize int) *ListRecentKnowledgeBasesOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *ListRecentKnowledgeBasesOperation) WithTx(tx *gorm.DB) *ListRecentKnowledgeBasesOperation {
op.tx = tx
return op
}
func (op *ListRecentKnowledgeBasesOperation) Exec() ([]*model.RecentKnowledgeBase, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.RecentKnowledgeBase{}).
Where("user_id = ?", op.userID)
if op.startTime != nil {
query = query.Where("accessed_at >= ?", *op.startTime)
}
if op.endTime != nil {
query = query.Where("accessed_at <= ?", *op.endTime)
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var records []*model.RecentKnowledgeBase
if err := query.Order("accessed_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
@@ -0,0 +1,39 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type MGetKnowledgeBaseByIDsOperation struct {
repo *KnowledgeBaseRepository
ids []int64
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) MGetKnowledgeBaseByIDs(ids []int64) *MGetKnowledgeBaseByIDsOperation {
return &MGetKnowledgeBaseByIDsOperation{
repo: r,
ids: ids,
}
}
func (op *MGetKnowledgeBaseByIDsOperation) WithTx(tx *gorm.DB) *MGetKnowledgeBaseByIDsOperation {
op.tx = tx
return op
}
func (op *MGetKnowledgeBaseByIDsOperation) Exec() ([]*model.KnowledgeBase, error) {
if len(op.ids) == 0 {
return []*model.KnowledgeBase{}, nil
}
db := op.repo.db
if op.tx != nil {
db = op.tx
}
var kbs []*model.KnowledgeBase
if err := db.Model(&model.KnowledgeBase{}).Where("id IN ?", op.ids).Find(&kbs).Error; err != nil {
return nil, err
}
return kbs, nil
}
@@ -0,0 +1,63 @@
package knowledge_base_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type MGetKnowledgeBaseDocumentsCountOperation struct {
repo *KnowledgeBaseRepository
knowledgeBaseIDs []int64
tx *gorm.DB
}
func (r *KnowledgeBaseRepository) MGetKnowledgeBaseDocumentsCount(knowledgeBaseIDs []int64) *MGetKnowledgeBaseDocumentsCountOperation {
return &MGetKnowledgeBaseDocumentsCountOperation{
repo: r,
knowledgeBaseIDs: knowledgeBaseIDs,
}
}
func (op *MGetKnowledgeBaseDocumentsCountOperation) WithTx(tx *gorm.DB) *MGetKnowledgeBaseDocumentsCountOperation {
op.tx = tx
return op
}
func (op *MGetKnowledgeBaseDocumentsCountOperation) Exec() (map[int64]int64, error) {
result := make(map[int64]int64)
if len(op.knowledgeBaseIDs) == 0 {
return result, nil
}
if op.tx == nil {
op.tx = op.repo.db
}
var counts []struct {
KnowledgeID int64
Count int64
}
err := op.tx.Model(&model.Document{}).
Select("knowledge_id, count(*) as count").
Where("knowledge_id IN ?", op.knowledgeBaseIDs).
Group("knowledge_id").
Find(&counts).Error
if err != nil {
return nil, err
}
for _, c := range counts {
result[c.KnowledgeID] = c.Count
}
for _, id := range op.knowledgeBaseIDs {
if _, exists := result[id]; !exists {
result[id] = 0
}
}
return result, nil
}
@@ -0,0 +1,56 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CreateUserGroupOperation struct {
repo *MembershipRepository
group *model.UserGroupMeta
tx *gorm.DB
}
func (r *MembershipRepository) CreateUserGroup(group *model.UserGroupMeta) *CreateUserGroupOperation {
return &CreateUserGroupOperation{
repo: r,
group: group,
}
}
func (op *CreateUserGroupOperation) WithTx(tx *gorm.DB) *CreateUserGroupOperation {
op.tx = tx
return op
}
func (op *CreateUserGroupOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.group).Error
}
return op.repo.db.Create(op.group).Error
}
type CreateOrgNodeOperation struct {
repo *MembershipRepository
orgNode *model.OrgNodeMeta
tx *gorm.DB
}
func (r *MembershipRepository) CreateOrgNode(orgNode *model.OrgNodeMeta) *CreateOrgNodeOperation {
return &CreateOrgNodeOperation{
repo: r,
orgNode: orgNode,
}
}
func (op *CreateOrgNodeOperation) WithTx(tx *gorm.DB) *CreateOrgNodeOperation {
op.tx = tx
return op
}
func (op *CreateOrgNodeOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.orgNode).Error
}
return op.repo.db.Create(op.orgNode).Error
}
@@ -0,0 +1,56 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type DeleteUserGroupOperation struct {
repo *MembershipRepository
group *model.UserGroupMeta
tx *gorm.DB
}
func (r *MembershipRepository) DeleteUserGroup(group *model.UserGroupMeta) *DeleteUserGroupOperation {
return &DeleteUserGroupOperation{
repo: r,
group: group,
}
}
func (op *DeleteUserGroupOperation) WithTx(tx *gorm.DB) *DeleteUserGroupOperation {
op.tx = tx
return op
}
func (op *DeleteUserGroupOperation) Exec() error {
if op.tx != nil {
return op.tx.Delete(op.group).Error
}
return op.repo.db.Delete(op.group).Error
}
type DeleteOrgNodeOperation struct {
repo *MembershipRepository
orgNode *model.OrgNodeMeta
tx *gorm.DB
}
func (r *MembershipRepository) DeleteOrgNode(orgNode *model.OrgNodeMeta) *DeleteOrgNodeOperation {
return &DeleteOrgNodeOperation{
repo: r,
orgNode: orgNode,
}
}
func (op *DeleteOrgNodeOperation) WithTx(tx *gorm.DB) *DeleteOrgNodeOperation {
op.tx = tx
return op
}
func (op *DeleteOrgNodeOperation) Exec() error {
if op.tx != nil {
return op.tx.Delete(op.orgNode).Error
}
return op.repo.db.Delete(op.orgNode).Error
}
@@ -0,0 +1,68 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type GetUserGroupByExternalIDOperation struct {
repo *MembershipRepository
externalID string
tx *gorm.DB
}
func (r *MembershipRepository) GetUserGroupByExternalID(externalID string) *GetUserGroupByExternalIDOperation {
return &GetUserGroupByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *GetUserGroupByExternalIDOperation) WithTx(tx *gorm.DB) *GetUserGroupByExternalIDOperation {
op.tx = tx
return op
}
func (op *GetUserGroupByExternalIDOperation) Exec() (*model.UserGroupMeta, error) {
var group model.UserGroupMeta
var err error
if op.tx != nil {
err = op.tx.Where("external_id = ?", op.externalID).First(&group).Error
} else {
err = op.repo.db.Where("external_id = ?", op.externalID).First(&group).Error
}
return &group, err
}
type GetOrgNodeByExternalID struct {
repo *MembershipRepository
externalID string
tx *gorm.DB
}
func (r *MembershipRepository) GetOrgNodeByExternalID(externalID string) *GetOrgNodeByExternalID {
return &GetOrgNodeByExternalID{
repo: r,
externalID: externalID,
}
}
func (op *GetOrgNodeByExternalID) WithTx(tx *gorm.DB) *GetOrgNodeByExternalID {
op.tx = tx
return op
}
func (op *GetOrgNodeByExternalID) Exec() (*model.OrgNodeMeta, error) {
var orgNode model.OrgNodeMeta
var err error
if op.tx != nil {
err = op.tx.Where("external_id = ?", op.externalID).First(&orgNode).Error
} else {
err = op.repo.db.Where("external_id = ?", op.externalID).First(&orgNode).Error
}
return &orgNode, err
}
@@ -0,0 +1,62 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type GetUserGroupIDByExternalIDOperation struct {
repo *MembershipRepository
externalID string
tx *gorm.DB
}
func (r *MembershipRepository) GetUserGroupIDByExternalID(externalID string) *GetUserGroupIDByExternalIDOperation {
return &GetUserGroupIDByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *GetUserGroupIDByExternalIDOperation) WithTx(tx *gorm.DB) *GetUserGroupIDByExternalIDOperation {
op.tx = tx
return op
}
func (op *GetUserGroupIDByExternalIDOperation) Exec() (int64, error) {
var id int64
var err error
if op.tx != nil {
err = op.tx.Model(&model.UserGroupMeta{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
}
err = op.repo.db.Model(&model.UserGroupMeta{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
return id, err
}
type GetOrgNodeIDByExternalIDOperation struct {
repo *MembershipRepository
externalID string
tx *gorm.DB
}
func (r *MembershipRepository) GetOrgNodeIDByExternalID(externalID string) *GetOrgNodeIDByExternalIDOperation {
return &GetOrgNodeIDByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *GetOrgNodeIDByExternalIDOperation) WithTx(tx *gorm.DB) *GetOrgNodeIDByExternalIDOperation {
op.tx = tx
return op
}
func (op *GetOrgNodeIDByExternalIDOperation) Exec() (int64, error) {
var id int64
var err error
if op.tx != nil {
err = op.tx.Model(&model.OrgNodeMeta{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
}
err = op.repo.db.Model(&model.OrgNodeMeta{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
return id, err
}
@@ -0,0 +1,41 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListMembershipByTypeAndIDsOperation struct {
repo *MembershipRepository
membershipType model.MembershipType
membershipIDs []int64
tx *gorm.DB
}
func (r *MembershipRepository) ListMembershipByTypeAndIDs(membershipType model.MembershipType, membershipIDs []int64) *ListMembershipByTypeAndIDsOperation {
return &ListMembershipByTypeAndIDsOperation{
repo: r,
membershipType: membershipType,
membershipIDs: membershipIDs,
}
}
func (op *ListMembershipByTypeAndIDsOperation) WithTx(tx *gorm.DB) *ListMembershipByTypeAndIDsOperation {
op.tx = tx
return op
}
func (op *ListMembershipByTypeAndIDsOperation) Exec() ([]model.MembershipRelation, error) {
if len(op.membershipIDs) == 0 {
return []model.MembershipRelation{}, nil
}
if op.tx == nil {
op.tx = op.repo.db
}
var memberships []model.MembershipRelation
err := op.tx.
Where("type = ? AND membership_id IN ?", op.membershipType, op.membershipIDs).
Find(&memberships).Error
return memberships, err
}
@@ -0,0 +1,35 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListUserGroupOperation struct {
repo *MembershipRepository
tx *gorm.DB
}
func (r *MembershipRepository) ListUserGroup() *ListUserGroupOperation {
return &ListUserGroupOperation{
repo: r,
}
}
func (op *ListUserGroupOperation) WithTx(tx *gorm.DB) *ListUserGroupOperation {
op.tx = tx
return op
}
func (op *ListUserGroupOperation) Exec() ([]model.UserGroupMeta, error) {
var userGroups []model.UserGroupMeta
var err error
if op.tx != nil {
err = op.tx.Find(&userGroups).Error
} else {
err = op.repo.db.Find(&userGroups).Error
}
return userGroups, err
}
@@ -0,0 +1,145 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type CreateMembershipOperation struct {
repo *MembershipRepository
membership *model.MembershipRelation
tx *gorm.DB
}
func (r *MembershipRepository) CreateMembership(membership *model.MembershipRelation) *CreateMembershipOperation {
return &CreateMembershipOperation{
repo: r,
membership: membership,
}
}
func (op *CreateMembershipOperation) WithTx(tx *gorm.DB) *CreateMembershipOperation {
op.tx = tx
return op
}
func (op *CreateMembershipOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.membership).Error
}
return op.repo.db.Create(op.membership).Error
}
type BatchCreateMembershipOperation struct {
repo *MembershipRepository
memberships []*model.MembershipRelation
tx *gorm.DB
}
func (r *MembershipRepository) BatchCreateMembership(memberships []*model.MembershipRelation) *BatchCreateMembershipOperation {
return &BatchCreateMembershipOperation{
repo: r,
memberships: memberships,
}
}
func (op *BatchCreateMembershipOperation) WithTx(tx *gorm.DB) *BatchCreateMembershipOperation {
op.tx = tx
return op
}
func (op *BatchCreateMembershipOperation) Exec() error {
if len(op.memberships) == 0 {
return nil
}
if op.tx != nil {
return op.tx.Create(&op.memberships).Error
}
return op.repo.db.Create(&op.memberships).Error
}
type ListMembershipOperation struct {
repo *MembershipRepository
query *model.MembershipRelation
tx *gorm.DB
}
func (r *MembershipRepository) ListMembership(query *model.MembershipRelation) *ListMembershipOperation {
return &ListMembershipOperation{
repo: r,
query: query,
}
}
func (op *ListMembershipOperation) WithTx(tx *gorm.DB) *ListMembershipOperation {
op.tx = tx
return op
}
func (op *ListMembershipOperation) Exec() ([]model.MembershipRelation, error) {
var memberships []model.MembershipRelation
var err error
if op.tx != nil {
err = op.tx.Where(op.query).Find(&memberships).Error
} else {
err = op.repo.db.Where(op.query).Find(&memberships).Error
}
return memberships, err
}
type DeleteMembershipOperation struct {
repo *MembershipRepository
membership *model.MembershipRelation
tx *gorm.DB
}
func (r *MembershipRepository) DeleteMembership(membership *model.MembershipRelation) *DeleteMembershipOperation {
return &DeleteMembershipOperation{
repo: r,
membership: membership,
}
}
func (op *DeleteMembershipOperation) WithTx(tx *gorm.DB) *DeleteMembershipOperation {
op.tx = tx
return op
}
func (op *DeleteMembershipOperation) Exec() error {
if op.tx != nil {
return op.tx.Delete(op.membership).Error
}
return op.repo.db.Delete(op.membership).Error
}
type BatchDeleteMembershipOperation struct {
repo *MembershipRepository
memberships []*model.MembershipRelation
tx *gorm.DB
}
func (r *MembershipRepository) BatchDeleteMembership(memberships []*model.MembershipRelation) *BatchDeleteMembershipOperation {
return &BatchDeleteMembershipOperation{
repo: r,
memberships: memberships,
}
}
func (op *BatchDeleteMembershipOperation) WithTx(tx *gorm.DB) *BatchDeleteMembershipOperation {
op.tx = tx
return op
}
func (op *BatchDeleteMembershipOperation) Exec() error {
if len(op.memberships) == 0 {
return nil
}
if op.tx != nil {
return op.tx.Delete(&op.memberships).Error
}
return op.repo.db.Delete(&op.memberships).Error
}
@@ -0,0 +1,11 @@
package membership_repo
import "gorm.io/gorm"
type MembershipRepository struct {
db *gorm.DB
}
func NewMembershipRepository(db *gorm.DB) *MembershipRepository {
return &MembershipRepository{db: db}
}
@@ -0,0 +1,172 @@
package membership_repo
import (
"strings"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type QueryOrgNodeOperation struct {
repo *MembershipRepository
tx *gorm.DB
parentID int64
keyword *string
page int
pageSize int
}
func (r *MembershipRepository) QueryOrgNode() *QueryOrgNodeOperation {
return &QueryOrgNodeOperation{
repo: r,
}
}
func (op *QueryOrgNodeOperation) WithTx(tx *gorm.DB) *QueryOrgNodeOperation {
op.tx = tx
return op
}
func (op *QueryOrgNodeOperation) WithParentID(parentID int64) *QueryOrgNodeOperation {
op.parentID = parentID
return op
}
func (op *QueryOrgNodeOperation) WithKeyword(keyword *string) *QueryOrgNodeOperation {
op.keyword = keyword
return op
}
func (op *QueryOrgNodeOperation) WithPagination(page, pageSize int) *QueryOrgNodeOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *QueryOrgNodeOperation) ExecWithTotal() ([]model.OrgNodeMeta, int64, error) {
if op.tx == nil {
op.tx = op.repo.db
}
query := op.tx.Model(&model.OrgNodeMeta{})
if op.parentID >= 0 {
query = query.Where("parent_id = ?", op.parentID)
}
if op.keyword != nil {
trimmed := strings.TrimSpace(*op.keyword)
if trimmed != "" {
like := "%" + trimmed + "%"
query = query.Where("name ILIKE ? OR description ILIKE ?", like, like)
}
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
query = query.Offset(offset).Limit(op.pageSize)
}
var orgNodes []model.OrgNodeMeta
if err := query.Order("id DESC").Find(&orgNodes).Error; err != nil {
return nil, 0, err
}
return orgNodes, total, nil
}
func (op *QueryOrgNodeOperation) Exec() ([]model.OrgNodeMeta, error) {
if op.tx == nil {
op.tx = op.repo.db
}
query := op.tx.Model(&model.OrgNodeMeta{})
if op.parentID >= 0 {
query = query.Where("parent_id = ?", op.parentID)
}
if op.keyword != nil {
trimmed := strings.TrimSpace(*op.keyword)
if trimmed != "" {
like := "%" + trimmed + "%"
query = query.Where("name ILIKE ? OR description ILIKE ?", like, like)
}
}
var orgNodes []model.OrgNodeMeta
if err := query.Order("id DESC").Find(&orgNodes).Error; err != nil {
return nil, err
}
return orgNodes, nil
}
type MGetOrgNodeByIDOperation struct {
repo *MembershipRepository
nodeIDs []int64
tx *gorm.DB
}
func (r *MembershipRepository) MGetOrgNodeByID(nodeIDs []int64) *MGetOrgNodeByIDOperation {
return &MGetOrgNodeByIDOperation{
repo: r,
nodeIDs: nodeIDs,
}
}
func (op *MGetOrgNodeByIDOperation) WithTx(tx *gorm.DB) *MGetOrgNodeByIDOperation {
op.tx = tx
return op
}
func (op *MGetOrgNodeByIDOperation) Exec() (map[int64]*model.OrgNodeMeta, error) {
if op.tx == nil {
op.tx = op.repo.db
}
var orgNodes []model.OrgNodeMeta
if err := op.tx.Where("id IN ?", op.nodeIDs).Find(&orgNodes).Error; err != nil {
return nil, err
}
result := make(map[int64]*model.OrgNodeMeta, len(orgNodes))
for i := range orgNodes {
result[orgNodes[i].ID] = &orgNodes[i]
}
return result, nil
}
type QueryOrgNodeByPathPrefixOperation struct {
repo *MembershipRepository
tx *gorm.DB
prefix string
}
func (r *MembershipRepository) QueryOrgNodeByPathPrefix(prefix string) *QueryOrgNodeByPathPrefixOperation {
return &QueryOrgNodeByPathPrefixOperation{
repo: r,
prefix: prefix,
}
}
func (op *QueryOrgNodeByPathPrefixOperation) WithTx(tx *gorm.DB) *QueryOrgNodeByPathPrefixOperation {
op.tx = tx
return op
}
func (op *QueryOrgNodeByPathPrefixOperation) Exec() ([]model.OrgNodeMeta, error) {
if op.tx == nil {
op.tx = op.repo.db
}
var orgNodes []model.OrgNodeMeta
prefix := strings.TrimSpace(op.prefix)
if prefix == "" {
return []model.OrgNodeMeta{}, nil
}
like := prefix + ".%"
if err := op.tx.Where("path = ? OR path LIKE ?", prefix, like).Find(&orgNodes).Error; err != nil {
return nil, err
}
return orgNodes, nil
}
@@ -0,0 +1,69 @@
package membership_repo
import (
"strings"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type QueryUserGroupOperation struct {
repo *MembershipRepository
tx *gorm.DB
keyword *string
page int
pageSize int
}
func (r *MembershipRepository) QueryUserGroup() *QueryUserGroupOperation {
return &QueryUserGroupOperation{
repo: r,
}
}
func (op *QueryUserGroupOperation) WithTx(tx *gorm.DB) *QueryUserGroupOperation {
op.tx = tx
return op
}
func (op *QueryUserGroupOperation) WithKeyword(keyword *string) *QueryUserGroupOperation {
op.keyword = keyword
return op
}
func (op *QueryUserGroupOperation) WithPagination(page, pageSize int) *QueryUserGroupOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *QueryUserGroupOperation) ExecWithTotal() ([]model.UserGroupMeta, int64, error) {
if op.tx == nil {
op.tx = op.repo.db
}
query := op.tx.Model(&model.UserGroupMeta{})
if op.keyword != nil {
trimmed := strings.TrimSpace(*op.keyword)
if trimmed != "" {
like := "%" + trimmed + "%"
query = query.Where("name ILIKE ? OR description ILIKE ?", like, like)
}
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
query = query.Offset(offset).Limit(op.pageSize)
}
var userGroups []model.UserGroupMeta
if err := query.Order("id DESC").Find(&userGroups).Error; err != nil {
return nil, 0, err
}
return userGroups, total, nil
}
@@ -0,0 +1,56 @@
package membership_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UpdateUserGroupOperation struct {
repo *MembershipRepository
group *model.UserGroupMeta
tx *gorm.DB
}
func (r *MembershipRepository) UpdateUserGroup(group *model.UserGroupMeta) *UpdateUserGroupOperation {
return &UpdateUserGroupOperation{
repo: r,
group: group,
}
}
func (op *UpdateUserGroupOperation) WithTx(tx *gorm.DB) *UpdateUserGroupOperation {
op.tx = tx
return op
}
func (op *UpdateUserGroupOperation) Exec() error {
if op.tx != nil {
return op.tx.Save(op.group).Error
}
return op.repo.db.Save(op.group).Error
}
type UpdateOrgNodeOperation struct {
repo *MembershipRepository
orgNode *model.OrgNodeMeta
tx *gorm.DB
}
func (r *MembershipRepository) UpdateOrgNode(orgNode *model.OrgNodeMeta) *UpdateOrgNodeOperation {
return &UpdateOrgNodeOperation{
repo: r,
orgNode: orgNode,
}
}
func (op *UpdateOrgNodeOperation) WithTx(tx *gorm.DB) *UpdateOrgNodeOperation {
op.tx = tx
return op
}
func (op *UpdateOrgNodeOperation) Exec() error {
if op.tx != nil {
return op.tx.Save(op.orgNode).Error
}
return op.repo.db.Save(op.orgNode).Error
}
@@ -0,0 +1,35 @@
package notification_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type NotificationCreateBatchOperation struct {
repo *NotificationRepository
items []model.Notification
tx *gorm.DB
}
func (r *NotificationRepository) CreateBatch(items []model.Notification) *NotificationCreateBatchOperation {
return &NotificationCreateBatchOperation{
repo: r,
items: items,
}
}
func (op *NotificationCreateBatchOperation) WithTx(tx *gorm.DB) *NotificationCreateBatchOperation {
op.tx = tx
return op
}
func (op *NotificationCreateBatchOperation) Exec() error {
if len(op.items) == 0 {
return nil
}
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Create(&op.items).Error
}
@@ -0,0 +1,74 @@
package notification_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type NotificationListOperation struct {
repo *NotificationRepository
receiverID int64
status *string
page int
pageSize int
tx *gorm.DB
}
func (r *NotificationRepository) List(receiverID int64) *NotificationListOperation {
return &NotificationListOperation{
repo: r,
receiverID: receiverID,
}
}
func (op *NotificationListOperation) WithTx(tx *gorm.DB) *NotificationListOperation {
op.tx = tx
return op
}
func (op *NotificationListOperation) WithStatus(status *string) *NotificationListOperation {
op.status = status
return op
}
func (op *NotificationListOperation) WithPagination(page, pageSize int) *NotificationListOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *NotificationListOperation) ExecWithTotal() ([]model.Notification, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.Notification{}).Where("receiver_id = ?", op.receiverID)
if op.status != nil && *op.status != "" {
query = query.Where("status = ?", *op.status)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var list []model.Notification
if err := query.Order("created_at DESC").Offset(offset).Limit(pageSize).Find(&list).Error; err != nil {
return nil, 0, err
}
return list, total, nil
}
@@ -0,0 +1,69 @@
package notification_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type NotificationMarkReadOperation struct {
repo *NotificationRepository
receiverID int64
externalIDs []string
tx *gorm.DB
}
func (r *NotificationRepository) MarkRead(receiverID int64, externalIDs []string) *NotificationMarkReadOperation {
return &NotificationMarkReadOperation{
repo: r,
receiverID: receiverID,
externalIDs: externalIDs,
}
}
func (op *NotificationMarkReadOperation) WithTx(tx *gorm.DB) *NotificationMarkReadOperation {
op.tx = tx
return op
}
func (op *NotificationMarkReadOperation) Exec() error {
if len(op.externalIDs) == 0 {
return nil
}
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Model(&model.Notification{}).
Where("receiver_id = ? AND external_id IN ?", op.receiverID, op.externalIDs).
Updates(map[string]any{"status": "read"}).
Error
}
type NotificationMarkAllReadOperation struct {
repo *NotificationRepository
receiverID int64
tx *gorm.DB
}
func (r *NotificationRepository) MarkAllRead(receiverID int64) *NotificationMarkAllReadOperation {
return &NotificationMarkAllReadOperation{
repo: r,
receiverID: receiverID,
}
}
func (op *NotificationMarkAllReadOperation) WithTx(tx *gorm.DB) *NotificationMarkAllReadOperation {
op.tx = tx
return op
}
func (op *NotificationMarkAllReadOperation) Exec() error {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
return db.Model(&model.Notification{}).
Where("receiver_id = ? AND status != ?", op.receiverID, "read").
Updates(map[string]any{"status": "read"}).
Error
}
@@ -0,0 +1,38 @@
package notification_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type NotificationRepository struct {
db *gorm.DB
}
func NewNotificationRepository(db *gorm.DB) *NotificationRepository {
return &NotificationRepository{db: db}
}
type NotificationUpdateExternalIDOperation struct {
repo *NotificationRepository
id int64
externalID string
}
func (r *NotificationRepository) UpdateExternalID(id int64, externalID string) *NotificationUpdateExternalIDOperation {
return &NotificationUpdateExternalIDOperation{
repo: r,
id: id,
externalID: externalID,
}
}
func (op *NotificationUpdateExternalIDOperation) Exec() error {
if op.id == 0 || op.externalID == "" {
return nil
}
return op.repo.db.Model(&model.Notification{}).
Where("id = ? AND (external_id IS NULL OR external_id = '')", op.id).
Updates(map[string]any{"external_id": op.externalID}).
Error
}
@@ -0,0 +1,31 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type TemplateCreateOperation struct {
repo *TemplateRepository
template *model.Template
tx *gorm.DB
}
func (r *TemplateRepository) Create(template *model.Template) *TemplateCreateOperation {
return &TemplateCreateOperation{
repo: r,
template: template,
}
}
func (op *TemplateCreateOperation) WithTx(tx *gorm.DB) *TemplateCreateOperation {
op.tx = tx
return op
}
func (op *TemplateCreateOperation) Exec() error {
if op.tx != nil {
return op.tx.Create(op.template).Error
}
return op.repo.db.Create(op.template).Error
}
@@ -0,0 +1,38 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type CreateRecentTemplateOperation struct {
repo *TemplateRepository
m *model.RecentTemplate
tx *gorm.DB
}
func (r *TemplateRepository) CreateRecentTemplate(m *model.RecentTemplate) *CreateRecentTemplateOperation {
return &CreateRecentTemplateOperation{
repo: r,
m: m,
}
}
func (op *CreateRecentTemplateOperation) WithTx(tx *gorm.DB) {
op.tx = tx
}
func (op *CreateRecentTemplateOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
return op.tx.Clauses(clause.OnConflict{
Columns: []clause.Column{
{Name: "user_id"},
{Name: "template_id"},
},
DoUpdates: clause.AssignmentColumns([]string{"accessed_at"}),
}).Create(op.m).Error
}
@@ -0,0 +1,33 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type TemplateGetByIDOperation struct {
repo *TemplateRepository
id int64
tx *gorm.DB
}
func (r *TemplateRepository) GetByID(id int64) (op *TemplateGetByIDOperation) {
return &TemplateGetByIDOperation{
repo: r,
id: id,
}
}
func (op *TemplateGetByIDOperation) WithTx(tx *gorm.DB) *TemplateGetByIDOperation {
op.tx = tx
return op
}
func (op *TemplateGetByIDOperation) Exec() (template *model.Template, err error) {
if op.tx == nil {
op.tx = op.repo.db
}
template = &model.Template{}
err = op.tx.First(template, "id = ?", op.id).Error
return
}
+119
View File
@@ -0,0 +1,119 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListTemplateOperation struct {
repo *TemplateRepository
model *model.Template
userID *int64
scope *string
knowledgeBaseID *int64
nameKeyword *string
documentType *model.DocumentType
sortField string
sortDesc bool
page int
pageSize int
tx *gorm.DB
}
func (r *TemplateRepository) List(m *model.Template) (op *ListTemplateOperation) {
return &ListTemplateOperation{
repo: r,
model: m,
}
}
func (op *ListTemplateOperation) WithTx(tx *gorm.DB) *ListTemplateOperation {
op.tx = tx
return op
}
func (op *ListTemplateOperation) WithUserID(userID *int64) *ListTemplateOperation {
op.userID = userID
return op
}
func (op *ListTemplateOperation) WithScope(scope *string) *ListTemplateOperation {
op.scope = scope
return op
}
func (op *ListTemplateOperation) WithKnowledgeBaseID(knowledgeBaseID *int64) *ListTemplateOperation {
op.knowledgeBaseID = knowledgeBaseID
return op
}
func (op *ListTemplateOperation) WithNameKeyword(nameKeyword *string) *ListTemplateOperation {
op.nameKeyword = nameKeyword
return op
}
func (op *ListTemplateOperation) WithDocumentType(documentType *model.DocumentType) *ListTemplateOperation {
op.documentType = documentType
return op
}
func (op *ListTemplateOperation) WithSort(field string, desc bool) *ListTemplateOperation {
op.sortField = field
op.sortDesc = desc
return op
}
func (op *ListTemplateOperation) WithPagination(page, pageSize int) *ListTemplateOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *ListTemplateOperation) ExecWithTotal() (templates []*model.Template, total int64, err error) {
if op.tx == nil {
op.tx = op.repo.db
}
dbQuery := op.tx.Model(op.model)
if op.model != nil {
dbQuery = dbQuery.Where(op.model)
}
if op.userID != nil {
dbQuery = dbQuery.Where("user_id = ?", *op.userID)
}
if op.scope != nil {
dbQuery = dbQuery.Where("scope = ?", *op.scope)
}
if op.knowledgeBaseID != nil {
dbQuery = dbQuery.Where("knowledge_base_id = ?", *op.knowledgeBaseID)
}
if op.nameKeyword != nil && *op.nameKeyword != "" {
dbQuery = dbQuery.Where("name ILIKE ?", "%"+*op.nameKeyword+"%")
}
if op.documentType != nil && *op.documentType != "" {
dbQuery = dbQuery.Where("document_type = ?", *op.documentType)
}
if err = dbQuery.Count(&total).Error; err != nil {
return nil, 0, err
}
orderStr := "created_at DESC"
if op.sortField != "" {
if op.sortDesc {
orderStr = op.sortField + " DESC"
} else {
orderStr = op.sortField + " ASC"
}
}
dbQuery = dbQuery.Order(orderStr)
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
dbQuery = dbQuery.Offset(offset).Limit(op.pageSize)
}
err = dbQuery.Find(&templates).Error
return
}
@@ -0,0 +1,82 @@
package template_repo
import (
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListRecentTemplatesOperation struct {
repo *TemplateRepository
userID int64
startTime *time.Time
endTime *time.Time
page int
pageSize int
tx *gorm.DB
}
func (r *TemplateRepository) ListRecentTemplates(userID int64) *ListRecentTemplatesOperation {
return &ListRecentTemplatesOperation{
repo: r,
userID: userID,
}
}
func (op *ListRecentTemplatesOperation) WithTimeRange(start, end *time.Time) *ListRecentTemplatesOperation {
op.startTime = start
op.endTime = end
return op
}
func (op *ListRecentTemplatesOperation) WithPagination(page, pageSize int) *ListRecentTemplatesOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *ListRecentTemplatesOperation) WithTx(tx *gorm.DB) *ListRecentTemplatesOperation {
op.tx = tx
return op
}
func (op *ListRecentTemplatesOperation) Exec() ([]*model.RecentTemplate, int64, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
query := db.Model(&model.RecentTemplate{}).
Where("user_id = ?", op.userID)
if op.startTime != nil {
query = query.Where("accessed_at >= ?", *op.startTime)
}
if op.endTime != nil {
query = query.Where("accessed_at <= ?", *op.endTime)
}
page := op.page
pageSize := op.pageSize
if page <= 0 {
page = 1
}
if pageSize <= 0 {
pageSize = 10
}
if pageSize > 100 {
pageSize = 100
}
offset := (page - 1) * pageSize
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var records []*model.RecentTemplate
if err := query.Order("accessed_at DESC").Offset(offset).Limit(pageSize).Find(&records).Error; err != nil {
return nil, 0, err
}
return records, total, nil
}
@@ -0,0 +1,39 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type TemplateMGetByIDsOperation struct {
repo *TemplateRepository
ids []int64
tx *gorm.DB
}
func (r *TemplateRepository) MGetByIDs(ids []int64) *TemplateMGetByIDsOperation {
return &TemplateMGetByIDsOperation{
repo: r,
ids: ids,
}
}
func (op *TemplateMGetByIDsOperation) WithTx(tx *gorm.DB) *TemplateMGetByIDsOperation {
op.tx = tx
return op
}
func (op *TemplateMGetByIDsOperation) Exec() ([]*model.Template, error) {
db := op.repo.db
if op.tx != nil {
db = op.tx
}
var templates []*model.Template
if len(op.ids) == 0 {
return templates, nil
}
if err := db.Where("id IN ?", op.ids).Find(&templates).Error; err != nil {
return nil, err
}
return templates, nil
}
@@ -0,0 +1,11 @@
package template_repo
import "gorm.io/gorm"
type TemplateRepository struct {
db *gorm.DB
}
func NewTemplateRepository(db *gorm.DB) *TemplateRepository {
return &TemplateRepository{db: db}
}
@@ -0,0 +1,63 @@
package template_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type TemplateUpdateOperation struct {
repo *TemplateRepository
template *model.Template
updateFields map[string]bool
tx *gorm.DB
}
func (r *TemplateRepository) Update(template *model.Template) *TemplateUpdateOperation {
return &TemplateUpdateOperation{
repo: r,
template: template,
updateFields: make(map[string]bool),
}
}
func (op *TemplateUpdateOperation) UpdateName() *TemplateUpdateOperation {
op.updateFields["name"] = true
return op
}
func (op *TemplateUpdateOperation) UpdateDescription() *TemplateUpdateOperation {
op.updateFields["description"] = true
return op
}
func (op *TemplateUpdateOperation) UpdateScope() *TemplateUpdateOperation {
op.updateFields["scope"] = true
return op
}
func (op *TemplateUpdateOperation) UpdateIsPublic() *TemplateUpdateOperation {
op.updateFields["is_public"] = true
return op
}
func (op *TemplateUpdateOperation) WithTx(tx *gorm.DB) *TemplateUpdateOperation {
op.tx = tx
return op
}
func (op *TemplateUpdateOperation) Exec() error {
if op.tx == nil {
op.tx = op.repo.db
}
query := op.tx.Model(op.template)
if len(op.updateFields) > 0 {
fields := make([]string, 0, len(op.updateFields))
for field := range op.updateFields {
fields = append(fields, field)
}
query = query.Select(fields)
}
return query.Updates(*op.template).Error
}
+45
View File
@@ -0,0 +1,45 @@
package user_repo
import (
"context"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type UserCreateOperation struct {
repo *UserRepository
user *model.User
tx *gorm.DB
}
func (r *UserRepository) Create(user *model.User) *UserCreateOperation {
return &UserCreateOperation{
repo: r,
user: user,
}
}
func (op *UserCreateOperation) WithTx(tx *gorm.DB) *UserCreateOperation {
op.tx = tx
return op
}
func (op *UserCreateOperation) Exec() error {
if op.tx != nil {
if err := op.tx.Create(op.user).Error; err != nil {
return err
}
return op.clearQueryCache()
}
if err := op.repo.db.Create(op.user).Error; err != nil {
return err
}
return op.clearQueryCache()
}
func (op *UserCreateOperation) clearQueryCache() error {
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
return nil
}
+45
View File
@@ -0,0 +1,45 @@
package user_repo
import (
"context"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type UserDeleteOperation struct {
repo *UserRepository
id int64
tx *gorm.DB
}
func (r *UserRepository) Delete(id int64) *UserDeleteOperation {
return &UserDeleteOperation{
repo: r,
id: id,
}
}
func (op *UserDeleteOperation) WithTx(tx *gorm.DB) *UserDeleteOperation {
op.tx = tx
return op
}
func (op *UserDeleteOperation) Exec() error {
if op.tx != nil {
if err := op.tx.Delete(&model.User{}, op.id).Error; err != nil {
return err
}
return op.clearQueryCache()
}
if err := op.repo.db.Delete(&model.User{}, op.id).Error; err != nil {
return err
}
return op.clearQueryCache()
}
func (op *UserDeleteOperation) clearQueryCache() error {
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
return nil
}
@@ -0,0 +1,37 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserGetByEmailOperation struct {
repo *UserRepository
email string
tx *gorm.DB
}
func (r *UserRepository) GetByEmail(email string) *UserGetByEmailOperation {
return &UserGetByEmailOperation{
repo: r,
email: email,
}
}
func (op *UserGetByEmailOperation) WithTx(tx *gorm.DB) *UserGetByEmailOperation {
op.tx = tx
return op
}
func (op *UserGetByEmailOperation) Exec() (*model.User, error) {
var user model.User
var err error
if op.tx != nil {
err = op.tx.Where("email = ?", op.email).First(&user).Error
} else {
err = op.repo.db.Where("email = ?", op.email).First(&user).Error
}
return &user, err
}
@@ -0,0 +1,37 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserGetByExternalIDOperation struct {
repo *UserRepository
externalID string
tx *gorm.DB
}
func (r *UserRepository) GetByExternalID(externalID string) *UserGetByExternalIDOperation {
return &UserGetByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *UserGetByExternalIDOperation) WithTx(tx *gorm.DB) *UserGetByExternalIDOperation {
op.tx = tx
return op
}
func (op *UserGetByExternalIDOperation) Exec() (*model.User, error) {
var user model.User
var err error
if op.tx != nil {
err = op.tx.Where("external_id = ?", op.externalID).First(&user).Error
} else {
err = op.repo.db.Where("external_id = ?", op.externalID).First(&user).Error
}
return &user, err
}
@@ -0,0 +1,37 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserGetByIDOperation struct {
repo *UserRepository
id int64
tx *gorm.DB
}
func (r *UserRepository) GetByID(id int64) *UserGetByIDOperation {
return &UserGetByIDOperation{
repo: r,
id: id,
}
}
func (op *UserGetByIDOperation) WithTx(tx *gorm.DB) *UserGetByIDOperation {
op.tx = tx
return op
}
func (op *UserGetByIDOperation) Exec() (*model.User, error) {
var user model.User
var err error
if op.tx != nil {
err = op.tx.First(&user, op.id).Error
} else {
err = op.repo.db.First(&user, op.id).Error
}
return &user, err
}
@@ -0,0 +1,37 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserGetIDByExternalIDOperation struct {
repo *UserRepository
externalID string
tx *gorm.DB
}
func (r *UserRepository) GetIDByExternalID(externalID string) *UserGetIDByExternalIDOperation {
return &UserGetIDByExternalIDOperation{
repo: r,
externalID: externalID,
}
}
func (op *UserGetIDByExternalIDOperation) WithTx(tx *gorm.DB) *UserGetIDByExternalIDOperation {
op.tx = tx
return op
}
func (op *UserGetIDByExternalIDOperation) Exec() (int64, error) {
var id int64
var err error
if op.tx != nil {
err = op.tx.Model(&model.User{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
} else {
err = op.repo.db.Model(&model.User{}).Where("external_id = ?", op.externalID).Pluck("id", &id).Error
}
return id, err
}
+35
View File
@@ -0,0 +1,35 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserListOperation struct {
repo *UserRepository
tx *gorm.DB
}
func (r *UserRepository) List() *UserListOperation {
return &UserListOperation{
repo: r,
}
}
func (op *UserListOperation) WithTx(tx *gorm.DB) *UserListOperation {
op.tx = tx
return op
}
func (op *UserListOperation) Exec() ([]model.User, error) {
var users []model.User
var err error
if op.tx != nil {
err = op.tx.Find(&users).Error
} else {
err = op.repo.db.Find(&users).Error
}
return users, err
}
@@ -0,0 +1,37 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type ListUserByExternalOperation struct {
repo *UserRepository
externalIDList []string
tx *gorm.DB
}
func (r *UserRepository) ListByExternal(externalIDList []string) *ListUserByExternalOperation {
return &ListUserByExternalOperation{
repo: r,
externalIDList: externalIDList,
}
}
func (op *ListUserByExternalOperation) WithTx(tx *gorm.DB) *ListUserByExternalOperation {
op.tx = tx
return op
}
func (op *ListUserByExternalOperation) Exec() ([]model.User, error) {
var users []model.User
var err error
if op.tx != nil {
err = op.tx.Where("external_id IN ?", op.externalIDList).Find(&users).Error
} else {
err = op.repo.db.Where("external_id IN ?", op.externalIDList).Find(&users).Error
}
return users, err
}
@@ -0,0 +1,51 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type MGetUserByIDOperation struct {
repo *UserRepository
userIDs []int64
tx *gorm.DB
}
func (r *UserRepository) MGetUserByID(userIDs []int64) *MGetUserByIDOperation {
return &MGetUserByIDOperation{
repo: r,
userIDs: userIDs,
}
}
func (op *MGetUserByIDOperation) WithTx(tx *gorm.DB) *MGetUserByIDOperation {
op.tx = tx
return op
}
func (op *MGetUserByIDOperation) Exec() (map[int64]*model.User, error) {
if op.tx == nil {
op.tx = op.repo.db
}
result := make(map[int64]*model.User)
if len(op.userIDs) == 0 {
return result, nil
}
var users []model.User
var err error
err = op.tx.Where("id IN ?", op.userIDs).Find(&users).Error
if err != nil {
return nil, err
}
for _, user := range users {
userCopy := user
result[user.ID] = &userCopy
}
return result, nil
}
@@ -0,0 +1,187 @@
package user_repo
import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"sort"
"strings"
"time"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/cache"
"github.com/XingfenD/yoresee_doc/pkg/key"
"github.com/sirupsen/logrus"
"gorm.io/gorm"
)
type QueryUsersOperation struct {
repo *UserRepository
tx *gorm.DB
keyword *string
userIDs []int64
page int
pageSize int
}
type userQueryCache struct {
UserIDs []int64 `json:"user_ids"`
Total int64 `json:"total"`
}
const userQueryCacheTTL = time.Minute
func (r *UserRepository) QueryUsers() *QueryUsersOperation {
return &QueryUsersOperation{
repo: r,
}
}
func (op *QueryUsersOperation) WithTx(tx *gorm.DB) *QueryUsersOperation {
op.tx = tx
return op
}
func (op *QueryUsersOperation) WithKeyword(keyword *string) *QueryUsersOperation {
op.keyword = keyword
return op
}
func (op *QueryUsersOperation) WithUserIDs(userIDs []int64) *QueryUsersOperation {
op.userIDs = userIDs
return op
}
func (op *QueryUsersOperation) WithPagination(page, pageSize int) *QueryUsersOperation {
op.page = page
op.pageSize = pageSize
return op
}
func (op *QueryUsersOperation) ExecWithTotal() ([]model.User, int64, error) {
if op.tx == nil {
op.tx = op.repo.db
}
if op.tx == op.repo.db && op.page > 0 && op.pageSize > 0 {
if users, total, ok := op.tryCache(context.Background()); ok {
return users, total, nil
}
}
query := op.tx.Model(&model.User{})
if len(op.userIDs) > 0 {
query = query.Where("id IN ?", op.userIDs)
}
if op.keyword != nil {
trimmed := strings.TrimSpace(*op.keyword)
if trimmed != "" {
like := "%" + trimmed + "%"
query = query.Where("username ILIKE ? OR email ILIKE ? OR external_id ILIKE ?", like, like, like)
}
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if op.page > 0 && op.pageSize > 0 {
offset := (op.page - 1) * op.pageSize
query = query.Offset(offset).Limit(op.pageSize)
}
var users []model.User
if err := query.Order("id DESC").Find(&users).Error; err != nil {
return nil, 0, err
}
if op.tx == op.repo.db && op.page > 0 && op.pageSize > 0 {
op.storeCache(context.Background(), users, total)
}
return users, total, nil
}
func (op *QueryUsersOperation) tryCache(ctx context.Context) ([]model.User, int64, bool) {
version := op.getUserQueryVersion(ctx)
queryHash := buildUserQueryHash(op.keyword, op.userIDs)
cacheKey := key.KeyUserQueryList(fmt.Sprintf("%d:%s", version, queryHash), op.page, op.pageSize)
var cached userQueryCache
ok, err := cache.GetJSON(ctx, cacheKey, &cached)
if err != nil || !ok {
if err != nil {
logrus.Warnf("user query cache read failed: %v", err)
}
return nil, 0, false
}
if len(cached.UserIDs) == 0 {
return []model.User{}, cached.Total, true
}
userMap, err := op.repo.MGetUserByID(cached.UserIDs).WithTx(op.tx).Exec()
if err != nil {
logrus.Warnf("user query cache mget failed: %v", err)
return nil, 0, false
}
users := make([]model.User, 0, len(cached.UserIDs))
for _, id := range cached.UserIDs {
if user, ok := userMap[id]; ok {
users = append(users, *user)
}
}
return users, cached.Total, true
}
func (op *QueryUsersOperation) storeCache(ctx context.Context, users []model.User, total int64) {
userIDs := make([]int64, 0, len(users))
for _, user := range users {
userIDs = append(userIDs, user.ID)
}
version := op.getUserQueryVersion(ctx)
queryHash := buildUserQueryHash(op.keyword, op.userIDs)
cacheKey := key.KeyUserQueryList(fmt.Sprintf("%d:%s", version, queryHash), op.page, op.pageSize)
if err := cache.SetJSON(ctx, cacheKey, &userQueryCache{UserIDs: userIDs, Total: total}, userQueryCacheTTL); err != nil {
logrus.Warnf("user query cache set failed: %v", err)
}
}
func buildUserQueryHash(keyword *string, userIDs []int64) string {
var trimmed string
if keyword != nil {
trimmed = strings.TrimSpace(*keyword)
}
sortedIDs := append([]int64{}, userIDs...)
sort.Slice(sortedIDs, func(i, j int) bool { return sortedIDs[i] < sortedIDs[j] })
builder := strings.Builder{}
builder.WriteString(trimmed)
builder.WriteString("|")
for i, id := range sortedIDs {
if i > 0 {
builder.WriteString(",")
}
builder.WriteString(fmt.Sprintf("%d", id))
}
sum := sha1.Sum([]byte(builder.String()))
return hex.EncodeToString(sum[:])
}
func (op *QueryUsersOperation) getUserQueryVersion(ctx context.Context) int64 {
val, err := op.repo.redis.Get(ctx, key.KeyUserQueryVersion()).Int64()
if err != nil {
return 1
}
if val <= 0 {
return 1
}
return val
}
+39
View File
@@ -0,0 +1,39 @@
package user_repo
import (
"github.com/XingfenD/yoresee_doc/internal/model"
"gorm.io/gorm"
)
type UserSearchOperation struct {
repo *UserRepository
query string
tx *gorm.DB
}
func (r *UserRepository) Search(query string) *UserSearchOperation {
return &UserSearchOperation{
repo: r,
query: query,
}
}
func (op *UserSearchOperation) WithTx(tx *gorm.DB) *UserSearchOperation {
op.tx = tx
return op
}
func (op *UserSearchOperation) Exec() ([]model.User, error) {
var users []model.User
var err error
if op.tx != nil {
db := op.tx.Where("username LIKE ? OR email LIKE ?", "%"+op.query+"%", "%"+op.query+"%")
err = db.Or("CAST(id AS CHAR) = ?", op.query).Find(&users).Error
} else {
db := op.repo.db.Where("username LIKE ? OR email LIKE ?", "%"+op.query+"%", "%"+op.query+"%")
err = db.Or("CAST(id AS CHAR) = ?", op.query).Find(&users).Error
}
return users, err
}
+45
View File
@@ -0,0 +1,45 @@
package user_repo
import (
"context"
"github.com/XingfenD/yoresee_doc/internal/model"
"github.com/XingfenD/yoresee_doc/pkg/key"
"gorm.io/gorm"
)
type UserUpdateOperation struct {
repo *UserRepository
user *model.User
tx *gorm.DB
}
func (r *UserRepository) Update(user *model.User) *UserUpdateOperation {
return &UserUpdateOperation{
repo: r,
user: user,
}
}
func (op *UserUpdateOperation) WithTx(tx *gorm.DB) *UserUpdateOperation {
op.tx = tx
return op
}
func (op *UserUpdateOperation) Exec() error {
if op.tx != nil {
if err := op.tx.Save(op.user).Error; err != nil {
return err
}
return op.clearQueryCache()
}
if err := op.repo.db.Save(op.user).Error; err != nil {
return err
}
return op.clearQueryCache()
}
func (op *UserUpdateOperation) clearQueryCache() error {
_, _ = op.repo.redis.Incr(context.Background(), key.KeyUserQueryVersion()).Result()
return nil
}
@@ -0,0 +1,15 @@
package user_repo
import (
"github.com/redis/go-redis/v9"
"gorm.io/gorm"
)
type UserRepository struct {
db *gorm.DB
redis *redis.Client
}
func NewUserRepository(db *gorm.DB, redis *redis.Client) *UserRepository {
return &UserRepository{db: db, redis: redis}
}