@robsun/create-keystone-app 0.2.13 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +46 -43
  2. package/dist/create-keystone-app.js +347 -10
  3. package/dist/create-module.js +1219 -607
  4. package/package.json +22 -23
  5. package/template/.claude/skills/keystone-implement/SKILL.md +113 -0
  6. package/template/.claude/skills/keystone-implement/references/CHECKLIST.md +91 -0
  7. package/template/.claude/skills/keystone-implement/references/PATTERNS.md +1088 -0
  8. package/template/.claude/skills/keystone-implement/references/SCHEMA.md +135 -0
  9. package/template/.claude/skills/keystone-implement/references/TESTING.md +231 -0
  10. package/template/.claude/skills/keystone-requirements/SKILL.md +296 -0
  11. package/template/.claude/skills/keystone-requirements/references/CONFIRM_TEMPLATE.md +170 -0
  12. package/template/.claude/skills/keystone-requirements/references/SCHEMA.md +135 -0
  13. package/template/.eslintrc.js +3 -0
  14. package/template/.github/workflows/ci.yml +30 -0
  15. package/template/.github/workflows/release.yml +32 -0
  16. package/template/.golangci.yml +11 -0
  17. package/template/README.md +81 -73
  18. package/template/apps/server/README.md +8 -0
  19. package/template/apps/server/cmd/server/main.go +27 -185
  20. package/template/apps/server/config.example.yaml +31 -1
  21. package/template/apps/server/config.yaml +31 -1
  22. package/template/apps/server/go.mod +60 -18
  23. package/template/apps/server/go.sum +183 -31
  24. package/template/apps/server/internal/frontend/embed.go +3 -8
  25. package/template/apps/server/internal/modules/example/README.md +18 -0
  26. package/template/apps/server/internal/modules/example/api/handler/handler_test.go +9 -0
  27. package/template/apps/server/internal/modules/example/api/handler/item_handler.go +468 -165
  28. package/template/apps/server/internal/modules/example/bootstrap/seeds/item.go +217 -8
  29. package/template/apps/server/internal/modules/example/domain/models/item.go +40 -7
  30. package/template/apps/server/internal/modules/example/domain/service/approval_callback.go +68 -0
  31. package/template/apps/server/internal/modules/example/domain/service/approval_schema.go +41 -0
  32. package/template/apps/server/internal/modules/example/domain/service/errors.go +20 -22
  33. package/template/apps/server/internal/modules/example/domain/service/item_service.go +267 -7
  34. package/template/apps/server/internal/modules/example/domain/service/item_service_test.go +281 -0
  35. package/template/apps/server/internal/modules/example/i18n/keys.go +32 -20
  36. package/template/apps/server/internal/modules/example/i18n/locales/en-US.json +30 -18
  37. package/template/apps/server/internal/modules/example/i18n/locales/zh-CN.json +30 -18
  38. package/template/apps/server/internal/modules/example/infra/exporter/item_exporter.go +119 -0
  39. package/template/apps/server/internal/modules/example/infra/importer/item_importer.go +77 -0
  40. package/template/apps/server/internal/modules/example/infra/repository/item_repository.go +99 -49
  41. package/template/apps/server/internal/modules/example/module.go +171 -97
  42. package/template/apps/server/internal/modules/example/tests/integration_test.go +7 -0
  43. package/template/apps/server/internal/modules/manifest.go +7 -7
  44. package/template/apps/web/README.md +4 -2
  45. package/template/apps/web/package.json +1 -1
  46. package/template/apps/web/src/app.config.ts +8 -6
  47. package/template/apps/web/src/index.css +7 -3
  48. package/template/apps/web/src/main.tsx +2 -5
  49. package/template/apps/web/src/modules/example/help/en-US/faq.md +27 -0
  50. package/template/apps/web/src/modules/example/help/en-US/items.md +30 -0
  51. package/template/apps/web/src/modules/example/help/en-US/overview.md +31 -0
  52. package/template/apps/web/src/modules/example/help/zh-CN/faq.md +27 -0
  53. package/template/apps/web/src/modules/example/help/zh-CN/items.md +31 -0
  54. package/template/apps/web/src/modules/example/help/zh-CN/overview.md +32 -0
  55. package/template/apps/web/src/modules/example/locales/en-US/example.json +99 -32
  56. package/template/apps/web/src/modules/example/locales/zh-CN/example.json +85 -18
  57. package/template/apps/web/src/modules/example/pages/ExampleItemsPage.tsx +840 -237
  58. package/template/apps/web/src/modules/example/services/exampleItems.ts +79 -8
  59. package/template/apps/web/src/modules/example/types.ts +14 -1
  60. package/template/apps/web/src/modules/index.ts +1 -0
  61. package/template/apps/web/vite.config.ts +9 -3
  62. package/template/docs/CONVENTIONS.md +10 -7
  63. package/template/package.json +4 -5
  64. package/template/pnpm-lock.yaml +76 -5
  65. package/template/scripts/build.bat +15 -3
  66. package/template/scripts/build.sh +9 -3
  67. package/template/scripts/check-help.js +249 -0
  68. package/template/scripts/compress-assets.js +89 -0
  69. package/template/scripts/test.bat +23 -0
  70. package/template/scripts/test.sh +16 -0
  71. package/template/.claude/skills/keystone-dev/SKILL.md +0 -103
  72. package/template/.claude/skills/keystone-dev/references/APPROVAL.md +0 -121
  73. package/template/.claude/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  74. package/template/.claude/skills/keystone-dev/references/TEMPLATES.md +0 -532
  75. package/template/.claude/skills/keystone-dev/references/TESTING.md +0 -44
  76. package/template/.codex/skills/keystone-dev/SKILL.md +0 -103
  77. package/template/.codex/skills/keystone-dev/references/APPROVAL.md +0 -121
  78. package/template/.codex/skills/keystone-dev/references/CAPABILITIES.md +0 -261
  79. package/template/.codex/skills/keystone-dev/references/TEMPLATES.md +0 -532
  80. package/template/.codex/skills/keystone-dev/references/TESTING.md +0 -44
  81. package/template/apps/server/internal/app/routes/module_routes.go +0 -16
  82. package/template/apps/server/internal/app/routes/routes.go +0 -226
  83. package/template/apps/server/internal/app/startup/startup.go +0 -74
  84. package/template/apps/server/internal/frontend/handler.go +0 -122
  85. package/template/apps/server/internal/modules/registry.go +0 -145
  86. package/template/apps/web/src/modules/example/help/faq.md +0 -23
  87. package/template/apps/web/src/modules/example/help/items.md +0 -26
  88. package/template/apps/web/src/modules/example/help/overview.md +0 -25
@@ -1,18 +1,30 @@
1
- {
2
- "example.item.created": "Item created successfully",
3
- "example.item.updated": "Item updated successfully",
4
- "example.item.deleted": "Item deleted successfully",
5
- "example.item.notFound": "Item not found",
6
- "example.item.loadFailed": "Failed to load items",
7
- "example.item.createFailed": "Failed to create item",
8
- "example.item.updateFailed": "Failed to update item",
9
- "example.item.deleteFailed": "Failed to delete item",
10
- "example.validation.titleRequired": "Title is required",
11
- "example.validation.statusInvalid": "Invalid status value",
12
- "example.validation.invalidId": "Invalid ID",
13
- "example.validation.invalidPayload": "Invalid request payload",
14
- "example.service.unavailable": "Example service is unavailable",
15
- "permission.example.item": "Example Items",
16
- "permission.example.item.view": "View Items",
17
- "permission.example.item.manage": "Manage Items"
18
- }
1
+ {
2
+ "example.item.created": "Item created successfully",
3
+ "example.item.updated": "Item updated successfully",
4
+ "example.item.deleted": "Item deleted successfully",
5
+ "example.item.submitted": "Item submitted for approval",
6
+ "example.item.cancelled": "Approval cancelled",
7
+ "example.item.notFound": "Item not found",
8
+ "example.item.loadFailed": "Failed to load items",
9
+ "example.item.createFailed": "Failed to create item",
10
+ "example.item.updateFailed": "Failed to update item",
11
+ "example.item.deleteFailed": "Failed to delete item",
12
+ "example.item.submitFailed": "Failed to submit item",
13
+ "example.item.cancelFailed": "Failed to cancel approval",
14
+ "example.item.exportFailed": "Failed to export items",
15
+ "example.validation.titleRequired": "Title is required",
16
+ "example.validation.statusInvalid": "Invalid status value",
17
+ "example.validation.categoryInvalid": "Invalid category",
18
+ "example.validation.amountInvalid": "Amount must be zero or greater",
19
+ "example.validation.itemLocked": "Item cannot be edited or deleted in the current status",
20
+ "example.validation.submitterRequired": "Submitter is required",
21
+ "example.validation.approvalNotReady": "Approval flow is not ready",
22
+ "example.validation.approvalNotPending": "Only pending approvals can be cancelled",
23
+ "example.validation.approvalInstanceMissing": "Approval instance is missing",
24
+ "example.validation.invalidId": "Invalid ID",
25
+ "example.validation.invalidPayload": "Invalid request payload",
26
+ "example.service.unavailable": "Example service is unavailable",
27
+ "permission.example.item": "Example Items",
28
+ "permission.example.item.view": "View Items",
29
+ "permission.example.item.manage": "Manage Items"
30
+ }
@@ -1,18 +1,30 @@
1
- {
2
- "example.item.created": "项目创建成功",
3
- "example.item.updated": "项目更新成功",
4
- "example.item.deleted": "项目删除成功",
5
- "example.item.notFound": "项目不存在",
6
- "example.item.loadFailed": "加载项目列表失败",
7
- "example.item.createFailed": "创建项目失败",
8
- "example.item.updateFailed": "更新项目失败",
9
- "example.item.deleteFailed": "删除项目失败",
10
- "example.validation.titleRequired": "标题不能为空",
11
- "example.validation.statusInvalid": "状态值无效",
12
- "example.validation.invalidId": "无效的 ID",
13
- "example.validation.invalidPayload": "请求参数无效",
14
- "example.service.unavailable": "示例服务暂不可用",
15
- "permission.example.item": "示例项目",
16
- "permission.example.item.view": "查看项目",
17
- "permission.example.item.manage": "管理项目"
18
- }
1
+ {
2
+ "example.item.created": "事项创建成功",
3
+ "example.item.updated": "事项更新成功",
4
+ "example.item.deleted": "事项删除成功",
5
+ "example.item.submitted": "事项已提交审批",
6
+ "example.item.cancelled": "事项已取消审批",
7
+ "example.item.notFound": "事项不存在",
8
+ "example.item.loadFailed": "加载事项列表失败",
9
+ "example.item.createFailed": "创建事项失败",
10
+ "example.item.updateFailed": "更新事项失败",
11
+ "example.item.deleteFailed": "删除事项失败",
12
+ "example.item.submitFailed": "提交审批失败",
13
+ "example.item.cancelFailed": "取消审批失败",
14
+ "example.item.exportFailed": "导出事项失败",
15
+ "example.validation.titleRequired": "标题不能为空",
16
+ "example.validation.statusInvalid": "状态无效",
17
+ "example.validation.categoryInvalid": "类别无效",
18
+ "example.validation.amountInvalid": "金额不能小于 0",
19
+ "example.validation.itemLocked": "当前状态不允许编辑或删除",
20
+ "example.validation.submitterRequired": "缺少提交人信息",
21
+ "example.validation.approvalNotReady": "审批流程未就绪",
22
+ "example.validation.approvalNotPending": "仅可取消审批中的事项",
23
+ "example.validation.approvalInstanceMissing": "审批实例不存在",
24
+ "example.validation.invalidId": "无效的 ID",
25
+ "example.validation.invalidPayload": "请求参数无效",
26
+ "example.service.unavailable": "示例服务暂不可用",
27
+ "permission.example.item": "示例事项",
28
+ "permission.example.item.view": "查看事项",
29
+ "permission.example.item.manage": "管理事项"
30
+ }
@@ -0,0 +1,119 @@
1
+ package exporter
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "time"
7
+
8
+ approvalmodels "github.com/robsuncn/keystone/domain/approval/models"
9
+ coreservice "github.com/robsuncn/keystone/domain/service"
10
+ coreexporter "github.com/robsuncn/keystone/infra/exporter"
11
+ "github.com/robsuncn/keystone/infra/jobs"
12
+
13
+ examplemodels "__APP_NAME__/apps/server/internal/modules/example/domain/models"
14
+ exampleservice "__APP_NAME__/apps/server/internal/modules/example/domain/service"
15
+ )
16
+
17
+ const ExportTypeExampleItems = "example_items"
18
+
19
+ func RegisterItemExporter(reg *coreexporter.Service, items *exampleservice.ItemService) error {
20
+ if reg == nil || items == nil {
21
+ return nil
22
+ }
23
+ return reg.Register(ExportTypeExampleItems, coreexporter.Definition{
24
+ Format: coreexporter.FormatExcel,
25
+ Build: func(ctx context.Context, payload jobs.ExportJobPayload, tracker coreexporter.ProgressTracker) (coreexporter.Dataset, error) {
26
+ tenantID, ok := coreservice.TenantIDFromContext(ctx)
27
+ if !ok || tenantID == 0 {
28
+ return coreexporter.Dataset{}, fmt.Errorf("tenant id missing")
29
+ }
30
+ filter := buildFilter(payload.Options)
31
+ filter.Page = 1
32
+ filter.PageSize = 0
33
+
34
+ list, _, err := items.List(ctx, tenantID, filter)
35
+ if err != nil {
36
+ return coreexporter.Dataset{}, err
37
+ }
38
+
39
+ headers := []string{"Number", "Title", "Category", "Amount", "Status", "Approval Status", "Created At", "Updated At"}
40
+ rows := make([][]string, 0, len(list))
41
+ for idx, item := range list {
42
+ rows = append(rows, []string{
43
+ item.Number,
44
+ item.Title,
45
+ string(item.Category),
46
+ fmt.Sprintf("%.2f", item.Amount),
47
+ string(item.Status),
48
+ string(item.ApprovalStatus),
49
+ formatTime(item.CreatedAt),
50
+ formatTime(item.UpdatedAt),
51
+ })
52
+ if tracker != nil {
53
+ _ = tracker.Report(ctx, coreexporter.Progress{Total: len(list), Processed: idx + 1})
54
+ }
55
+ }
56
+
57
+ return coreexporter.Dataset{Headers: headers, Rows: rows}, nil
58
+ },
59
+ })
60
+ }
61
+
62
+ func buildFilter(options map[string]interface{}) exampleservice.ItemListFilter {
63
+ filter := exampleservice.ItemListFilter{}
64
+ if options == nil {
65
+ return filter
66
+ }
67
+ if raw, ok := options["keyword"].(string); ok {
68
+ filter.Keyword = raw
69
+ }
70
+ if raw, ok := options["status"].(string); ok && raw != "" {
71
+ status := examplemodels.ItemStatus(raw)
72
+ if status.IsValid() {
73
+ filter.Status = &status
74
+ }
75
+ }
76
+ if raw, ok := options["approval_status"].(string); ok && raw != "" {
77
+ status := approvalmodels.InstanceStatus(raw)
78
+ filter.ApprovalStatus = &status
79
+ }
80
+ if raw, ok := options["category"].(string); ok && raw != "" {
81
+ category := examplemodels.ItemCategory(raw)
82
+ if category.IsValid() {
83
+ filter.Category = &category
84
+ }
85
+ }
86
+ if raw, ok := options["start_date"].(string); ok && raw != "" {
87
+ if parsed, err := parseDate(raw, false); err == nil {
88
+ filter.StartDate = parsed
89
+ }
90
+ }
91
+ if raw, ok := options["end_date"].(string); ok && raw != "" {
92
+ if parsed, err := parseDate(raw, true); err == nil {
93
+ filter.EndDate = parsed
94
+ }
95
+ }
96
+ return filter
97
+ }
98
+
99
+ func parseDate(value string, endOfDay bool) (*time.Time, error) {
100
+ layouts := []string{time.RFC3339, "2006-01-02"}
101
+ for _, layout := range layouts {
102
+ parsed, err := time.Parse(layout, value)
103
+ if err != nil {
104
+ continue
105
+ }
106
+ if endOfDay && layout == "2006-01-02" {
107
+ parsed = parsed.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
108
+ }
109
+ return &parsed, nil
110
+ }
111
+ return nil, fmt.Errorf("invalid date")
112
+ }
113
+
114
+ func formatTime(value time.Time) string {
115
+ if value.IsZero() {
116
+ return ""
117
+ }
118
+ return value.UTC().Format(time.RFC3339)
119
+ }
@@ -0,0 +1,77 @@
1
+ package importer
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "strconv"
7
+ "strings"
8
+
9
+ coreimporter "github.com/robsuncn/keystone/infra/importer"
10
+ coreservice "github.com/robsuncn/keystone/domain/service"
11
+
12
+ examplemodels "__APP_NAME__/apps/server/internal/modules/example/domain/models"
13
+ exampleservice "__APP_NAME__/apps/server/internal/modules/example/domain/service"
14
+ )
15
+
16
+ const ImportTypeExampleItems = "example_items"
17
+
18
+ func RegisterItemImporter(reg *coreimporter.Service, items *exampleservice.ItemService) error {
19
+ if reg == nil || items == nil {
20
+ return nil
21
+ }
22
+ return reg.Register(ImportTypeExampleItems, coreimporter.Definition{
23
+ Format: coreimporter.FormatExcel,
24
+ Validator: coreimporter.RequiredColumnsValidator{
25
+ Columns: []string{"title"},
26
+ },
27
+ Handler: func(ctx context.Context, record coreimporter.Record) error {
28
+ tenantID, ok := coreservice.TenantIDFromContext(ctx)
29
+ if !ok || tenantID == 0 {
30
+ return fmt.Errorf("tenant id missing")
31
+ }
32
+ input := exampleservice.ItemInput{
33
+ Title: record["title"],
34
+ Description: record["description"],
35
+ Category: parseCategory(record["category"]),
36
+ Amount: parseAmount(record["amount"]),
37
+ Status: parseStatus(record["status"]),
38
+ }
39
+ _, err := items.Create(ctx, tenantID, input)
40
+ return err
41
+ },
42
+ })
43
+ }
44
+
45
+ func parseCategory(raw string) examplemodels.ItemCategory {
46
+ value := examplemodels.ItemCategory(strings.TrimSpace(raw))
47
+ if value == "" {
48
+ return examplemodels.CategoryGeneral
49
+ }
50
+ if value.IsValid() {
51
+ return value
52
+ }
53
+ return examplemodels.CategoryGeneral
54
+ }
55
+
56
+ func parseStatus(raw string) examplemodels.ItemStatus {
57
+ value := examplemodels.ItemStatus(strings.TrimSpace(raw))
58
+ if value == "" {
59
+ return examplemodels.StatusDraft
60
+ }
61
+ if value.IsValid() {
62
+ return value
63
+ }
64
+ return examplemodels.StatusDraft
65
+ }
66
+
67
+ func parseAmount(raw string) float64 {
68
+ trimmed := strings.TrimSpace(raw)
69
+ if trimmed == "" {
70
+ return 0
71
+ }
72
+ amount, err := strconv.ParseFloat(trimmed, 64)
73
+ if err != nil {
74
+ return 0
75
+ }
76
+ return amount
77
+ }
@@ -1,49 +1,99 @@
1
- package repository
2
-
3
- import (
4
- "context"
5
- "errors"
6
-
7
- "gorm.io/gorm"
8
-
9
- "__APP_NAME__/apps/server/internal/modules/example/domain/models"
10
- "__APP_NAME__/apps/server/internal/modules/example/domain/service"
11
- )
12
-
13
- type ItemRepository struct {
14
- db *gorm.DB
15
- }
16
-
17
- func NewItemRepository(db *gorm.DB) *ItemRepository {
18
- return &ItemRepository{db: db}
19
- }
20
-
21
- func (r *ItemRepository) List(ctx context.Context, tenantID uint) ([]models.ExampleItem, error) {
22
- var items []models.ExampleItem
23
- err := r.db.WithContext(ctx).
24
- Where("tenant_id = ?", tenantID).
25
- Order("created_at desc").
26
- Find(&items).Error
27
- return items, err
28
- }
29
-
30
- func (r *ItemRepository) FindByID(tenantID, id uint) (*models.ExampleItem, error) {
31
- var item models.ExampleItem
32
- err := r.db.Where("tenant_id = ? AND id = ?", tenantID, id).First(&item).Error
33
- if errors.Is(err, gorm.ErrRecordNotFound) {
34
- return nil, service.ErrItemNotFound
35
- }
36
- return &item, err
37
- }
38
-
39
- func (r *ItemRepository) Create(ctx context.Context, item *models.ExampleItem) error {
40
- return r.db.WithContext(ctx).Create(item).Error
41
- }
42
-
43
- func (r *ItemRepository) Update(ctx context.Context, item *models.ExampleItem) error {
44
- return r.db.WithContext(ctx).Save(item).Error
45
- }
46
-
47
- func (r *ItemRepository) Delete(ctx context.Context, item *models.ExampleItem) error {
48
- return r.db.WithContext(ctx).Delete(item).Error
49
- }
1
+ package repository
2
+
3
+ import (
4
+ "context"
5
+ "errors"
6
+ "strings"
7
+
8
+ "gorm.io/gorm"
9
+
10
+ "github.com/robsuncn/keystone/domain/dbutil"
11
+
12
+ "__APP_NAME__/apps/server/internal/modules/example/domain/models"
13
+ "__APP_NAME__/apps/server/internal/modules/example/domain/service"
14
+ )
15
+
16
+ type ItemRepository struct {
17
+ db *gorm.DB
18
+ }
19
+
20
+ func NewItemRepository(db *gorm.DB) *ItemRepository {
21
+ return &ItemRepository{db: db}
22
+ }
23
+
24
+ func (r *ItemRepository) List(ctx context.Context, tenantID uint, filter service.ItemListFilter) ([]models.ExampleItem, int64, error) {
25
+ var items []models.ExampleItem
26
+ query := r.getDB(ctx).WithContext(ctx).
27
+ Model(&models.ExampleItem{}).
28
+ Where("tenant_id = ?", tenantID)
29
+
30
+ if filter.Keyword != "" {
31
+ keyword := strings.ToLower(strings.TrimSpace(filter.Keyword))
32
+ if keyword != "" {
33
+ like := "%" + keyword + "%"
34
+ query = query.Where("LOWER(title) LIKE ? OR LOWER(number) LIKE ?", like, like)
35
+ }
36
+ }
37
+ if filter.Status != nil && *filter.Status != "" {
38
+ query = query.Where("status = ?", *filter.Status)
39
+ }
40
+ if filter.ApprovalStatus != nil && *filter.ApprovalStatus != "" {
41
+ query = query.Where("approval_status = ?", *filter.ApprovalStatus)
42
+ }
43
+ if filter.Category != nil && *filter.Category != "" {
44
+ query = query.Where("category = ?", *filter.Category)
45
+ }
46
+ if filter.StartDate != nil {
47
+ query = query.Where("created_at >= ?", *filter.StartDate)
48
+ }
49
+ if filter.EndDate != nil {
50
+ query = query.Where("created_at <= ?", *filter.EndDate)
51
+ }
52
+
53
+ var total int64
54
+ if err := query.Count(&total).Error; err != nil {
55
+ return nil, 0, err
56
+ }
57
+
58
+ if filter.PageSize > 0 {
59
+ page := filter.Page
60
+ if page < 1 {
61
+ page = 1
62
+ }
63
+ offset := (page - 1) * filter.PageSize
64
+ query = query.Limit(filter.PageSize).Offset(offset)
65
+ }
66
+
67
+ err := query.Order("created_at desc").Find(&items).Error
68
+ return items, total, err
69
+ }
70
+
71
+ func (r *ItemRepository) FindByID(tenantID, id uint) (*models.ExampleItem, error) {
72
+ var item models.ExampleItem
73
+ err := r.getDB(nil).Where("tenant_id = ? AND id = ?", tenantID, id).First(&item).Error
74
+ if errors.Is(err, gorm.ErrRecordNotFound) {
75
+ return nil, service.ErrItemNotFound
76
+ }
77
+ return &item, err
78
+ }
79
+
80
+ func (r *ItemRepository) Create(ctx context.Context, item *models.ExampleItem) error {
81
+ return r.getDB(ctx).WithContext(ctx).Create(item).Error
82
+ }
83
+
84
+ func (r *ItemRepository) Update(ctx context.Context, item *models.ExampleItem) error {
85
+ return r.getDB(ctx).WithContext(ctx).Save(item).Error
86
+ }
87
+
88
+ func (r *ItemRepository) Delete(ctx context.Context, item *models.ExampleItem) error {
89
+ return r.getDB(ctx).WithContext(ctx).Delete(item).Error
90
+ }
91
+
92
+ func (r *ItemRepository) getDB(ctx context.Context) *gorm.DB {
93
+ if ctx != nil {
94
+ if tx := dbutil.TxFromContext(ctx); tx != nil {
95
+ return tx
96
+ }
97
+ }
98
+ return r.db
99
+ }