🎨 Optimize storage logic
This commit is contained in:
@@ -33,6 +33,11 @@ export class AppConfig {
|
|||||||
*/
|
*/
|
||||||
"updates": UpdatesConfig;
|
"updates": UpdatesConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Git同步设置
|
||||||
|
*/
|
||||||
|
"sync": GitSyncConfig;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 配置元数据
|
* 配置元数据
|
||||||
*/
|
*/
|
||||||
@@ -52,6 +57,9 @@ export class AppConfig {
|
|||||||
if (!("updates" in $$source)) {
|
if (!("updates" in $$source)) {
|
||||||
this["updates"] = (new UpdatesConfig());
|
this["updates"] = (new UpdatesConfig());
|
||||||
}
|
}
|
||||||
|
if (!("sync" in $$source)) {
|
||||||
|
this["sync"] = (new GitSyncConfig());
|
||||||
|
}
|
||||||
if (!("metadata" in $$source)) {
|
if (!("metadata" in $$source)) {
|
||||||
this["metadata"] = (new ConfigMetadata());
|
this["metadata"] = (new ConfigMetadata());
|
||||||
}
|
}
|
||||||
@@ -68,6 +76,7 @@ export class AppConfig {
|
|||||||
const $$createField2_0 = $$createType2;
|
const $$createField2_0 = $$createType2;
|
||||||
const $$createField3_0 = $$createType3;
|
const $$createField3_0 = $$createType3;
|
||||||
const $$createField4_0 = $$createType4;
|
const $$createField4_0 = $$createType4;
|
||||||
|
const $$createField5_0 = $$createType5;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("general" in $$parsedSource) {
|
if ("general" in $$parsedSource) {
|
||||||
$$parsedSource["general"] = $$createField0_0($$parsedSource["general"]);
|
$$parsedSource["general"] = $$createField0_0($$parsedSource["general"]);
|
||||||
@@ -81,8 +90,11 @@ export class AppConfig {
|
|||||||
if ("updates" in $$parsedSource) {
|
if ("updates" in $$parsedSource) {
|
||||||
$$parsedSource["updates"] = $$createField3_0($$parsedSource["updates"]);
|
$$parsedSource["updates"] = $$createField3_0($$parsedSource["updates"]);
|
||||||
}
|
}
|
||||||
|
if ("sync" in $$parsedSource) {
|
||||||
|
$$parsedSource["sync"] = $$createField4_0($$parsedSource["sync"]);
|
||||||
|
}
|
||||||
if ("metadata" in $$parsedSource) {
|
if ("metadata" in $$parsedSource) {
|
||||||
$$parsedSource["metadata"] = $$createField4_0($$parsedSource["metadata"]);
|
$$parsedSource["metadata"] = $$createField5_0($$parsedSource["metadata"]);
|
||||||
}
|
}
|
||||||
return new AppConfig($$parsedSource as Partial<AppConfig>);
|
return new AppConfig($$parsedSource as Partial<AppConfig>);
|
||||||
}
|
}
|
||||||
@@ -126,7 +138,7 @@ export class AppearanceConfig {
|
|||||||
* Creates a new AppearanceConfig instance from a string or object.
|
* Creates a new AppearanceConfig instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): AppearanceConfig {
|
static createFrom($$source: any = {}): AppearanceConfig {
|
||||||
const $$createField2_0 = $$createType5;
|
const $$createField2_0 = $$createType6;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("customTheme" in $$parsedSource) {
|
if ("customTheme" in $$parsedSource) {
|
||||||
$$parsedSource["customTheme"] = $$createField2_0($$parsedSource["customTheme"]);
|
$$parsedSource["customTheme"] = $$createField2_0($$parsedSource["customTheme"]);
|
||||||
@@ -135,6 +147,31 @@ export class AppearanceConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthMethod 定义Git认证方式
|
||||||
|
*/
|
||||||
|
export enum AuthMethod {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 个人访问令牌
|
||||||
|
*/
|
||||||
|
Token = "token",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SSH密钥
|
||||||
|
*/
|
||||||
|
SSHKey = "ssh_key",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名密码
|
||||||
|
*/
|
||||||
|
UserPass = "user_pass",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ConfigMetadata 配置元数据
|
* ConfigMetadata 配置元数据
|
||||||
*/
|
*/
|
||||||
@@ -200,8 +237,8 @@ export class CustomThemeConfig {
|
|||||||
* Creates a new CustomThemeConfig instance from a string or object.
|
* Creates a new CustomThemeConfig instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): CustomThemeConfig {
|
static createFrom($$source: any = {}): CustomThemeConfig {
|
||||||
const $$createField0_0 = $$createType6;
|
const $$createField0_0 = $$createType7;
|
||||||
const $$createField1_0 = $$createType6;
|
const $$createField1_0 = $$createType7;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("darkTheme" in $$parsedSource) {
|
if ("darkTheme" in $$parsedSource) {
|
||||||
$$parsedSource["darkTheme"] = $$createField0_0($$parsedSource["darkTheme"]);
|
$$parsedSource["darkTheme"] = $$createField0_0($$parsedSource["darkTheme"]);
|
||||||
@@ -224,6 +261,11 @@ export class Document {
|
|||||||
"updatedAt": time$0.Time;
|
"updatedAt": time$0.Time;
|
||||||
"is_deleted": boolean;
|
"is_deleted": boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 锁定标志,锁定的文档无法被删除
|
||||||
|
*/
|
||||||
|
"is_locked": boolean;
|
||||||
|
|
||||||
/** Creates a new Document instance. */
|
/** Creates a new Document instance. */
|
||||||
constructor($$source: Partial<Document> = {}) {
|
constructor($$source: Partial<Document> = {}) {
|
||||||
if (!("id" in $$source)) {
|
if (!("id" in $$source)) {
|
||||||
@@ -244,6 +286,9 @@ export class Document {
|
|||||||
if (!("is_deleted" in $$source)) {
|
if (!("is_deleted" in $$source)) {
|
||||||
this["is_deleted"] = false;
|
this["is_deleted"] = false;
|
||||||
}
|
}
|
||||||
|
if (!("is_locked" in $$source)) {
|
||||||
|
this["is_locked"] = false;
|
||||||
|
}
|
||||||
|
|
||||||
Object.assign(this, $$source);
|
Object.assign(this, $$source);
|
||||||
}
|
}
|
||||||
@@ -389,7 +434,7 @@ export class Extension {
|
|||||||
* Creates a new Extension instance from a string or object.
|
* Creates a new Extension instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): Extension {
|
static createFrom($$source: any = {}): Extension {
|
||||||
const $$createField3_0 = $$createType7;
|
const $$createField3_0 = $$createType8;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("config" in $$parsedSource) {
|
if ("config" in $$parsedSource) {
|
||||||
$$parsedSource["config"] = $$createField3_0($$parsedSource["config"]);
|
$$parsedSource["config"] = $$createField3_0($$parsedSource["config"]);
|
||||||
@@ -522,7 +567,7 @@ export class GeneralConfig {
|
|||||||
* Creates a new GeneralConfig instance from a string or object.
|
* Creates a new GeneralConfig instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): GeneralConfig {
|
static createFrom($$source: any = {}): GeneralConfig {
|
||||||
const $$createField5_0 = $$createType9;
|
const $$createField5_0 = $$createType10;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("globalHotkey" in $$parsedSource) {
|
if ("globalHotkey" in $$parsedSource) {
|
||||||
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
$$parsedSource["globalHotkey"] = $$createField5_0($$parsedSource["globalHotkey"]);
|
||||||
@@ -531,6 +576,91 @@ export class GeneralConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GitSyncConfig 保存Git同步的配置信息
|
||||||
|
*/
|
||||||
|
export class GitSyncConfig {
|
||||||
|
"enabled": boolean;
|
||||||
|
"repo_url": string;
|
||||||
|
"branch": string;
|
||||||
|
"auth_method": AuthMethod;
|
||||||
|
"username"?: string;
|
||||||
|
"password"?: string;
|
||||||
|
"token"?: string;
|
||||||
|
"ssh_key_path"?: string;
|
||||||
|
"ssh_key_passphrase"?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步间隔(分钟)
|
||||||
|
*/
|
||||||
|
"sync_interval": number;
|
||||||
|
"last_sync_time": time$0.Time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否启用自动同步
|
||||||
|
*/
|
||||||
|
"auto_sync": boolean;
|
||||||
|
"local_repo_path": string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并冲突策略
|
||||||
|
*/
|
||||||
|
"sync_strategy": SyncStrategy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 要同步的文件列表,默认为数据库文件
|
||||||
|
*/
|
||||||
|
"files_to_sync": string[];
|
||||||
|
|
||||||
|
/** Creates a new GitSyncConfig instance. */
|
||||||
|
constructor($$source: Partial<GitSyncConfig> = {}) {
|
||||||
|
if (!("enabled" in $$source)) {
|
||||||
|
this["enabled"] = false;
|
||||||
|
}
|
||||||
|
if (!("repo_url" in $$source)) {
|
||||||
|
this["repo_url"] = "";
|
||||||
|
}
|
||||||
|
if (!("branch" in $$source)) {
|
||||||
|
this["branch"] = "";
|
||||||
|
}
|
||||||
|
if (!("auth_method" in $$source)) {
|
||||||
|
this["auth_method"] = ("" as AuthMethod);
|
||||||
|
}
|
||||||
|
if (!("sync_interval" in $$source)) {
|
||||||
|
this["sync_interval"] = 0;
|
||||||
|
}
|
||||||
|
if (!("last_sync_time" in $$source)) {
|
||||||
|
this["last_sync_time"] = null;
|
||||||
|
}
|
||||||
|
if (!("auto_sync" in $$source)) {
|
||||||
|
this["auto_sync"] = false;
|
||||||
|
}
|
||||||
|
if (!("local_repo_path" in $$source)) {
|
||||||
|
this["local_repo_path"] = "";
|
||||||
|
}
|
||||||
|
if (!("sync_strategy" in $$source)) {
|
||||||
|
this["sync_strategy"] = ("" as SyncStrategy);
|
||||||
|
}
|
||||||
|
if (!("files_to_sync" in $$source)) {
|
||||||
|
this["files_to_sync"] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.assign(this, $$source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new GitSyncConfig instance from a string or object.
|
||||||
|
*/
|
||||||
|
static createFrom($$source: any = {}): GitSyncConfig {
|
||||||
|
const $$createField14_0 = $$createType11;
|
||||||
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
|
if ("files_to_sync" in $$parsedSource) {
|
||||||
|
$$parsedSource["files_to_sync"] = $$createField14_0($$parsedSource["files_to_sync"]);
|
||||||
|
}
|
||||||
|
return new GitSyncConfig($$parsedSource as Partial<GitSyncConfig>);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* GiteaConfig Gitea配置
|
* GiteaConfig Gitea配置
|
||||||
*/
|
*/
|
||||||
@@ -1038,6 +1168,26 @@ export enum LanguageType {
|
|||||||
LangEnUS = "en-US",
|
LangEnUS = "en-US",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SyncStrategy 定义同步策略
|
||||||
|
*/
|
||||||
|
export enum SyncStrategy {
|
||||||
|
/**
|
||||||
|
* The Go zero value for the underlying type of the enum.
|
||||||
|
*/
|
||||||
|
$zero = "",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LocalFirst 本地优先:如有冲突,保留本地修改
|
||||||
|
*/
|
||||||
|
LocalFirst = "local_first",
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RemoteFirst 远程优先:如有冲突,采用远程版本
|
||||||
|
*/
|
||||||
|
RemoteFirst = "remote_first",
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SystemThemeType 系统主题类型定义
|
* SystemThemeType 系统主题类型定义
|
||||||
*/
|
*/
|
||||||
@@ -1389,8 +1539,8 @@ export class UpdatesConfig {
|
|||||||
* Creates a new UpdatesConfig instance from a string or object.
|
* Creates a new UpdatesConfig instance from a string or object.
|
||||||
*/
|
*/
|
||||||
static createFrom($$source: any = {}): UpdatesConfig {
|
static createFrom($$source: any = {}): UpdatesConfig {
|
||||||
const $$createField6_0 = $$createType10;
|
const $$createField6_0 = $$createType12;
|
||||||
const $$createField7_0 = $$createType11;
|
const $$createField7_0 = $$createType13;
|
||||||
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
|
||||||
if ("github" in $$parsedSource) {
|
if ("github" in $$parsedSource) {
|
||||||
$$parsedSource["github"] = $$createField6_0($$parsedSource["github"]);
|
$$parsedSource["github"] = $$createField6_0($$parsedSource["github"]);
|
||||||
@@ -1407,16 +1557,18 @@ const $$createType0 = GeneralConfig.createFrom;
|
|||||||
const $$createType1 = EditingConfig.createFrom;
|
const $$createType1 = EditingConfig.createFrom;
|
||||||
const $$createType2 = AppearanceConfig.createFrom;
|
const $$createType2 = AppearanceConfig.createFrom;
|
||||||
const $$createType3 = UpdatesConfig.createFrom;
|
const $$createType3 = UpdatesConfig.createFrom;
|
||||||
const $$createType4 = ConfigMetadata.createFrom;
|
const $$createType4 = GitSyncConfig.createFrom;
|
||||||
const $$createType5 = CustomThemeConfig.createFrom;
|
const $$createType5 = ConfigMetadata.createFrom;
|
||||||
const $$createType6 = ThemeColorConfig.createFrom;
|
const $$createType6 = CustomThemeConfig.createFrom;
|
||||||
var $$createType7 = (function $$initCreateType7(...args): any {
|
const $$createType7 = ThemeColorConfig.createFrom;
|
||||||
if ($$createType7 === $$initCreateType7) {
|
var $$createType8 = (function $$initCreateType8(...args): any {
|
||||||
$$createType7 = $$createType8;
|
if ($$createType8 === $$initCreateType8) {
|
||||||
|
$$createType8 = $$createType9;
|
||||||
}
|
}
|
||||||
return $$createType7(...args);
|
return $$createType8(...args);
|
||||||
});
|
});
|
||||||
const $$createType8 = $Create.Map($Create.Any, $Create.Any);
|
const $$createType9 = $Create.Map($Create.Any, $Create.Any);
|
||||||
const $$createType9 = HotkeyCombo.createFrom;
|
const $$createType10 = HotkeyCombo.createFrom;
|
||||||
const $$createType10 = GithubConfig.createFrom;
|
const $$createType11 = $Create.Array($Create.Any);
|
||||||
const $$createType11 = GiteaConfig.createFrom;
|
const $$createType12 = GithubConfig.createFrom;
|
||||||
|
const $$createType13 = GiteaConfig.createFrom;
|
||||||
|
@@ -22,6 +22,14 @@ export function OnDataPathChanged(): Promise<void> & { cancel(): void } {
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RegisterModel 注册模型与表的映射关系
|
||||||
|
*/
|
||||||
|
export function RegisterModel(tableName: string, model: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(175397515, tableName, model) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ServiceShutdown shuts down the service when the application closes
|
* ServiceShutdown shuts down the service when the application closes
|
||||||
*/
|
*/
|
||||||
|
@@ -81,6 +81,14 @@ export function ListDeletedDocumentsMeta(): Promise<(models$0.Document | null)[]
|
|||||||
return $typingPromise;
|
return $typingPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LockDocument 锁定文档,防止删除
|
||||||
|
*/
|
||||||
|
export function LockDocument(id: number): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(1889494473, id) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RestoreDocument restores a deleted document
|
* RestoreDocument restores a deleted document
|
||||||
*/
|
*/
|
||||||
@@ -97,6 +105,14 @@ export function ServiceStartup(options: application$0.ServiceOptions): Promise<v
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* UnlockDocument 解锁文档
|
||||||
|
*/
|
||||||
|
export function UnlockDocument(id: number): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(222307930, id) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* UpdateDocumentContent updates the content of a document
|
* UpdateDocumentContent updates the content of a document
|
||||||
*/
|
*/
|
||||||
|
@@ -11,6 +11,9 @@ import {
|
|||||||
TabType,
|
TabType,
|
||||||
UpdatesConfig,
|
UpdatesConfig,
|
||||||
UpdateSourceType,
|
UpdateSourceType,
|
||||||
|
GitSyncConfig,
|
||||||
|
AuthMethod,
|
||||||
|
SyncStrategy
|
||||||
} from '@/../bindings/voidraft/internal/models/models';
|
} from '@/../bindings/voidraft/internal/models/models';
|
||||||
import {useI18n} from 'vue-i18n';
|
import {useI18n} from 'vue-i18n';
|
||||||
import {ConfigUtils} from '@/utils/configUtils';
|
import {ConfigUtils} from '@/utils/configUtils';
|
||||||
@@ -48,6 +51,10 @@ type UpdatesConfigKeyMap = {
|
|||||||
readonly [K in keyof UpdatesConfig]: string;
|
readonly [K in keyof UpdatesConfig]: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type SyncConfigKeyMap = {
|
||||||
|
readonly [K in keyof GitSyncConfig]: string;
|
||||||
|
};
|
||||||
|
|
||||||
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
type NumberConfigKey = 'fontSize' | 'tabSize' | 'lineHeight';
|
||||||
|
|
||||||
// 配置键映射
|
// 配置键映射
|
||||||
@@ -88,6 +95,24 @@ const UPDATES_CONFIG_KEY_MAP: UpdatesConfigKeyMap = {
|
|||||||
gitea: 'updates.gitea'
|
gitea: 'updates.gitea'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
const SYNC_CONFIG_KEY_MAP: SyncConfigKeyMap = {
|
||||||
|
enabled: 'sync.enabled',
|
||||||
|
repo_url: 'sync.repo_url',
|
||||||
|
branch: 'sync.branch',
|
||||||
|
auth_method: 'sync.auth_method',
|
||||||
|
username: 'sync.username',
|
||||||
|
password: 'sync.password',
|
||||||
|
token: 'sync.token',
|
||||||
|
ssh_key_path: 'sync.ssh_key_path',
|
||||||
|
ssh_key_passphrase: 'sync.ssh_key_passphrase',
|
||||||
|
sync_interval: 'sync.sync_interval',
|
||||||
|
last_sync_time: 'sync.last_sync_time',
|
||||||
|
auto_sync: 'sync.auto_sync',
|
||||||
|
local_repo_path: 'sync.local_repo_path',
|
||||||
|
sync_strategy: 'sync.sync_strategy',
|
||||||
|
files_to_sync: 'sync.files_to_sync'
|
||||||
|
} as const;
|
||||||
|
|
||||||
// 配置限制
|
// 配置限制
|
||||||
const CONFIG_LIMITS = {
|
const CONFIG_LIMITS = {
|
||||||
fontSize: {min: 12, max: 28, default: 13},
|
fontSize: {min: 12, max: 28, default: 13},
|
||||||
@@ -261,6 +286,23 @@ const DEFAULT_CONFIG: AppConfig = {
|
|||||||
repo: "voidraft",
|
repo: "voidraft",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
sync: {
|
||||||
|
enabled: false,
|
||||||
|
repo_url: "",
|
||||||
|
branch: "main",
|
||||||
|
auth_method: AuthMethod.Token,
|
||||||
|
username: "",
|
||||||
|
password: "",
|
||||||
|
token: "",
|
||||||
|
ssh_key_path: "",
|
||||||
|
ssh_key_passphrase: "",
|
||||||
|
sync_interval: 60,
|
||||||
|
last_sync_time: null,
|
||||||
|
auto_sync: true,
|
||||||
|
local_repo_path: "",
|
||||||
|
sync_strategy: SyncStrategy.LocalFirst,
|
||||||
|
files_to_sync: ["voidraft.db"]
|
||||||
|
},
|
||||||
metadata: {
|
metadata: {
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
lastUpdated: new Date().toString(),
|
lastUpdated: new Date().toString(),
|
||||||
@@ -348,6 +390,21 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
state.config.updates[key] = value;
|
state.config.updates[key] = value;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateSyncConfig = async <K extends keyof GitSyncConfig>(key: K, value: GitSyncConfig[K]): Promise<void> => {
|
||||||
|
// 确保配置已加载
|
||||||
|
if (!state.configLoaded && !state.isLoading) {
|
||||||
|
await initConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
const backendKey = SYNC_CONFIG_KEY_MAP[key];
|
||||||
|
if (!backendKey) {
|
||||||
|
throw new Error(`No backend key mapping found for sync.${key.toString()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await ConfigService.Set(backendKey, value);
|
||||||
|
state.config.sync[key] = value;
|
||||||
|
};
|
||||||
|
|
||||||
// 加载配置
|
// 加载配置
|
||||||
const initConfig = async (): Promise<void> => {
|
const initConfig = async (): Promise<void> => {
|
||||||
if (state.isLoading) return;
|
if (state.isLoading) return;
|
||||||
@@ -362,6 +419,7 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing);
|
if (appConfig.editing) Object.assign(state.config.editing, appConfig.editing);
|
||||||
if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance);
|
if (appConfig.appearance) Object.assign(state.config.appearance, appConfig.appearance);
|
||||||
if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates);
|
if (appConfig.updates) Object.assign(state.config.updates, appConfig.updates);
|
||||||
|
if (appConfig.sync) Object.assign(state.config.sync, appConfig.sync);
|
||||||
if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata);
|
if (appConfig.metadata) Object.assign(state.config.metadata, appConfig.metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -594,6 +652,20 @@ export const useConfigStore = defineStore('config', () => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 更新配置相关方法
|
// 更新配置相关方法
|
||||||
setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value)
|
setAutoUpdate: async (value: boolean) => await updateUpdatesConfig('autoUpdate', value),
|
||||||
|
|
||||||
|
// Git同步配置相关方法
|
||||||
|
setGitSyncEnabled: (value: boolean) => updateSyncConfig('enabled', value),
|
||||||
|
setGitRepoUrl: (value: string) => updateSyncConfig('repo_url', value),
|
||||||
|
setGitBranch: (value: string) => updateSyncConfig('branch', value),
|
||||||
|
setAuthMethod: (value: AuthMethod) => updateSyncConfig('auth_method', value),
|
||||||
|
setGitUsername: (value: string) => updateSyncConfig('username', value),
|
||||||
|
setGitPassword: (value: string) => updateSyncConfig('password', value),
|
||||||
|
setGitToken: (value: string) => updateSyncConfig('token', value),
|
||||||
|
setSSHKeyPath: (value: string) => updateSyncConfig('ssh_key_path', value),
|
||||||
|
setSyncInterval: (value: number) => updateSyncConfig('sync_interval', value),
|
||||||
|
setAutoSync: (value: boolean) => updateSyncConfig('auto_sync', value),
|
||||||
|
setSyncStrategy: (value: SyncStrategy) => updateSyncConfig('sync_strategy', value),
|
||||||
|
setFilesToSync: (value: string[]) => updateSyncConfig('files_to_sync', value),
|
||||||
};
|
};
|
||||||
});
|
});
|
@@ -124,6 +124,7 @@ type AppConfig struct {
|
|||||||
Editing EditingConfig `json:"editing"` // 编辑设置
|
Editing EditingConfig `json:"editing"` // 编辑设置
|
||||||
Appearance AppearanceConfig `json:"appearance"` // 外观设置
|
Appearance AppearanceConfig `json:"appearance"` // 外观设置
|
||||||
Updates UpdatesConfig `json:"updates"` // 更新设置
|
Updates UpdatesConfig `json:"updates"` // 更新设置
|
||||||
|
Sync GitSyncConfig `json:"sync"` // Git同步设置
|
||||||
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,6 +139,7 @@ func NewDefaultAppConfig() *AppConfig {
|
|||||||
|
|
||||||
currentDir, _ := os.UserHomeDir()
|
currentDir, _ := os.UserHomeDir()
|
||||||
dataDir := filepath.Join(currentDir, ".voidraft", "data")
|
dataDir := filepath.Join(currentDir, ".voidraft", "data")
|
||||||
|
repoPath := filepath.Join(currentDir, ".voidraft", "sync-repo")
|
||||||
|
|
||||||
return &AppConfig{
|
return &AppConfig{
|
||||||
General: GeneralConfig{
|
General: GeneralConfig{
|
||||||
@@ -173,7 +175,7 @@ func NewDefaultAppConfig() *AppConfig {
|
|||||||
CustomTheme: *NewDefaultCustomThemeConfig(),
|
CustomTheme: *NewDefaultCustomThemeConfig(),
|
||||||
},
|
},
|
||||||
Updates: UpdatesConfig{
|
Updates: UpdatesConfig{
|
||||||
Version: "1.0.0",
|
Version: "1.2.0",
|
||||||
AutoUpdate: true,
|
AutoUpdate: true,
|
||||||
PrimarySource: UpdateSourceGithub,
|
PrimarySource: UpdateSourceGithub,
|
||||||
BackupSource: UpdateSourceGitea,
|
BackupSource: UpdateSourceGitea,
|
||||||
@@ -189,9 +191,25 @@ func NewDefaultAppConfig() *AppConfig {
|
|||||||
Repo: "voidraft",
|
Repo: "voidraft",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Sync: GitSyncConfig{
|
||||||
|
Enabled: false,
|
||||||
|
RepoURL: "",
|
||||||
|
Branch: "main",
|
||||||
|
AuthMethod: Token,
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
Token: "",
|
||||||
|
SSHKeyPath: "",
|
||||||
|
SyncInterval: 60,
|
||||||
|
LastSyncTime: time.Time{},
|
||||||
|
AutoSync: true,
|
||||||
|
LocalRepoPath: repoPath,
|
||||||
|
SyncStrategy: LocalFirst,
|
||||||
|
FilesToSync: []string{"voidraft.db"},
|
||||||
|
},
|
||||||
Metadata: ConfigMetadata{
|
Metadata: ConfigMetadata{
|
||||||
LastUpdated: time.Now().Format(time.RFC3339),
|
LastUpdated: time.Now().Format(time.RFC3339),
|
||||||
Version: "1.0.0",
|
Version: "1.2.0",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -12,9 +12,10 @@ type Document struct {
|
|||||||
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
CreatedAt time.Time `json:"createdAt" db:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
UpdatedAt time.Time `json:"updatedAt" db:"updated_at"`
|
||||||
IsDeleted bool `json:"is_deleted"`
|
IsDeleted bool `json:"is_deleted"`
|
||||||
|
IsLocked bool `json:"is_locked" db:"is_locked"` // 锁定标志,锁定的文档无法被删除
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDocument 创建新文档(不需要传ID,由数据库自增)
|
// NewDocument 创建新文档
|
||||||
func NewDocument(title, content string) *Document {
|
func NewDocument(title, content string) *Document {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
return &Document{
|
return &Document{
|
||||||
@@ -23,6 +24,7 @@ func NewDocument(title, content string) *Document {
|
|||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
IsDeleted: false,
|
IsDeleted: false,
|
||||||
|
IsLocked: false, // 默认不锁定
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -4,10 +4,10 @@ import "time"
|
|||||||
|
|
||||||
// Extension 单个扩展配置
|
// Extension 单个扩展配置
|
||||||
type Extension struct {
|
type Extension struct {
|
||||||
ID ExtensionID `json:"id"` // 扩展唯一标识
|
ID ExtensionID `json:"id" db:"id"` // 扩展唯一标识
|
||||||
Enabled bool `json:"enabled"` // 是否启用
|
Enabled bool `json:"enabled" db:"enabled"` // 是否启用
|
||||||
IsDefault bool `json:"isDefault"` // 是否为默认扩展
|
IsDefault bool `json:"isDefault" db:"is_default"` // 是否为默认扩展
|
||||||
Config ExtensionConfig `json:"config"` // 扩展配置项
|
Config ExtensionConfig `json:"config" db:"config"` // 扩展配置项
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtensionID 扩展标识符
|
// ExtensionID 扩展标识符
|
||||||
|
@@ -4,11 +4,11 @@ import "time"
|
|||||||
|
|
||||||
// KeyBinding 单个快捷键绑定
|
// KeyBinding 单个快捷键绑定
|
||||||
type KeyBinding struct {
|
type KeyBinding struct {
|
||||||
Command KeyBindingCommand `json:"command"` // 快捷键动作
|
Command KeyBindingCommand `json:"command" db:"command"` // 快捷键动作
|
||||||
Extension ExtensionID `json:"extension"` // 所属扩展
|
Extension ExtensionID `json:"extension" db:"extension"` // 所属扩展
|
||||||
Key string `json:"key"` // 快捷键组合(如 "Mod-f", "Ctrl-Shift-p")
|
Key string `json:"key" db:"key"` // 快捷键组合(如 "Mod-f", "Ctrl-Shift-p")
|
||||||
Enabled bool `json:"enabled"` // 是否启用
|
Enabled bool `json:"enabled" db:"enabled"` // 是否启用
|
||||||
IsDefault bool `json:"isDefault"` // 是否为默认快捷键
|
IsDefault bool `json:"isDefault" db:"is_default"` // 是否为默认快捷键
|
||||||
}
|
}
|
||||||
|
|
||||||
// KeyBindingCommand 快捷键命令
|
// KeyBindingCommand 快捷键命令
|
||||||
|
63
internal/models/sync.go
Normal file
63
internal/models/sync.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
// AuthMethod 定义Git认证方式
|
||||||
|
type AuthMethod string
|
||||||
|
|
||||||
|
const (
|
||||||
|
Token AuthMethod = "token" // 个人访问令牌
|
||||||
|
SSHKey AuthMethod = "ssh_key" // SSH密钥
|
||||||
|
UserPass AuthMethod = "user_pass" // 用户名密码
|
||||||
|
)
|
||||||
|
|
||||||
|
// SyncStrategy 定义同步策略
|
||||||
|
type SyncStrategy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LocalFirst 本地优先:如有冲突,保留本地修改
|
||||||
|
LocalFirst SyncStrategy = "local_first"
|
||||||
|
// RemoteFirst 远程优先:如有冲突,采用远程版本
|
||||||
|
RemoteFirst SyncStrategy = "remote_first"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitSyncConfig 保存Git同步的配置信息
|
||||||
|
type GitSyncConfig struct {
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
RepoURL string `json:"repo_url"`
|
||||||
|
Branch string `json:"branch"`
|
||||||
|
AuthMethod AuthMethod `json:"auth_method"`
|
||||||
|
Username string `json:"username,omitempty"`
|
||||||
|
Password string `json:"password,omitempty"`
|
||||||
|
Token string `json:"token,omitempty"`
|
||||||
|
SSHKeyPath string `json:"ssh_key_path,omitempty"`
|
||||||
|
SSHKeyPassphrase string `json:"ssh_key_passphrase,omitempty"`
|
||||||
|
SyncInterval int `json:"sync_interval"` // 同步间隔(分钟)
|
||||||
|
LastSyncTime time.Time `json:"last_sync_time"`
|
||||||
|
AutoSync bool `json:"auto_sync"` // 是否启用自动同步
|
||||||
|
LocalRepoPath string `json:"local_repo_path"`
|
||||||
|
SyncStrategy SyncStrategy `json:"sync_strategy"` // 合并冲突策略
|
||||||
|
FilesToSync []string `json:"files_to_sync"` // 要同步的文件列表,默认为数据库文件
|
||||||
|
}
|
||||||
|
|
||||||
|
// GitSyncStatus 保存同步状态信息
|
||||||
|
type GitSyncStatus struct {
|
||||||
|
IsSyncing bool `json:"is_syncing"`
|
||||||
|
LastSyncTime time.Time `json:"last_sync_time"`
|
||||||
|
LastSyncStatus string `json:"last_sync_status"` // success, failed, conflict
|
||||||
|
LastErrorMsg string `json:"last_error_msg,omitempty"`
|
||||||
|
LastCommitID string `json:"last_commit_id,omitempty"`
|
||||||
|
RemoteCommitID string `json:"remote_commit_id,omitempty"`
|
||||||
|
CommitAhead int `json:"commit_ahead"` // 本地领先远程的提交数
|
||||||
|
CommitBehind int `json:"commit_behind"` // 本地落后远程的提交数
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncLogEntry 记录每次同步操作的日志
|
||||||
|
type SyncLogEntry struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Timestamp time.Time `json:"timestamp"`
|
||||||
|
Action string `json:"action"` // push, pull, reset
|
||||||
|
Status string `json:"status"` // success, failed
|
||||||
|
Message string `json:"message,omitempty"`
|
||||||
|
ChangedFiles int `json:"changed_files"`
|
||||||
|
}
|
@@ -5,7 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
"voidraft/internal/models"
|
||||||
|
|
||||||
"github.com/wailsapp/wails/v3/pkg/application"
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
"github.com/wailsapp/wails/v3/pkg/services/log"
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
||||||
@@ -31,7 +35,8 @@ CREATE TABLE IF NOT EXISTS documents (
|
|||||||
content TEXT DEFAULT '∞∞∞text-a',
|
content TEXT DEFAULT '∞∞∞text-a',
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
is_deleted INTEGER DEFAULT 0
|
is_deleted INTEGER DEFAULT 0,
|
||||||
|
is_locked INTEGER DEFAULT 0
|
||||||
)`
|
)`
|
||||||
|
|
||||||
// Extensions table
|
// Extensions table
|
||||||
@@ -58,8 +63,34 @@ CREATE TABLE IF NOT EXISTS key_bindings (
|
|||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE(command, extension)
|
UNIQUE(command, extension)
|
||||||
)`
|
)`
|
||||||
|
|
||||||
|
// Git sync logs table
|
||||||
|
sqlCreateSyncLogsTable = `
|
||||||
|
CREATE TABLE IF NOT EXISTS sync_logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
action TEXT NOT NULL,
|
||||||
|
status TEXT NOT NULL,
|
||||||
|
message TEXT,
|
||||||
|
commit_id TEXT,
|
||||||
|
changed_files INTEGER DEFAULT 0,
|
||||||
|
repo_url TEXT NOT NULL,
|
||||||
|
branch TEXT NOT NULL
|
||||||
|
)`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ColumnInfo 存储列的信息
|
||||||
|
type ColumnInfo struct {
|
||||||
|
SQLType string
|
||||||
|
DefaultValue string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TableModel 表示表与模型之间的映射关系
|
||||||
|
type TableModel struct {
|
||||||
|
TableName string
|
||||||
|
Model interface{}
|
||||||
|
}
|
||||||
|
|
||||||
// DatabaseService provides shared database functionality
|
// DatabaseService provides shared database functionality
|
||||||
type DatabaseService struct {
|
type DatabaseService struct {
|
||||||
configService *ConfigService
|
configService *ConfigService
|
||||||
@@ -67,6 +98,7 @@ type DatabaseService struct {
|
|||||||
SQLite *sqlite.Service
|
SQLite *sqlite.Service
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
ctx context.Context
|
ctx context.Context
|
||||||
|
tableModels []TableModel // 注册的表模型
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewDatabaseService creates a new database service
|
// NewDatabaseService creates a new database service
|
||||||
@@ -75,11 +107,28 @@ func NewDatabaseService(configService *ConfigService, logger *log.Service) *Data
|
|||||||
logger = log.New()
|
logger = log.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DatabaseService{
|
ds := &DatabaseService{
|
||||||
configService: configService,
|
configService: configService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
SQLite: sqlite.New(),
|
SQLite: sqlite.New(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 注册所有模型
|
||||||
|
ds.registerAllModels()
|
||||||
|
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
|
// registerAllModels 注册所有数据模型,集中管理表-模型映射
|
||||||
|
func (ds *DatabaseService) registerAllModels() {
|
||||||
|
// 文档表
|
||||||
|
ds.RegisterModel("documents", &models.Document{})
|
||||||
|
// 扩展表
|
||||||
|
ds.RegisterModel("extensions", &models.Extension{})
|
||||||
|
// 快捷键表
|
||||||
|
ds.RegisterModel("key_bindings", &models.KeyBinding{})
|
||||||
|
// 同步日志表
|
||||||
|
ds.RegisterModel("sync_logs", &models.SyncLogEntry{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStartup initializes the service when the application starts
|
// ServiceStartup initializes the service when the application starts
|
||||||
@@ -135,6 +184,11 @@ func (ds *DatabaseService) initDatabase() error {
|
|||||||
return fmt.Errorf("failed to create indexes: %w", err)
|
return fmt.Errorf("failed to create indexes: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 执行模型与表结构同步
|
||||||
|
if err := ds.syncAllModelTables(); err != nil {
|
||||||
|
return fmt.Errorf("failed to sync model tables: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +207,7 @@ func (ds *DatabaseService) createTables() error {
|
|||||||
sqlCreateDocumentsTable,
|
sqlCreateDocumentsTable,
|
||||||
sqlCreateExtensionsTable,
|
sqlCreateExtensionsTable,
|
||||||
sqlCreateKeyBindingsTable,
|
sqlCreateKeyBindingsTable,
|
||||||
|
sqlCreateSyncLogsTable,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
@@ -176,6 +231,10 @@ func (ds *DatabaseService) createIndexes() error {
|
|||||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`,
|
`CREATE INDEX IF NOT EXISTS idx_key_bindings_command ON key_bindings(command)`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`,
|
`CREATE INDEX IF NOT EXISTS idx_key_bindings_extension ON key_bindings(extension)`,
|
||||||
`CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`,
|
`CREATE INDEX IF NOT EXISTS idx_key_bindings_enabled ON key_bindings(enabled)`,
|
||||||
|
// Sync logs indexes
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_sync_logs_timestamp ON sync_logs(timestamp DESC)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_sync_logs_action ON sync_logs(action)`,
|
||||||
|
`CREATE INDEX IF NOT EXISTS idx_sync_logs_status ON sync_logs(status)`,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, index := range indexes {
|
for _, index := range indexes {
|
||||||
@@ -186,6 +245,149 @@ func (ds *DatabaseService) createIndexes() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterModel 注册模型与表的映射关系
|
||||||
|
func (ds *DatabaseService) RegisterModel(tableName string, model interface{}) {
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
ds.tableModels = append(ds.tableModels, TableModel{
|
||||||
|
TableName: tableName,
|
||||||
|
Model: model,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncAllModelTables 同步所有注册的模型与表结构
|
||||||
|
func (ds *DatabaseService) syncAllModelTables() error {
|
||||||
|
for _, tm := range ds.tableModels {
|
||||||
|
if err := ds.syncModelTable(tm.TableName, tm.Model); err != nil {
|
||||||
|
return fmt.Errorf("failed to sync table %s: %w", tm.TableName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncModelTable 同步模型与表结构
|
||||||
|
func (ds *DatabaseService) syncModelTable(tableName string, model interface{}) error {
|
||||||
|
// 获取表结构元数据
|
||||||
|
columns, err := ds.getTableColumns(tableName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get table columns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用反射从模型中提取字段信息
|
||||||
|
expectedColumns, err := ds.getModelColumns(model)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get model columns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查缺失的列并添加
|
||||||
|
for colName, colInfo := range expectedColumns {
|
||||||
|
if _, exists := columns[colName]; !exists {
|
||||||
|
// 执行添加列的SQL
|
||||||
|
alterSQL := fmt.Sprintf("ALTER TABLE %s ADD COLUMN %s %s DEFAULT %s",
|
||||||
|
tableName, colName, colInfo.SQLType, colInfo.DefaultValue)
|
||||||
|
if err := ds.SQLite.Execute(alterSQL); err != nil {
|
||||||
|
return fmt.Errorf("failed to add column %s: %w", colName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getTableColumns 获取表的列信息
|
||||||
|
func (ds *DatabaseService) getTableColumns(table string) (map[string]string, error) {
|
||||||
|
query := fmt.Sprintf("PRAGMA table_info(%s)", table)
|
||||||
|
rows, err := ds.SQLite.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := make(map[string]string)
|
||||||
|
for _, row := range rows {
|
||||||
|
name, ok1 := row["name"].(string)
|
||||||
|
typeName, ok2 := row["type"].(string)
|
||||||
|
|
||||||
|
if !ok1 || !ok2 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
columns[name] = typeName
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getModelColumns 从模型结构体中提取数据库列信息
|
||||||
|
func (ds *DatabaseService) getModelColumns(model interface{}) (map[string]ColumnInfo, error) {
|
||||||
|
columns := make(map[string]ColumnInfo)
|
||||||
|
|
||||||
|
// 使用反射获取结构体的类型信息
|
||||||
|
t := reflect.TypeOf(model)
|
||||||
|
if t.Kind() == reflect.Ptr {
|
||||||
|
t = t.Elem()
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Kind() != reflect.Struct {
|
||||||
|
return nil, fmt.Errorf("model must be a struct or a pointer to struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有字段
|
||||||
|
for i := 0; i < t.NumField(); i++ {
|
||||||
|
field := t.Field(i)
|
||||||
|
|
||||||
|
// 获取数据库字段名
|
||||||
|
dbTag := field.Tag.Get("db")
|
||||||
|
if dbTag == "" {
|
||||||
|
// 如果没有db标签,则使用字段名的蛇形命名方式
|
||||||
|
dbTag = toSnakeCase(field.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取字段类型对应的SQL类型和默认值
|
||||||
|
sqlType, defaultVal := getSQLTypeAndDefault(field.Type)
|
||||||
|
|
||||||
|
columns[dbTag] = ColumnInfo{
|
||||||
|
SQLType: sqlType,
|
||||||
|
DefaultValue: defaultVal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// toSnakeCase 将驼峰命名转换为蛇形命名
|
||||||
|
func toSnakeCase(s string) string {
|
||||||
|
var result strings.Builder
|
||||||
|
for i, r := range s {
|
||||||
|
if i > 0 && 'A' <= r && r <= 'Z' {
|
||||||
|
result.WriteRune('_')
|
||||||
|
}
|
||||||
|
result.WriteRune(r)
|
||||||
|
}
|
||||||
|
return strings.ToLower(result.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSQLTypeAndDefault 根据Go类型获取对应的SQL类型和默认值
|
||||||
|
func getSQLTypeAndDefault(t reflect.Type) (string, string) {
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Bool:
|
||||||
|
return "INTEGER", "0"
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||||
|
return "INTEGER", "0"
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return "REAL", "0.0"
|
||||||
|
case reflect.String:
|
||||||
|
return "TEXT", "''"
|
||||||
|
default:
|
||||||
|
// 处理特殊类型
|
||||||
|
if t == reflect.TypeOf(time.Time{}) {
|
||||||
|
return "DATETIME", "CURRENT_TIMESTAMP"
|
||||||
|
}
|
||||||
|
return "TEXT", "NULL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ServiceShutdown shuts down the service when the application closes
|
// ServiceShutdown shuts down the service when the application closes
|
||||||
func (ds *DatabaseService) ServiceShutdown() error {
|
func (ds *DatabaseService) ServiceShutdown() error {
|
||||||
return ds.SQLite.Close()
|
return ds.SQLite.Close()
|
||||||
|
@@ -18,28 +18,28 @@ const (
|
|||||||
|
|
||||||
// Document operations
|
// Document operations
|
||||||
sqlGetDocumentByID = `
|
sqlGetDocumentByID = `
|
||||||
SELECT id, title, content, created_at, updated_at, is_deleted
|
SELECT id, title, content, created_at, updated_at, is_deleted, is_locked
|
||||||
FROM documents
|
FROM documents
|
||||||
WHERE id = ?`
|
WHERE id = ?`
|
||||||
|
|
||||||
sqlInsertDocument = `
|
sqlInsertDocument = `
|
||||||
INSERT INTO documents (title, content, created_at, updated_at, is_deleted)
|
INSERT INTO documents (title, content, created_at, updated_at, is_deleted, is_locked)
|
||||||
VALUES (?, ?, ?, ?, 0)`
|
VALUES (?, ?, ?, ?, 0, 0)`
|
||||||
|
|
||||||
sqlUpdateDocumentContent = `
|
sqlUpdateDocumentContent = `
|
||||||
UPDATE documents
|
UPDATE documents
|
||||||
SET content = ?, updated_at = ?
|
SET content = ?, updated_at = ?
|
||||||
WHERE id = ?`
|
WHERE id = ? AND is_deleted = 0`
|
||||||
|
|
||||||
sqlUpdateDocumentTitle = `
|
sqlUpdateDocumentTitle = `
|
||||||
UPDATE documents
|
UPDATE documents
|
||||||
SET title = ?, updated_at = ?
|
SET title = ?, updated_at = ?
|
||||||
WHERE id = ?`
|
WHERE id = ? AND is_deleted = 0`
|
||||||
|
|
||||||
sqlMarkDocumentAsDeleted = `
|
sqlMarkDocumentAsDeleted = `
|
||||||
UPDATE documents
|
UPDATE documents
|
||||||
SET is_deleted = 1, updated_at = ?
|
SET is_deleted = 1, updated_at = ?
|
||||||
WHERE id = ?`
|
WHERE id = ? AND is_locked = 0`
|
||||||
|
|
||||||
sqlRestoreDocument = `
|
sqlRestoreDocument = `
|
||||||
UPDATE documents
|
UPDATE documents
|
||||||
@@ -47,13 +47,13 @@ SET is_deleted = 0, updated_at = ?
|
|||||||
WHERE id = ?`
|
WHERE id = ?`
|
||||||
|
|
||||||
sqlListAllDocumentsMeta = `
|
sqlListAllDocumentsMeta = `
|
||||||
SELECT id, title, created_at, updated_at
|
SELECT id, title, created_at, updated_at, is_locked
|
||||||
FROM documents
|
FROM documents
|
||||||
WHERE is_deleted = 0
|
WHERE is_deleted = 0
|
||||||
ORDER BY updated_at DESC`
|
ORDER BY updated_at DESC`
|
||||||
|
|
||||||
sqlListDeletedDocumentsMeta = `
|
sqlListDeletedDocumentsMeta = `
|
||||||
SELECT id, title, created_at, updated_at
|
SELECT id, title, created_at, updated_at, is_locked
|
||||||
FROM documents
|
FROM documents
|
||||||
WHERE is_deleted = 1
|
WHERE is_deleted = 1
|
||||||
ORDER BY updated_at DESC`
|
ORDER BY updated_at DESC`
|
||||||
@@ -63,6 +63,16 @@ SELECT id FROM documents WHERE is_deleted = 0 ORDER BY id LIMIT 1`
|
|||||||
|
|
||||||
sqlCountDocuments = `SELECT COUNT(*) FROM documents WHERE is_deleted = 0`
|
sqlCountDocuments = `SELECT COUNT(*) FROM documents WHERE is_deleted = 0`
|
||||||
|
|
||||||
|
sqlSetDocumentLocked = `
|
||||||
|
UPDATE documents
|
||||||
|
SET is_locked = 1, updated_at = ?
|
||||||
|
WHERE id = ?`
|
||||||
|
|
||||||
|
sqlSetDocumentUnlocked = `
|
||||||
|
UPDATE documents
|
||||||
|
SET is_locked = 0, updated_at = ?
|
||||||
|
WHERE id = ?`
|
||||||
|
|
||||||
sqlDefaultDocumentID = 1 // 默认文档的ID
|
sqlDefaultDocumentID = 1 // 默认文档的ID
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -80,16 +90,19 @@ func NewDocumentService(databaseService *DatabaseService, logger *log.Service) *
|
|||||||
logger = log.New()
|
logger = log.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DocumentService{
|
ds := &DocumentService{
|
||||||
databaseService: databaseService,
|
databaseService: databaseService,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ds
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceStartup initializes the service when the application starts
|
// ServiceStartup initializes the service when the application starts
|
||||||
func (ds *DocumentService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
func (ds *DocumentService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
|
||||||
ds.ctx = ctx
|
ds.ctx = ctx
|
||||||
// Ensure default document exists
|
|
||||||
|
// 确保默认文档存在
|
||||||
if err := ds.ensureDefaultDocument(); err != nil {
|
if err := ds.ensureDefaultDocument(); err != nil {
|
||||||
return fmt.Errorf("failed to ensure default document: %w", err)
|
return fmt.Errorf("failed to ensure default document: %w", err)
|
||||||
}
|
}
|
||||||
@@ -170,6 +183,10 @@ func (ds *DocumentService) GetDocumentByID(id int64) (*models.Document, error) {
|
|||||||
doc.IsDeleted = isDeletedInt == 1
|
doc.IsDeleted = isDeletedInt == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||||
|
doc.IsLocked = isLockedInt == 1
|
||||||
|
}
|
||||||
|
|
||||||
return doc, nil
|
return doc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,6 +203,7 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
|||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
IsDeleted: false,
|
IsDeleted: false,
|
||||||
|
IsLocked: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行插入操作
|
// 执行插入操作
|
||||||
@@ -215,6 +233,61 @@ func (ds *DocumentService) CreateDocument(title string) (*models.Document, error
|
|||||||
return doc, nil
|
return doc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LockDocument 锁定文档,防止删除
|
||||||
|
func (ds *DocumentService) LockDocument(id int64) error {
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
// 检查文档是否存在且未删除
|
||||||
|
doc, err := ds.GetDocumentByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get document: %w", err)
|
||||||
|
}
|
||||||
|
if doc == nil {
|
||||||
|
return fmt.Errorf("document not found: %d", id)
|
||||||
|
}
|
||||||
|
if doc.IsDeleted {
|
||||||
|
return fmt.Errorf("cannot lock deleted document: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经锁定,无需操作
|
||||||
|
if doc.IsLocked {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.databaseService.SQLite.Execute(sqlSetDocumentLocked, time.Now(), id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to lock document: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnlockDocument 解锁文档
|
||||||
|
func (ds *DocumentService) UnlockDocument(id int64) error {
|
||||||
|
ds.mu.Lock()
|
||||||
|
defer ds.mu.Unlock()
|
||||||
|
|
||||||
|
// 检查文档是否存在
|
||||||
|
doc, err := ds.GetDocumentByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get document: %w", err)
|
||||||
|
}
|
||||||
|
if doc == nil {
|
||||||
|
return fmt.Errorf("document not found: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果未锁定,无需操作
|
||||||
|
if !doc.IsLocked {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.databaseService.SQLite.Execute(sqlSetDocumentUnlocked, time.Now(), id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unlock document: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateDocumentContent updates the content of a document
|
// UpdateDocumentContent updates the content of a document
|
||||||
func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error {
|
func (ds *DocumentService) UpdateDocumentContent(id int64, content string) error {
|
||||||
ds.mu.Lock()
|
ds.mu.Lock()
|
||||||
@@ -249,7 +322,19 @@ func (ds *DocumentService) DeleteDocument(id int64) error {
|
|||||||
return fmt.Errorf("cannot delete the default document")
|
return fmt.Errorf("cannot delete the default document")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), id)
|
// 检查文档是否锁定
|
||||||
|
doc, err := ds.GetDocumentByID(id)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get document: %w", err)
|
||||||
|
}
|
||||||
|
if doc == nil {
|
||||||
|
return fmt.Errorf("document not found: %d", id)
|
||||||
|
}
|
||||||
|
if doc.IsLocked {
|
||||||
|
return fmt.Errorf("cannot delete locked document: %d", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ds.databaseService.SQLite.Execute(sqlMarkDocumentAsDeleted, time.Now(), id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
return fmt.Errorf("failed to mark document as deleted: %w", err)
|
||||||
}
|
}
|
||||||
@@ -304,6 +389,10 @@ func (ds *DocumentService) ListAllDocumentsMeta() ([]*models.Document, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||||
|
doc.IsLocked = isLockedInt == 1
|
||||||
|
}
|
||||||
|
|
||||||
documents = append(documents, doc)
|
documents = append(documents, doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,6 +435,10 @@ func (ds *DocumentService) ListDeletedDocumentsMeta() ([]*models.Document, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isLockedInt, ok := row["is_locked"].(int64); ok {
|
||||||
|
doc.IsLocked = isLockedInt == 1
|
||||||
|
}
|
||||||
|
|
||||||
documents = append(documents, doc)
|
documents = append(documents, doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user