feat: added dataPath for projects

This commit is contained in:
2026-02-12 15:00:37 +01:00
parent 2b95f3d72c
commit 85d196e598
15 changed files with 263 additions and 49 deletions

View File

@@ -49,14 +49,20 @@ export interface MediaMetadata {
export class MediaEngine extends EventEmitter {
private currentProjectId: string = 'default';
private projectBaseDir: string | null = null;
constructor() {
super();
}
private getMediaBaseDir(): string {
private getProjectBaseDir(): string {
if (this.projectBaseDir) return this.projectBaseDir;
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId, 'media');
return path.join(userDataPath, 'projects', this.currentProjectId);
}
private getMediaBaseDir(): string {
return path.join(this.getProjectBaseDir(), 'media');
}
private getMediaDir(): string {
@@ -85,8 +91,9 @@ export class MediaEngine extends EventEmitter {
return path.join(dir, `${id}${extension}`);
}
setProjectContext(projectId: string): void {
setProjectContext(projectId: string, baseDir?: string): void {
this.currentProjectId = projectId;
this.projectBaseDir = baseDir || null;
}
getProjectContext(): string {
@@ -101,8 +108,7 @@ export class MediaEngine extends EventEmitter {
* Get the thumbnails directory for the current project
*/
private getThumbnailsDir(): string {
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId, 'thumbnails');
return path.join(this.getProjectBaseDir(), 'thumbnails');
}
/**

View File

@@ -12,6 +12,7 @@ import { posts, projects } from '../database/schema';
export interface ProjectMetadata {
name: string;
description?: string;
dataPath?: string; // Custom path for project data
}
/**
@@ -31,6 +32,7 @@ export const DEFAULT_CATEGORIES = ['article', 'picture', 'aside', 'page'];
*/
export class MetaEngine extends EventEmitter {
private currentProjectId: string = 'default';
private projectBaseDir: string | null = null;
private tags: Set<string> = new Set();
private categories: Set<string> = new Set();
private projectMetadata: ProjectMetadata | null = null;
@@ -40,13 +42,18 @@ export class MetaEngine extends EventEmitter {
super();
}
private getProjectBaseDir(): string {
if (this.projectBaseDir) return this.projectBaseDir;
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId);
}
/**
* Get the meta directory path for the current project.
* Format: {userData}/projects/{projectId}/meta/
* Format: {baseDir}/meta/
*/
getMetaDir(): string {
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId, 'meta');
return path.join(this.getProjectBaseDir(), 'meta');
}
private getTagsFilePath(): string {
@@ -61,8 +68,9 @@ export class MetaEngine extends EventEmitter {
return path.join(this.getMetaDir(), 'project.json');
}
setProjectContext(projectId: string): void {
setProjectContext(projectId: string, baseDir?: string): void {
this.currentProjectId = projectId;
this.projectBaseDir = baseDir || null;
// Reset in-memory cache when project changes
this.tags.clear();
this.categories.clear();
@@ -334,14 +342,14 @@ export class MetaEngine extends EventEmitter {
/**
* Fetch the current project's data from the database.
*/
private async fetchProjectFromDatabase(): Promise<{ name: string; description: string | null } | null> {
private async fetchProjectFromDatabase(): Promise<{ name: string; description: string | null; dataPath: string | null } | null> {
const db = getDatabase().getLocal();
const project = await db
.select({ name: projects.name, description: projects.description })
.select({ name: projects.name, description: projects.description, dataPath: projects.dataPath })
.from(projects)
.where(eq(projects.id, this.currentProjectId))
.get();
return project || null;
}
@@ -459,6 +467,18 @@ export class MetaEngine extends EventEmitter {
// Handle project metadata
if (projectMetadataFileExists) {
await this.loadProjectMetadata();
// If project.json has a dataPath, sync it back to the database
if (this.projectMetadata?.dataPath !== undefined) {
const projectData = await this.fetchProjectFromDatabase();
if (projectData && projectData.dataPath !== this.projectMetadata.dataPath) {
const db = getDatabase().getLocal();
await db.update(projects)
.set({ dataPath: this.projectMetadata.dataPath || null })
.where(eq(projects.id, this.currentProjectId));
console.log(`[MetaEngine] Synced dataPath from project.json to database: ${this.projectMetadata.dataPath || '(default)'}`);
}
}
} else {
// No file exists, fetch project data from database and create file
const projectData = await this.fetchProjectFromDatabase();
@@ -468,6 +488,7 @@ export class MetaEngine extends EventEmitter {
this.projectMetadata = {
name: projectData.name,
description: projectData.description || undefined,
dataPath: projectData.dataPath || undefined,
};
await this.saveProjectMetadata();
}

View File

@@ -142,9 +142,16 @@ export class PostEngine extends EventEmitter {
await client.execute({ sql: 'DELETE FROM posts_fts WHERE id = ?', args: [id] });
}
private getPostsBaseDir(): string {
private projectBaseDir: string | null = null;
private getProjectBaseDir(): string {
if (this.projectBaseDir) return this.projectBaseDir;
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId, 'posts');
return path.join(userDataPath, 'projects', this.currentProjectId);
}
private getPostsBaseDir(): string {
return path.join(this.getProjectBaseDir(), 'posts');
}
private getPostsDir(): string {
@@ -172,8 +179,9 @@ export class PostEngine extends EventEmitter {
return path.join(dir, `${slug}.md`);
}
setProjectContext(projectId: string): void {
setProjectContext(projectId: string, baseDir?: string): void {
this.currentProjectId = projectId;
this.projectBaseDir = baseDir || null;
}
getProjectContext(): string {

View File

@@ -12,6 +12,7 @@ export interface ProjectData {
name: string;
slug: string;
description?: string;
dataPath?: string; // Custom path for project data (undefined = default)
createdAt: Date;
updatedAt: Date;
isActive: boolean;
@@ -29,18 +30,41 @@ export class ProjectEngine extends EventEmitter {
.replace(/^-|-$/g, '');
}
private async ensureProjectDirectories(projectId: string): Promise<void> {
/**
* Get the base directory for a project's data.
* If the project has a custom dataPath, use that; otherwise use the default.
*/
getProjectBaseDir(projectId: string, dataPath?: string | null): string {
if (dataPath) {
return dataPath;
}
const userDataPath = app.getPath('userData');
const projectDir = path.join(userDataPath, 'projects', projectId);
return path.join(userDataPath, 'projects', projectId);
}
/**
* Get the default base directory (in userData) for a project.
*/
getDefaultProjectBaseDir(projectId: string): string {
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', projectId);
}
private async ensureProjectDirectories(projectId: string, dataPath?: string | null): Promise<void> {
const projectDir = this.getProjectBaseDir(projectId, dataPath);
const postsDir = path.join(projectDir, 'posts');
const mediaDir = path.join(projectDir, 'media');
const thumbnailsDir = path.join(projectDir, 'thumbnails');
const metaDir = path.join(projectDir, 'meta');
await fs.mkdir(projectDir, { recursive: true });
await fs.mkdir(postsDir, { recursive: true });
await fs.mkdir(mediaDir, { recursive: true });
await fs.mkdir(thumbnailsDir, { recursive: true });
await fs.mkdir(metaDir, { recursive: true });
}
async createProject(data: { name: string; description?: string; slug?: string }): Promise<ProjectData> {
async createProject(data: { name: string; description?: string; slug?: string; dataPath?: string }): Promise<ProjectData> {
const db = getDatabase().getLocal();
const now = new Date();
const id = uuidv4();
@@ -61,13 +85,14 @@ export class ProjectEngine extends EventEmitter {
name: data.name,
slug: finalSlug,
description: data.description,
dataPath: data.dataPath,
createdAt: now,
updatedAt: now,
isActive: false,
};
// Create directories using project ID (not slug)
await this.ensureProjectDirectories(id);
await this.ensureProjectDirectories(id, data.dataPath);
// Insert into database
const dbProject: NewProject = {
@@ -75,6 +100,7 @@ export class ProjectEngine extends EventEmitter {
name: project.name,
slug: project.slug,
description: project.description,
dataPath: project.dataPath,
createdAt: project.createdAt,
updatedAt: project.updatedAt,
isActive: project.isActive,
@@ -106,6 +132,7 @@ export class ProjectEngine extends EventEmitter {
name: updated.name,
slug: updated.slug,
description: updated.description,
dataPath: updated.dataPath,
updatedAt: updated.updatedAt,
isActive: updated.isActive,
})
@@ -116,6 +143,7 @@ export class ProjectEngine extends EventEmitter {
name: updated.name,
slug: updated.slug,
description: updated.description || undefined,
dataPath: updated.dataPath || undefined,
createdAt: updated.createdAt,
updatedAt: updated.updatedAt,
isActive: updated.isActive ?? false,
@@ -196,6 +224,7 @@ export class ProjectEngine extends EventEmitter {
name: dbProject.name,
slug: dbProject.slug,
description: dbProject.description || undefined,
dataPath: dbProject.dataPath || undefined,
createdAt: dbProject.createdAt,
updatedAt: dbProject.updatedAt,
isActive: dbProject.isActive ?? false,
@@ -211,6 +240,7 @@ export class ProjectEngine extends EventEmitter {
name: p.name,
slug: p.slug,
description: p.description || undefined,
dataPath: p.dataPath || undefined,
createdAt: p.createdAt,
updatedAt: p.updatedAt,
isActive: p.isActive ?? false,
@@ -231,6 +261,7 @@ export class ProjectEngine extends EventEmitter {
name: dbProject.name,
slug: dbProject.slug,
description: dbProject.description || undefined,
dataPath: dbProject.dataPath || undefined,
createdAt: dbProject.createdAt,
updatedAt: dbProject.updatedAt,
isActive: dbProject.isActive ?? false,
@@ -255,13 +286,21 @@ export class ProjectEngine extends EventEmitter {
return project;
}
getProjectPaths(projectId: string): { posts: string; media: string } {
const userDataPath = app.getPath('userData');
getProjectPaths(projectId: string, dataPath?: string | null): { posts: string; media: string } {
const baseDir = this.getProjectBaseDir(projectId, dataPath);
return {
posts: path.join(userDataPath, 'projects', projectId, 'posts'),
media: path.join(userDataPath, 'projects', projectId, 'media'),
posts: path.join(baseDir, 'posts'),
media: path.join(baseDir, 'media'),
};
}
/**
* Get project paths by looking up the project's dataPath from the database.
*/
async getProjectPathsResolved(projectId: string): Promise<{ posts: string; media: string }> {
const project = await this.getProject(projectId);
return this.getProjectPaths(projectId, project?.dataPath);
}
}
// Singleton instance

View File

@@ -110,16 +110,24 @@ function isValidHexColor(color: string): boolean {
*/
export class TagEngine extends EventEmitter {
private currentProjectId: string = 'default';
private projectBaseDir: string | null = null;
constructor() {
super();
}
private getProjectBaseDir(): string {
if (this.projectBaseDir) return this.projectBaseDir;
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId);
}
/**
* Set the current project context
*/
setProjectContext(projectId: string): void {
setProjectContext(projectId: string, baseDir?: string): void {
this.currentProjectId = projectId;
this.projectBaseDir = baseDir || null;
}
/**
@@ -133,8 +141,7 @@ export class TagEngine extends EventEmitter {
* Get the tags file path for filesystem persistence
*/
private getTagsFilePath(): string {
const userDataPath = app.getPath('userData');
return path.join(userDataPath, 'projects', this.currentProjectId, 'meta', 'tags-metadata.json');
return path.join(this.getProjectBaseDir(), 'meta', 'tags-metadata.json');
}
/**