Skip to content

Commit 9a4d854

Browse files
author
Marvin Zhang
committed
refactor: update project and devlog entities to use numeric IDs, enhance project manager initialization
1 parent 4428d87 commit 9a4d854

File tree

9 files changed

+153
-106
lines changed

9 files changed

+153
-106
lines changed

packages/ai/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ export * from './automation/index.js';
2323
export {
2424
MessageData as Message,
2525
ChatSessionData as ChatSession,
26-
ProjectDataContainer as ProjectData,
26+
WorkspaceDataContainer as ProjectData,
2727
} from './models/index.js';

packages/core/src/entities/devlog-entry.entity.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
@Index(['priority'])
3434
@Index(['assignee'])
3535
@Index(['key'])
36+
@Index(['projectId'])
3637
export class DevlogEntryEntity {
3738
@PrimaryGeneratedColumn()
3839
id!: number;
@@ -76,6 +77,9 @@ export class DevlogEntryEntity {
7677
@Column({ type: 'varchar', length: 255, nullable: true })
7778
assignee?: string;
7879

80+
@Column({ type: 'int', nullable: true, name: 'project_id' })
81+
projectId?: number;
82+
7983
// Flattened DevlogContext fields (simple strings and arrays)
8084
@Column({ type: 'text', nullable: true, name: 'business_context' })
8185
businessContext?: string;

packages/core/src/entities/project.entity.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
*/
77

88
import 'reflect-metadata';
9-
import { Column, CreateDateColumn, Entity, PrimaryColumn } from 'typeorm';
9+
import { Column, CreateDateColumn, Entity, PrimaryGeneratedColumn } from 'typeorm';
1010
import type { ProjectMetadata, ProjectSettings } from '../types/index.js';
1111
import { JsonColumn, TimestampColumn, getTimestampType } from './decorators.js';
1212

1313
@Entity('devlog_projects')
1414
export class ProjectEntity {
15-
@PrimaryColumn()
16-
id!: string;
15+
@PrimaryGeneratedColumn()
16+
id!: number;
1717

1818
@Column()
1919
name!: string;
@@ -27,9 +27,6 @@ export class ProjectEntity {
2727
@JsonColumn({ nullable: true })
2828
settings?: ProjectSettings;
2929

30-
@JsonColumn({ nullable: true })
31-
tags?: string[];
32-
3330
@CreateDateColumn({
3431
type: getTimestampType(),
3532
name: 'created_at',
@@ -49,7 +46,6 @@ export class ProjectEntity {
4946
description: this.description,
5047
repositoryUrl: this.repositoryUrl,
5148
settings: this.settings || {},
52-
tags: this.tags || [],
5349
createdAt: this.createdAt,
5450
lastAccessedAt: this.lastAccessedAt,
5551
};
@@ -59,15 +55,14 @@ export class ProjectEntity {
5955
* Create entity from ProjectMetadata
6056
*/
6157
static fromProjectData(
62-
project: Omit<ProjectMetadata, 'createdAt' | 'lastAccessedAt'>,
58+
project: Omit<ProjectMetadata, 'id' | 'createdAt' | 'lastAccessedAt'>,
6359
): ProjectEntity {
6460
const entity = new ProjectEntity();
65-
entity.id = project.id;
61+
// id will be auto-generated by the database
6662
entity.name = project.name;
6763
entity.description = project.description;
6864
entity.repositoryUrl = project.repositoryUrl;
6965
entity.settings = project.settings;
70-
entity.tags = project.tags;
7166
entity.lastAccessedAt = new Date();
7267
return entity;
7368
}
@@ -80,7 +75,6 @@ export class ProjectEntity {
8075
if (updates.description !== undefined) this.description = updates.description;
8176
if (updates.repositoryUrl !== undefined) this.repositoryUrl = updates.repositoryUrl;
8277
if (updates.settings !== undefined) this.settings = updates.settings;
83-
if (updates.tags !== undefined) this.tags = updates.tags;
8478
this.lastAccessedAt = new Date();
8579
}
8680
}

packages/core/src/managers/devlog/project-devlog-manager.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,19 +33,34 @@ export interface ProjectDevlogManagerOptions {
3333
export class ProjectDevlogManager {
3434
private storageProvider: StorageProvider | null = null;
3535
private initialized = false;
36+
private initPromise: Promise<void> | null = null;
3637

3738
constructor(private options: ProjectDevlogManagerOptions) {}
3839

3940
/**
4041
* Initialize the devlog manager
4142
*/
4243
async initialize(): Promise<void> {
43-
if (this.initialized) return;
44+
if (this.initialized) {
45+
console.log('🔄 ProjectDevlogManager already initialized, skipping...');
46+
return;
47+
}
48+
49+
console.log('🚀 Initializing ProjectDevlogManager...');
50+
51+
if (this.options.projectContext) {
52+
console.log(`📂 Project context: ${this.options.projectContext.projectId}`);
53+
} else {
54+
console.log('📂 No project context configured');
55+
}
56+
57+
console.log(`💾 Storage type: ${this.options.storageConfig.type}`);
4458

4559
this.storageProvider = await StorageProviderFactory.create(this.options.storageConfig);
4660
await this.storageProvider.initialize();
4761

4862
this.initialized = true;
63+
console.log('✅ ProjectDevlogManager initialized successfully');
4964
}
5065

5166
/**

packages/core/src/managers/project/database-project-manager.ts

Lines changed: 51 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export interface DatabaseProjectManagerOptions {
2828
*/
2929
export class DatabaseProjectManager implements ProjectManager {
3030
private repository: Repository<ProjectEntity>;
31-
private currentProjectId: string | null = null;
31+
private currentProjectId: number | null = null;
3232
private initialized = false;
3333

3434
constructor(private options: DatabaseProjectManagerOptions) {
@@ -71,21 +71,32 @@ export class DatabaseProjectManager implements ProjectManager {
7171
* Create default project
7272
*/
7373
private async createDefaultProject(): Promise<void> {
74-
const defaultProject: Omit<ProjectMetadata, 'createdAt' | 'lastAccessedAt'> = {
75-
id: 'default',
74+
const defaultProject: Omit<ProjectMetadata, 'id' | 'createdAt' | 'lastAccessedAt'> = {
7675
name: 'Default Project',
7776
description: 'Default devlog project',
7877
settings: {
7978
defaultPriority: 'medium',
8079
},
81-
tags: [],
8280
...this.options.defaultProjectConfig,
8381
};
8482

85-
// Force the ID to be 'default' even if overridden
86-
defaultProject.id = 'default';
83+
// Create project directly without initialization check since this is called during initialization
84+
const existing = await this.repository.findOne({ where: { name: defaultProject.name } });
85+
if (existing) {
86+
return; // Default project already exists
87+
}
88+
89+
// Check project limits
90+
if (this.options.maxProjects) {
91+
const projectCount = await this.repository.count();
92+
if (projectCount >= this.options.maxProjects) {
93+
throw new Error(`Maximum number of projects (${this.options.maxProjects}) reached`);
94+
}
95+
}
8796

88-
await this.createProject(defaultProject);
97+
// Create and save new project entity
98+
const entity = ProjectEntity.fromProjectData(defaultProject);
99+
await this.repository.save(entity);
89100
}
90101

91102
private ensureInitialized(): void {
@@ -102,7 +113,7 @@ export class DatabaseProjectManager implements ProjectManager {
102113
return entities.map((entity) => entity.toProjectMetadata());
103114
}
104115

105-
async getProject(id: string): Promise<ProjectMetadata | null> {
116+
async getProject(id: number): Promise<ProjectMetadata | null> {
106117
this.ensureInitialized();
107118
const entity = await this.repository.findOne({ where: { id } });
108119

@@ -118,16 +129,10 @@ export class DatabaseProjectManager implements ProjectManager {
118129
}
119130

120131
async createProject(
121-
project: Omit<ProjectMetadata, 'createdAt' | 'lastAccessedAt'>,
132+
project: Omit<ProjectMetadata, 'id' | 'createdAt' | 'lastAccessedAt'>,
122133
): Promise<ProjectMetadata> {
123134
this.ensureInitialized();
124135

125-
// Check if project already exists
126-
const existing = await this.repository.findOne({ where: { id: project.id } });
127-
if (existing) {
128-
throw new Error(`Project '${project.id}' already exists`);
129-
}
130-
131136
// Check project limits
132137
if (this.options.maxProjects) {
133138
const projectCount = await this.repository.count();
@@ -143,12 +148,12 @@ export class DatabaseProjectManager implements ProjectManager {
143148
return savedEntity.toProjectMetadata();
144149
}
145150

146-
async updateProject(id: string, updates: Partial<ProjectMetadata>): Promise<ProjectMetadata> {
151+
async updateProject(id: number, updates: Partial<ProjectMetadata>): Promise<ProjectMetadata> {
147152
this.ensureInitialized();
148153

149154
const entity = await this.repository.findOne({ where: { id } });
150155
if (!entity) {
151-
throw new Error(`Project '${id}' not found`);
156+
throw new Error(`Project with ID '${id}' not found`);
152157
}
153158

154159
// Prevent changing project ID
@@ -163,54 +168,54 @@ export class DatabaseProjectManager implements ProjectManager {
163168
return savedEntity.toProjectMetadata();
164169
}
165170

166-
async deleteProject(id: string): Promise<void> {
171+
async deleteProject(id: number): Promise<void> {
167172
this.ensureInitialized();
168173

169-
// Prevent deleting the default project
170-
if (id === 'default') {
171-
throw new Error('Cannot delete the default project');
172-
}
173-
174174
const result = await this.repository.delete({ id });
175175
if (result.affected === 0) {
176-
throw new Error(`Project '${id}' not found`);
176+
throw new Error(`Project with ID '${id}' not found`);
177177
}
178178

179-
// If this was the current project, reset to default
179+
// If this was the current project, reset to null
180180
if (this.currentProjectId === id) {
181181
this.currentProjectId = null;
182182
}
183183
}
184184

185-
async getDefaultProject(): Promise<string> {
185+
async getDefaultProject(): Promise<number> {
186186
this.ensureInitialized();
187-
// For now, we'll use a simple default project approach
188-
// In the future, this could be stored in a settings table
189-
return 'default';
187+
// Get the first project (lowest ID) as the default
188+
const defaultProject = await this.repository.findOne({
189+
order: { id: 'ASC' },
190+
});
191+
192+
if (!defaultProject) {
193+
throw new Error('No projects found');
194+
}
195+
196+
return defaultProject.id;
190197
}
191198

192-
async setDefaultProject(id: string): Promise<void> {
199+
async setDefaultProject(id: number): Promise<void> {
193200
this.ensureInitialized();
194201

195202
// Verify project exists
196203
const project = await this.repository.findOne({ where: { id } });
197204
if (!project) {
198-
throw new Error(`Project '${id}' not found`);
205+
throw new Error(`Project with ID '${id}' not found`);
199206
}
200207

201-
// For now, we'll keep the default as 'default'
208+
// For now, we'll consider the first project (lowest ID) as default
202209
// In the future, this could be stored in a settings table
203-
if (id !== 'default') {
204-
throw new Error('Setting custom default project not yet supported in database mode');
205-
}
210+
// This is a no-op for now since we determine default by ID ordering
206211
}
207212

208-
async switchToProject(id: string): Promise<ProjectContext> {
213+
async switchToProject(id: number): Promise<ProjectContext> {
209214
this.ensureInitialized();
210215

211216
const entity = await this.repository.findOne({ where: { id } });
212217
if (!entity) {
213-
throw new Error(`Project '${id}' not found`);
218+
throw new Error(`Project with ID '${id}' not found`);
214219
}
215220

216221
// Update last accessed time
@@ -221,10 +226,11 @@ export class DatabaseProjectManager implements ProjectManager {
221226
this.currentProjectId = id;
222227

223228
const project = entity.toProjectMetadata();
229+
const defaultProjectId = await this.getDefaultProject();
224230
return {
225231
projectId: id,
226232
project,
227-
isDefault: id === 'default',
233+
isDefault: id === defaultProjectId,
228234
};
229235
}
230236

@@ -235,7 +241,11 @@ export class DatabaseProjectManager implements ProjectManager {
235241

236242
// Fall back to default project if no current project set
237243
if (!projectId) {
238-
projectId = await this.getDefaultProject();
244+
try {
245+
projectId = await this.getDefaultProject();
246+
} catch {
247+
return null; // No projects exist
248+
}
239249
}
240250

241251
const entity = await this.repository.findOne({ where: { id: projectId } });
@@ -244,10 +254,11 @@ export class DatabaseProjectManager implements ProjectManager {
244254
}
245255

246256
const project = entity.toProjectMetadata();
257+
const defaultProjectId = await this.getDefaultProject();
247258
return {
248259
projectId,
249260
project,
250-
isDefault: projectId === 'default',
261+
isDefault: projectId === defaultProjectId,
251262
};
252263
}
253264
}

packages/core/src/types/core.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ export interface DevlogEntry {
180180
closedAt?: string; // ISO timestamp when status changed to 'done' or 'cancelled'
181181
assignee?: string;
182182
archived?: boolean; // For long-term management and performance
183-
projectId?: string; // Project context for multi-project isolation
183+
projectId?: number; // Project context for multi-project isolation
184184

185185
// Flattened context fields
186186
acceptanceCriteria?: string[];
@@ -210,7 +210,7 @@ export interface DevlogFilter {
210210
toDate?: string;
211211
search?: string;
212212
archived?: boolean; // Filter for archived status
213-
projectId?: string; // Filter by project context
213+
projectId?: number; // Filter by project context
214214
// Pagination options
215215
pagination?: PaginationOptions;
216216
}

0 commit comments

Comments
 (0)