♻️ Refactor synchronization service
This commit is contained in:
61
internal/syncer/resource/adapter.go
Normal file
61
internal/syncer/resource/adapter.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sort"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// Adapter 描述单类资源的导出与应用能力。
|
||||
type Adapter interface {
|
||||
Kind() string
|
||||
Export(ctx context.Context) ([]snapshot.Record, error)
|
||||
Apply(ctx context.Context, records []snapshot.Record) error
|
||||
}
|
||||
|
||||
// Registry 聚合所有资源适配器,并实现快照导入导出接口。
|
||||
type Registry struct {
|
||||
adapters []Adapter
|
||||
}
|
||||
|
||||
// NewRegistry 创建新的资源注册表。
|
||||
func NewRegistry(adapters ...Adapter) *Registry {
|
||||
return &Registry{adapters: adapters}
|
||||
}
|
||||
|
||||
// Export 导出所有已注册资源的快照。
|
||||
func (r *Registry) Export(ctx context.Context) (*snapshot.Snapshot, error) {
|
||||
snap := snapshot.New()
|
||||
|
||||
for _, adapter := range r.adapters {
|
||||
records, err := adapter.Export(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(records) == 0 {
|
||||
continue
|
||||
}
|
||||
sort.Slice(records, func(i int, j int) bool {
|
||||
return records[i].ID < records[j].ID
|
||||
})
|
||||
snap.Resources[adapter.Kind()] = records
|
||||
}
|
||||
|
||||
return snap, nil
|
||||
}
|
||||
|
||||
// Apply 将快照内容应用到本地资源。
|
||||
func (r *Registry) Apply(ctx context.Context, snap *snapshot.Snapshot) error {
|
||||
if snap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, adapter := range r.adapters {
|
||||
records := snap.Resources[adapter.Kind()]
|
||||
if err := adapter.Apply(ctx, records); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
117
internal/syncer/resource/document_adapter.go
Normal file
117
internal/syncer/resource/document_adapter.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/document"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
const documentContentBlob = "content.md"
|
||||
|
||||
// DocumentAdapter 负责文档资源的快照导入导出。
|
||||
type DocumentAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewDocumentAdapter 创建文档适配器。
|
||||
func NewDocumentAdapter(client *ent.Client) *DocumentAdapter {
|
||||
return &DocumentAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *DocumentAdapter) Kind() string {
|
||||
return "documents"
|
||||
}
|
||||
|
||||
// Export 导出文档快照记录。
|
||||
func (a *DocumentAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
documents, err := a.client.Document.Query().Order(document.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(documents))
|
||||
for _, item := range documents {
|
||||
values := map[string]interface{}{
|
||||
document.FieldUUID: item.UUID,
|
||||
document.FieldCreatedAt: item.CreatedAt,
|
||||
document.FieldUpdatedAt: item.UpdatedAt,
|
||||
document.FieldTitle: item.Title,
|
||||
document.FieldLocked: item.Locked,
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[document.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, map[string][]byte{
|
||||
documentContentBlob: []byte(item.Content),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build document record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地文档表。
|
||||
func (a *DocumentAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Document.Query().Where(document.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的文档记录。
|
||||
func (a *DocumentAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Document.Create().
|
||||
SetUUID(record.ID).
|
||||
SetTitle(stringValue(record, document.FieldTitle)).
|
||||
SetContent(blobString(record, documentContentBlob)).
|
||||
SetLocked(boolValue(record, document.FieldLocked)).
|
||||
SetCreatedAt(stringValue(record, document.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, document.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有文档记录。
|
||||
func (a *DocumentAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Document.UpdateOneID(id).
|
||||
SetTitle(stringValue(record, document.FieldTitle)).
|
||||
SetContent(blobString(record, documentContentBlob)).
|
||||
SetLocked(boolValue(record, document.FieldLocked)).
|
||||
SetUpdatedAt(stringValue(record, document.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
114
internal/syncer/resource/extension_adapter.go
Normal file
114
internal/syncer/resource/extension_adapter.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/extension"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// ExtensionAdapter 负责扩展资源的快照导入导出。
|
||||
type ExtensionAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewExtensionAdapter 创建扩展适配器。
|
||||
func NewExtensionAdapter(client *ent.Client) *ExtensionAdapter {
|
||||
return &ExtensionAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *ExtensionAdapter) Kind() string {
|
||||
return "extensions"
|
||||
}
|
||||
|
||||
// Export 导出扩展快照记录。
|
||||
func (a *ExtensionAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
extensions, err := a.client.Extension.Query().Order(extension.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(extensions))
|
||||
for _, item := range extensions {
|
||||
values := map[string]interface{}{
|
||||
extension.FieldUUID: item.UUID,
|
||||
extension.FieldCreatedAt: item.CreatedAt,
|
||||
extension.FieldUpdatedAt: item.UpdatedAt,
|
||||
extension.FieldName: item.Name,
|
||||
extension.FieldEnabled: item.Enabled,
|
||||
extension.FieldConfig: cloneMap(item.Config),
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[extension.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build extension record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地扩展表。
|
||||
func (a *ExtensionAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Extension.Query().Where(extension.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的扩展记录。
|
||||
func (a *ExtensionAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Extension.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, extension.FieldName)).
|
||||
SetEnabled(boolValue(record, extension.FieldEnabled)).
|
||||
SetConfig(mapValue(record, extension.FieldConfig)).
|
||||
SetCreatedAt(stringValue(record, extension.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, extension.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有扩展记录。
|
||||
func (a *ExtensionAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Extension.UpdateOneID(id).
|
||||
SetName(stringValue(record, extension.FieldName)).
|
||||
SetEnabled(boolValue(record, extension.FieldEnabled)).
|
||||
SetConfig(mapValue(record, extension.FieldConfig)).
|
||||
SetUpdatedAt(stringValue(record, extension.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
75
internal/syncer/resource/helpers.go
Normal file
75
internal/syncer/resource/helpers.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"time"
|
||||
"voidraft/internal/models/schema/mixin"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// importContext 构造同步导入所需的上下文。
|
||||
func importContext(ctx context.Context) context.Context {
|
||||
return mixin.SkipAutoUpdate(mixin.SkipSoftDelete(ctx))
|
||||
}
|
||||
|
||||
// exportContext 构造同步导出所需的上下文。
|
||||
func exportContext(ctx context.Context) context.Context {
|
||||
return mixin.SkipSoftDelete(ctx)
|
||||
}
|
||||
|
||||
// cloneMap 返回 map 的安全副本。
|
||||
func cloneMap(value map[string]interface{}) map[string]interface{} {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return maps.Clone(value)
|
||||
}
|
||||
|
||||
// recordDeletedAtString 返回记录中的删除时间字符串。
|
||||
func recordDeletedAtString(record snapshot.Record) *string {
|
||||
if record.DeletedAt == nil {
|
||||
return nil
|
||||
}
|
||||
value := record.DeletedAt.Format(time.RFC3339)
|
||||
return &value
|
||||
}
|
||||
|
||||
// shouldApplyRecord 判断记录是否应该覆盖本地数据。
|
||||
func shouldApplyRecord(localUpdatedAt string, record snapshot.Record) bool {
|
||||
if localUpdatedAt == "" {
|
||||
return true
|
||||
}
|
||||
localTime, err := time.Parse(time.RFC3339, localUpdatedAt)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return record.UpdatedAt.After(localTime)
|
||||
}
|
||||
|
||||
// stringValue 从记录字段中读取字符串。
|
||||
func stringValue(record snapshot.Record, key string) string {
|
||||
value, _ := record.Values[key].(string)
|
||||
return value
|
||||
}
|
||||
|
||||
// boolValue 从记录字段中读取布尔值。
|
||||
func boolValue(record snapshot.Record, key string) bool {
|
||||
value, _ := record.Values[key].(bool)
|
||||
return value
|
||||
}
|
||||
|
||||
// mapValue 从记录字段中读取 map 值。
|
||||
func mapValue(record snapshot.Record, key string) map[string]interface{} {
|
||||
value, _ := record.Values[key].(map[string]interface{})
|
||||
return cloneMap(value)
|
||||
}
|
||||
|
||||
// blobString 读取记录中的文本 blob。
|
||||
func blobString(record snapshot.Record, name string) string {
|
||||
value, ok := record.Blobs[name]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
return string(value)
|
||||
}
|
||||
135
internal/syncer/resource/keybinding_adapter.go
Normal file
135
internal/syncer/resource/keybinding_adapter.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/keybinding"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// KeyBindingAdapter 负责快捷键资源的快照导入导出。
|
||||
type KeyBindingAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewKeyBindingAdapter 创建快捷键适配器。
|
||||
func NewKeyBindingAdapter(client *ent.Client) *KeyBindingAdapter {
|
||||
return &KeyBindingAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *KeyBindingAdapter) Kind() string {
|
||||
return "keybindings"
|
||||
}
|
||||
|
||||
// Export 导出快捷键快照记录。
|
||||
func (a *KeyBindingAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
keyBindings, err := a.client.KeyBinding.Query().Order(keybinding.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(keyBindings))
|
||||
for _, item := range keyBindings {
|
||||
values := map[string]interface{}{
|
||||
keybinding.FieldUUID: item.UUID,
|
||||
keybinding.FieldCreatedAt: item.CreatedAt,
|
||||
keybinding.FieldUpdatedAt: item.UpdatedAt,
|
||||
keybinding.FieldName: item.Name,
|
||||
keybinding.FieldType: item.Type,
|
||||
keybinding.FieldKey: item.Key,
|
||||
keybinding.FieldMacos: item.Macos,
|
||||
keybinding.FieldWindows: item.Windows,
|
||||
keybinding.FieldLinux: item.Linux,
|
||||
keybinding.FieldExtension: item.Extension,
|
||||
keybinding.FieldEnabled: item.Enabled,
|
||||
keybinding.FieldPreventDefault: item.PreventDefault,
|
||||
keybinding.FieldScope: item.Scope,
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[keybinding.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build keybinding record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地快捷键表。
|
||||
func (a *KeyBindingAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.KeyBinding.Query().Where(keybinding.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的快捷键记录。
|
||||
func (a *KeyBindingAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.KeyBinding.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, keybinding.FieldName)).
|
||||
SetType(stringValue(record, keybinding.FieldType)).
|
||||
SetKey(stringValue(record, keybinding.FieldKey)).
|
||||
SetMacos(stringValue(record, keybinding.FieldMacos)).
|
||||
SetWindows(stringValue(record, keybinding.FieldWindows)).
|
||||
SetLinux(stringValue(record, keybinding.FieldLinux)).
|
||||
SetExtension(stringValue(record, keybinding.FieldExtension)).
|
||||
SetEnabled(boolValue(record, keybinding.FieldEnabled)).
|
||||
SetPreventDefault(boolValue(record, keybinding.FieldPreventDefault)).
|
||||
SetScope(stringValue(record, keybinding.FieldScope)).
|
||||
SetCreatedAt(stringValue(record, keybinding.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, keybinding.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有快捷键记录。
|
||||
func (a *KeyBindingAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.KeyBinding.UpdateOneID(id).
|
||||
SetName(stringValue(record, keybinding.FieldName)).
|
||||
SetType(stringValue(record, keybinding.FieldType)).
|
||||
SetKey(stringValue(record, keybinding.FieldKey)).
|
||||
SetMacos(stringValue(record, keybinding.FieldMacos)).
|
||||
SetWindows(stringValue(record, keybinding.FieldWindows)).
|
||||
SetLinux(stringValue(record, keybinding.FieldLinux)).
|
||||
SetExtension(stringValue(record, keybinding.FieldExtension)).
|
||||
SetEnabled(boolValue(record, keybinding.FieldEnabled)).
|
||||
SetPreventDefault(boolValue(record, keybinding.FieldPreventDefault)).
|
||||
SetScope(stringValue(record, keybinding.FieldScope)).
|
||||
SetUpdatedAt(stringValue(record, keybinding.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
114
internal/syncer/resource/theme_adapter.go
Normal file
114
internal/syncer/resource/theme_adapter.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package resource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"voidraft/internal/models/ent"
|
||||
"voidraft/internal/models/ent/theme"
|
||||
"voidraft/internal/syncer/snapshot"
|
||||
)
|
||||
|
||||
// ThemeAdapter 负责主题资源的快照导入导出。
|
||||
type ThemeAdapter struct {
|
||||
client *ent.Client
|
||||
}
|
||||
|
||||
// NewThemeAdapter 创建主题适配器。
|
||||
func NewThemeAdapter(client *ent.Client) *ThemeAdapter {
|
||||
return &ThemeAdapter{client: client}
|
||||
}
|
||||
|
||||
// Kind 返回适配器负责的资源类型。
|
||||
func (a *ThemeAdapter) Kind() string {
|
||||
return "themes"
|
||||
}
|
||||
|
||||
// Export 导出主题快照记录。
|
||||
func (a *ThemeAdapter) Export(ctx context.Context) ([]snapshot.Record, error) {
|
||||
themes, err := a.client.Theme.Query().Order(theme.ByUUID()).All(exportContext(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
records := make([]snapshot.Record, 0, len(themes))
|
||||
for _, item := range themes {
|
||||
values := map[string]interface{}{
|
||||
theme.FieldUUID: item.UUID,
|
||||
theme.FieldCreatedAt: item.CreatedAt,
|
||||
theme.FieldUpdatedAt: item.UpdatedAt,
|
||||
theme.FieldName: item.Name,
|
||||
theme.FieldType: item.Type.String(),
|
||||
theme.FieldColors: cloneMap(item.Colors),
|
||||
}
|
||||
if item.DeletedAt != nil {
|
||||
values[theme.FieldDeletedAt] = *item.DeletedAt
|
||||
}
|
||||
|
||||
record, err := snapshot.NewRecord(a.Kind(), item.UUID, values, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("build theme record %s: %w", item.UUID, err)
|
||||
}
|
||||
records = append(records, record)
|
||||
}
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// Apply 将快照记录应用到本地主题表。
|
||||
func (a *ThemeAdapter) Apply(ctx context.Context, records []snapshot.Record) error {
|
||||
applyCtx := importContext(ctx)
|
||||
|
||||
for _, record := range records {
|
||||
found, err := a.client.Theme.Query().Where(theme.UUIDEQ(record.ID)).First(applyCtx)
|
||||
switch {
|
||||
case ent.IsNotFound(err):
|
||||
if err := a.create(applyCtx, record); err != nil {
|
||||
return err
|
||||
}
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
if shouldApplyRecord(found.UpdatedAt, record) {
|
||||
if err := a.update(applyCtx, found.ID, record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// create 创建新的主题记录。
|
||||
func (a *ThemeAdapter) create(ctx context.Context, record snapshot.Record) error {
|
||||
builder := a.client.Theme.Create().
|
||||
SetUUID(record.ID).
|
||||
SetName(stringValue(record, theme.FieldName)).
|
||||
SetType(theme.Type(stringValue(record, theme.FieldType))).
|
||||
SetColors(mapValue(record, theme.FieldColors)).
|
||||
SetCreatedAt(stringValue(record, theme.FieldCreatedAt)).
|
||||
SetUpdatedAt(stringValue(record, theme.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
|
||||
// update 更新已有主题记录。
|
||||
func (a *ThemeAdapter) update(ctx context.Context, id int, record snapshot.Record) error {
|
||||
builder := a.client.Theme.UpdateOneID(id).
|
||||
SetName(stringValue(record, theme.FieldName)).
|
||||
SetType(theme.Type(stringValue(record, theme.FieldType))).
|
||||
SetColors(mapValue(record, theme.FieldColors)).
|
||||
SetUpdatedAt(stringValue(record, theme.FieldUpdatedAt))
|
||||
|
||||
if deletedAt := recordDeletedAtString(record); deletedAt != nil {
|
||||
builder.SetDeletedAt(*deletedAt)
|
||||
} else {
|
||||
builder.ClearDeletedAt()
|
||||
}
|
||||
|
||||
return builder.Exec(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user