migrate from github
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
Reference in New Issue
Block a user