Skip to content

Commit 9d3e7ee

Browse files
authored
Merge pull request #169 from import-ai/fix/upsert_index
refactor(resources): fix update resource api
2 parents bbbe0d2 + 2a9c1c6 commit 9d3e7ee

File tree

9 files changed

+185
-45
lines changed

9 files changed

+185
-45
lines changed

src/namespace-resources/dto/update-resource.dto.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
IsOptional,
77
IsNotEmpty,
88
} from 'class-validator';
9+
import { UpdateResourceReqDto } from 'omniboxd/resources/dto/update-resource-req.dto';
910
import { ResourceType } from 'omniboxd/resources/entities/resource.entity';
1011

1112
export class UpdateResourceDto {
@@ -38,4 +39,14 @@ export class UpdateResourceDto {
3839
@IsObject()
3940
@IsOptional()
4041
attrs?: Record<string, any>;
42+
43+
toUpdateReq(): UpdateResourceReqDto {
44+
const updateReq = new UpdateResourceReqDto();
45+
updateReq.name = this.name;
46+
updateReq.parentId = this.parentId;
47+
updateReq.tagIds = this.tag_ids ? this.tag_ids : undefined;
48+
updateReq.content = this.content;
49+
updateReq.attrs = this.attrs;
50+
return updateReq;
51+
}
4152
}

src/namespace-resources/namespace-resources.service.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export class NamespaceResourcesService {
4949
constructor(
5050
@InjectRepository(Resource)
5151
private readonly resourceRepository: Repository<Resource>,
52-
@InjectRepository(Task)
53-
private readonly taskRepository: Repository<Task>,
5452
@InjectRepository(Namespace)
5553
private readonly namespaceRepository: Repository<Namespace>,
5654
private readonly tagService: TagService,
@@ -586,28 +584,11 @@ export class NamespaceResourcesService {
586584
}
587585

588586
async update(userId: string, id: string, data: UpdateResourceDto) {
589-
const resource = await this.resourceRepository.findOne({
590-
where: { id, namespaceId: data.namespaceId },
591-
});
592-
593-
if (!resource) {
594-
throw new NotFoundException('Resource not found.');
595-
}
596-
597-
// Use provided tag_ids directly
598-
const tagIds = data.tag_ids || resource.tagIds || [];
599-
600-
const newResource = this.resourceRepository.create({
601-
...resource,
602-
...data,
603-
namespaceId: data.namespaceId,
604-
tagIds: tagIds.length > 0 ? tagIds : [],
605-
});
606-
const savedNewResource = await this.resourceRepository.save(newResource);
607-
await this.wizardTaskService.createIndexTask(
608-
TASK_PRIORITY,
587+
await this.resourcesService.updateResource(
588+
data.namespaceId,
589+
id,
609590
userId,
610-
savedNewResource,
591+
data.toUpdateReq(),
611592
);
612593
}
613594

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export class UpdateResourceReqDto {
2+
name?: string;
3+
parentId?: string;
4+
tagIds?: string[];
5+
content?: string;
6+
attrs?: Record<string, any>;
7+
}

src/resources/resources.module.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
22
import { TypeOrmModule } from '@nestjs/typeorm';
33
import { Resource } from './entities/resource.entity';
44
import { ResourcesService } from './resources.service';
5+
import { TasksModule } from 'omniboxd/tasks/tasks.module';
56

67
@Module({
7-
imports: [TypeOrmModule.forFeature([Resource])],
8+
imports: [TypeOrmModule.forFeature([Resource]), TasksModule],
89
providers: [ResourcesService],
910
exports: [ResourcesService],
1011
})

src/resources/resources.service.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,17 @@ import { InjectRepository } from '@nestjs/typeorm';
33
import { Resource } from './entities/resource.entity';
44
import { Repository } from 'typeorm';
55
import { ResourceMetaDto } from './dto/resource-meta.dto';
6+
import { UpdateResourceReqDto } from './dto/update-resource-req.dto';
7+
import { WizardTaskService } from 'omniboxd/tasks/wizard-task.service';
8+
9+
const TASK_PRIORITY = 5;
610

711
@Injectable()
812
export class ResourcesService {
913
constructor(
1014
@InjectRepository(Resource)
1115
private readonly resourceRepository: Repository<Resource>,
16+
private readonly wizardTaskService: WizardTaskService,
1217
) {}
1318

1419
async getParentResources(
@@ -66,4 +71,36 @@ export class ResourcesService {
6671
});
6772
return children.map((r) => ResourceMetaDto.fromEntity(r));
6873
}
74+
75+
async updateResource(
76+
namespaceId: string,
77+
resourceId: string,
78+
userId: string,
79+
updateReq: UpdateResourceReqDto,
80+
) {
81+
await this.resourceRepository.update(
82+
{ namespaceId, id: resourceId },
83+
{
84+
name: updateReq.name,
85+
parentId: updateReq.parentId,
86+
tagIds: updateReq.tagIds,
87+
content: updateReq.content,
88+
attrs: updateReq.attrs,
89+
},
90+
);
91+
const resource = await this.resourceRepository.findOne({
92+
where: {
93+
namespaceId,
94+
id: resourceId,
95+
},
96+
});
97+
if (!resource) {
98+
throw new NotFoundException('Resource not found.');
99+
}
100+
await this.wizardTaskService.createIndexTask(
101+
TASK_PRIORITY,
102+
userId,
103+
resource,
104+
);
105+
}
69106
}

src/tasks/task-pipeline.e2e-spec.ts

Lines changed: 86 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ import { HttpStatus } from '@nestjs/common';
33
import { Task } from 'omniboxd/tasks/tasks.entity';
44
import { TaskCallbackDto } from 'omniboxd/wizard/dto/task-callback.dto';
55
import { isEmpty } from 'omniboxd/utils/is-empty';
6-
import { TaskDto, TaskMetaDto } from 'omniboxd/tasks/dto/task.dto';
6+
import {
7+
InternalTaskDto,
8+
TaskDto,
9+
TaskMetaDto,
10+
} from 'omniboxd/tasks/dto/task.dto';
711

812
/**
913
* Mock wizard worker that simulates the wizard worker service behavior
@@ -198,7 +202,10 @@ class MockWizardWorker {
198202
* Simulates upsert_index task processing
199203
*/
200204
private processUpsertIndexTask(task: TaskDto): { output: any } {
201-
console.log({ taskId: task.id, function: 'upsertIndex' });
205+
expect(task.input).toHaveProperty('meta_info');
206+
expect(task.input.meta_info).toHaveProperty('resource_id');
207+
expect(task.input.meta_info).toHaveProperty('user_id');
208+
expect(task.input.meta_info).toHaveProperty('parent_id');
202209
return {
203210
output: {
204211
indexed: true,
@@ -290,6 +297,70 @@ describe('Task Pipeline (e2e)', () => {
290297
});
291298

292299
describe('Basic Task Processing', () => {
300+
it('create resource trigger upsertIndex', async () => {
301+
mockWorker.startPolling();
302+
303+
const createResponse = (
304+
await client
305+
.post(`/api/v1/namespaces/${client.namespace.id}/resources`)
306+
.send({
307+
name: 'Test Document',
308+
content: 'Sample content for the test document.',
309+
parentId: client.namespace.root_resource_id,
310+
namespaceId: client.namespace.id,
311+
resourceType: 'doc',
312+
})
313+
).body;
314+
315+
const taskMetas: TaskMetaDto[] = (
316+
await client.get(`/api/v1/namespaces/${client.namespace.id}/tasks`)
317+
).body;
318+
319+
const upsertTaskMeta = taskMetas.find(
320+
(t: TaskMetaDto) =>
321+
t.function === 'upsert_index' &&
322+
t.attrs?.resource_id === createResponse.id,
323+
);
324+
325+
expect(upsertTaskMeta).toBeDefined();
326+
const upsertTask: InternalTaskDto = (
327+
await client.get(
328+
`/api/v1/namespaces/${client.namespace.id}/tasks/${upsertTaskMeta?.id}`,
329+
)
330+
).body;
331+
expect(upsertTask).toBeDefined();
332+
expect(upsertTask.input.meta_info.parent_id).toBeDefined();
333+
expect(upsertTask.input.meta_info.parent_id).not.toBeNull();
334+
335+
const patchResponse = await client
336+
.patch(
337+
`/api/v1/namespaces/${client.namespace.id}/resources/${createResponse.id}`,
338+
)
339+
.send({
340+
namespaceId: client.namespace.id,
341+
content: 'Updated content for the test document.',
342+
});
343+
expect(patchResponse.status).toBe(200);
344+
const patchTaskMetas: TaskMetaDto[] = (
345+
await client.get(`/api/v1/namespaces/${client.namespace.id}/tasks`)
346+
).body;
347+
const patchUpsertTaskMeta = patchTaskMetas.find(
348+
(t: TaskMetaDto) =>
349+
t.function === 'upsert_index' &&
350+
t.attrs?.resource_id === createResponse.id &&
351+
t.id !== upsertTaskMeta?.id,
352+
);
353+
expect(patchUpsertTaskMeta).toBeDefined();
354+
const patchUpsertTask: InternalTaskDto = (
355+
await client.get(
356+
`/api/v1/namespaces/${client.namespace.id}/tasks/${patchUpsertTaskMeta?.id}`,
357+
)
358+
).body;
359+
expect(patchUpsertTask).toBeDefined();
360+
expect(patchUpsertTask.input.meta_info.parent_id).toBeDefined();
361+
expect(patchUpsertTask.input.meta_info.parent_id).not.toBeNull();
362+
});
363+
293364
it('should process a collect task end-to-end', async () => {
294365
// Start the mock worker
295366
mockWorker.startPolling();
@@ -343,6 +414,19 @@ describe('Task Pipeline (e2e)', () => {
343414
expect(resourceResponse.status).toBe(200);
344415
expect(resourceResponse.body.id).toBe(resourceId);
345416
expect(resourceResponse.body.content).toContain('Test Page Title');
417+
418+
const taskMetas: TaskMetaDto[] = (
419+
await client.get(`/api/v1/namespaces/${client.namespace.id}/tasks`)
420+
).body;
421+
const upsertTaskMeta = taskMetas.find(
422+
(t: TaskMetaDto) => t.function === 'upsert_index',
423+
);
424+
const upsertTask: InternalTaskDto = (
425+
await client.get(
426+
`/api/v1/namespaces/${client.namespace.id}/tasks/${upsertTaskMeta?.id}`,
427+
)
428+
).body;
429+
expect(upsertTask).toBeDefined();
346430
});
347431

348432
it('should handle task exceptions properly', async () => {

src/wizard/processors/collect.processor.ts

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Processor } from 'omniboxd/wizard/processors/processor.abstract';
55
import { isEmpty } from 'omniboxd/utils/is-empty';
66
import { TagService } from 'omniboxd/tag/tag.service';
77
import { ProcessedImage } from 'omniboxd/wizard/types/wizard.types';
8+
import { UpdateResourceDto } from 'omniboxd/namespace-resources/dto/update-resource.dto';
89

910
export class CollectProcessor extends Processor {
1011
constructor(
@@ -20,10 +21,14 @@ export class CollectProcessor extends Processor {
2021
throw new BadRequestException('Invalid task payload');
2122
}
2223
if (task.exception && !isEmpty(task.exception)) {
23-
await this.namespaceResourcesService.update(task.userId, resourceId, {
24-
namespaceId: task.namespaceId,
25-
content: task.exception.error,
26-
});
24+
await this.namespaceResourcesService.update(
25+
task.userId,
26+
resourceId,
27+
Object.assign(new UpdateResourceDto(), {
28+
namespaceId: task.namespaceId,
29+
content: task.exception.error,
30+
}),
31+
);
2732
return {};
2833
} else if (task.output) {
2934
const { markdown, title, ...attrs } = task.output || {};
@@ -51,13 +56,17 @@ export class CollectProcessor extends Processor {
5156

5257
const resource = await this.namespaceResourcesService.get(resourceId);
5358
const mergedAttrs = { ...(resource?.attrs || {}), ...attrs };
54-
await this.namespaceResourcesService.update(task.userId, resourceId, {
55-
namespaceId: task.namespaceId,
56-
name: title,
57-
content: processedMarkdown,
58-
attrs: mergedAttrs,
59-
tag_ids: tagIds,
60-
});
59+
await this.namespaceResourcesService.update(
60+
task.userId,
61+
resourceId,
62+
Object.assign(new UpdateResourceDto(), {
63+
namespaceId: task.namespaceId,
64+
name: title,
65+
content: processedMarkdown,
66+
attrs: mergedAttrs,
67+
tag_ids: tagIds,
68+
}),
69+
);
6170
return { resourceId, tagIds };
6271
}
6372
return {};

src/wizard/processors/extract-tags.processor.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { TagService } from 'omniboxd/tag/tag.service';
55
import { BadRequestException } from '@nestjs/common';
66
import { isEmpty } from 'omniboxd/utils/is-empty';
77
import { ExtractTagsOutputDto } from 'omniboxd/wizard/processors/dto/extract-tags.output.dto';
8+
import { UpdateResourceDto } from 'omniboxd/namespace-resources/dto/update-resource.dto';
89

910
export class ExtractTagsProcessor extends Processor {
1011
constructor(
@@ -38,10 +39,14 @@ export class ExtractTagsProcessor extends Processor {
3839
);
3940

4041
// Update the resource with extracted tag IDs from external service
41-
await this.namespaceResourcesService.update(task.userId, resourceId, {
42-
namespaceId: task.namespaceId,
43-
tag_ids: tagIds,
44-
});
42+
await this.namespaceResourcesService.update(
43+
task.userId,
44+
resourceId,
45+
Object.assign(new UpdateResourceDto(), {
46+
namespaceId: task.namespaceId,
47+
tag_ids: tagIds,
48+
}),
49+
);
4550

4651
return { resourceId, tags: tagNames, tagIds };
4752
}

src/wizard/processors/generate-title.processor.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Processor } from 'omniboxd/wizard/processors/processor.abstract';
33
import { NamespaceResourcesService } from 'omniboxd/namespace-resources/namespace-resources.service';
44
import { BadRequestException } from '@nestjs/common';
55
import { isEmpty } from 'omniboxd/utils/is-empty';
6+
import { UpdateResourceDto } from 'omniboxd/namespace-resources/dto/update-resource.dto';
67

78
export class GenerateTitleProcessor extends Processor {
89
constructor(
@@ -28,10 +29,14 @@ export class GenerateTitleProcessor extends Processor {
2829

2930
if (typeof generatedTitle === 'string' && generatedTitle.trim()) {
3031
// Update the resource with generated title
31-
await this.namespaceResourcesService.update(task.userId, resourceId, {
32-
namespaceId: task.namespaceId,
33-
name: generatedTitle.trim(),
34-
});
32+
await this.namespaceResourcesService.update(
33+
task.userId,
34+
resourceId,
35+
Object.assign(new UpdateResourceDto(), {
36+
namespaceId: task.namespaceId,
37+
name: generatedTitle.trim(),
38+
}),
39+
);
3540

3641
return { resourceId, title: generatedTitle.trim() };
3742
}

0 commit comments

Comments
 (0)