Skip to content

Commit efe56d0

Browse files
authored
Merge pull request #620 from dzcode-io/index-models-during-cron-job
Indexing models during cron job
2 parents 2aed7e7 + 63e1ec7 commit efe56d0

File tree

6 files changed

+129
-52
lines changed

6 files changed

+129
-52
lines changed

api/src/app/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ useContainer(Container); // eslint-disable-line react-hooks/rules-of-hooks
4141

4242
// Initialize Search Service
4343
const searchService = Container.get(SearchService);
44-
await searchService.ensureIndexes();
44+
await searchService.setupSearch();
4545

4646
// Add crons to DI container
4747
const CronServices = [DigestCron];

api/src/contributor/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const contributorsTable = pgTable("contributors", {
88
recordImportedAt: text("record_imported_at")
99
.notNull()
1010
.default(sql`CURRENT_TIMESTAMP`),
11-
runId: text("run_id").notNull().default("initial-run-id"),
11+
runId: text("run_id").notNull(),
1212
name: text("name").notNull(),
1313
username: text("username").notNull(),
1414
url: text("url").notNull().unique(),

api/src/digest/cron.ts

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
import { captureException, cron } from "@sentry/node";
2-
import { CronJob } from "cron";
2+
33
import { ContributionRepository } from "src/contribution/repository";
4+
import { ContributionRow } from "src/contribution/table";
45
import { ContributorRepository } from "src/contributor/repository";
6+
import { ContributorRow } from "src/contributor/table";
7+
import { CronJob } from "cron";
58
import { DataService } from "src/data/service";
69
import { GithubService } from "src/github/service";
710
import { LoggerService } from "src/logger/service";
811
import { ProjectRepository } from "src/project/repository";
12+
import { ProjectRow } from "src/project/table";
913
import { RepositoryRepository } from "src/repository/repository";
14+
import { SearchService } from "src/search/service";
1015
import { Service } from "typedi";
1116

1217
@Service()
@@ -22,6 +27,7 @@ export class DigestCron {
2227
private readonly repositoriesRepository: RepositoryRepository,
2328
private readonly contributionsRepository: ContributionRepository,
2429
private readonly contributorsRepository: ContributorRepository,
30+
private readonly searchService: SearchService,
2531
) {
2632
const SentryCronJob = cron.instrumentCron(CronJob, "DigestCron");
2733
new SentryCronJob(
@@ -67,11 +73,14 @@ export class DigestCron {
6773
const projectsFromDataFolder = await this.dataService.listProjects();
6874

6975
for (const project of projectsFromDataFolder) {
70-
const [{ id: projectId }] = await this.projectsRepository.upsert({
71-
...project,
76+
const projectEntity: ProjectRow = {
7277
runId,
73-
id: project.slug,
74-
});
78+
id: project.slug.replace(/[.]/g, "-"), // NOTE-OB: MeiliSearch doesn't allow dots in ids
79+
name: project.name,
80+
};
81+
const [{ id: projectId }] =
82+
await this.projectsRepository.upsert(projectEntity);
83+
await this.searchService.upsert("project", projectEntity);
7584

7685
let addedRepositoryCount = 0;
7786
try {
@@ -84,15 +93,16 @@ export class DigestCron {
8493
});
8594

8695
const provider = "github";
87-
const [{ id: repositoryId }] = await this.repositoriesRepository.upsert({
88-
provider,
89-
name: repoInfo.name,
90-
owner: repoInfo.owner.login,
91-
runId,
92-
projectId,
93-
stars: repoInfo.stargazers_count,
94-
id: `${provider}-${repoInfo.id}`,
95-
});
96+
const [{ id: repositoryId }] =
97+
await this.repositoriesRepository.upsert({
98+
provider,
99+
name: repoInfo.name,
100+
owner: repoInfo.owner.login,
101+
runId,
102+
projectId,
103+
stars: repoInfo.stargazers_count,
104+
id: `${provider}-${repoInfo.id}`,
105+
});
96106
addedRepositoryCount++;
97107

98108
const issues = await this.githubService.listRepositoryIssues({
@@ -101,18 +111,24 @@ export class DigestCron {
101111
});
102112

103113
for (const issue of issues) {
104-
const githubUser = await this.githubService.getUser({ username: issue.user.login });
114+
const githubUser = await this.githubService.getUser({
115+
username: issue.user.login,
116+
});
105117

106118
if (githubUser.type !== "User") continue;
107119

108-
const [{ id: contributorId }] = await this.contributorsRepository.upsert({
120+
const contributorEntity: ContributorRow = {
109121
name: githubUser.name || githubUser.login,
110122
username: githubUser.login,
111123
url: githubUser.html_url,
112124
avatarUrl: githubUser.avatar_url,
113125
runId,
114126
id: `${provider}-${githubUser.login}`,
115-
});
127+
};
128+
129+
const [{ id: contributorId }] =
130+
await this.contributorsRepository.upsert(contributorEntity);
131+
await this.searchService.upsert("contributor", contributorEntity);
116132

117133
await this.contributorsRepository.upsertRelationWithRepository({
118134
contributorId,
@@ -122,23 +138,32 @@ export class DigestCron {
122138
});
123139

124140
const type = issue.pull_request ? "PULL_REQUEST" : "ISSUE";
125-
await this.contributionsRepository.upsert({
141+
const contributionEntity: ContributionRow = {
126142
title: issue.title,
127143
type,
128144
updatedAt: issue.updated_at,
129145
activityCount: issue.comments,
130146
runId,
131-
url: type === "PULL_REQUEST" ? issue.pull_request.html_url : issue.html_url,
147+
url:
148+
type === "PULL_REQUEST"
149+
? issue.pull_request.html_url
150+
: issue.html_url,
132151
repositoryId,
133152
contributorId,
134153
id: `${provider}-${issue.id}`,
135-
});
154+
};
155+
await this.contributionsRepository.upsert(contributionEntity);
156+
await this.searchService.upsert(
157+
"contribution",
158+
contributionEntity,
159+
);
136160
}
137161

138-
const repoContributors = await this.githubService.listRepositoryContributors({
139-
owner: repository.owner,
140-
repository: repository.name,
141-
});
162+
const repoContributors =
163+
await this.githubService.listRepositoryContributors({
164+
owner: repository.owner,
165+
repository: repository.name,
166+
});
142167

143168
const repoContributorsFiltered = repoContributors.filter(
144169
(contributor) => contributor.type === "User",
@@ -148,14 +173,17 @@ export class DigestCron {
148173
const contributor = await this.githubService.getUser({
149174
username: repoContributor.login,
150175
});
151-
const [{ id: contributorId }] = await this.contributorsRepository.upsert({
176+
const contributorEntity: ContributorRow = {
152177
name: contributor.name || contributor.login,
153178
username: contributor.login,
154179
url: contributor.html_url,
155180
avatarUrl: contributor.avatar_url,
156181
runId,
157182
id: `${provider}-${contributor.login}`,
158-
});
183+
};
184+
const [{ id: contributorId }] =
185+
await this.contributorsRepository.upsert(contributorEntity);
186+
await this.searchService.upsert("contributor", contributorEntity);
159187

160188
await this.contributorsRepository.upsertRelationWithRepository({
161189
contributorId,
@@ -179,11 +207,18 @@ export class DigestCron {
179207
}
180208

181209
try {
182-
await this.contributorsRepository.deleteAllRelationWithRepositoryButWithRunId(runId);
210+
await this.contributorsRepository.deleteAllRelationWithRepositoryButWithRunId(
211+
runId,
212+
);
183213
await this.contributionsRepository.deleteAllButWithRunId(runId);
184214
await this.contributorsRepository.deleteAllButWithRunId(runId);
185215
await this.repositoriesRepository.deleteAllButWithRunId(runId);
186216
await this.projectsRepository.deleteAllButWithRunId(runId);
217+
await Promise.all([
218+
this.searchService.deleteAllButWithRunId("project", runId),
219+
this.searchService.deleteAllButWithRunId("contribution", runId),
220+
this.searchService.deleteAllButWithRunId("contributor", runId),
221+
]);
187222
} catch (error) {
188223
captureException(error, { tags: { type: "CRON" } });
189224
}

api/src/project/table.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const projectsTable = pgTable("projects", {
88
.notNull()
99
.default(sql`CURRENT_TIMESTAMP`),
1010
name: text("name").notNull(),
11-
runId: text("run_id").notNull().default("initial-run-id"),
11+
runId: text("run_id").notNull(),
1212
});
1313

1414
projectsTable.$inferSelect satisfies ProjectEntity;

api/src/search/service.ts

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { SearchItem, SearchType } from "./types";
1+
import { SearchResults, SearchType } from "./types";
22

3+
import { BaseEntity } from "@dzcode.io/models/dist/_base";
34
import { ConfigService } from "src/config/service";
45
import { LoggerService } from "src/logger/service";
56
import { MeiliSearch } from "meilisearch";
@@ -25,32 +26,71 @@ export class SearchService {
2526
});
2627
}
2728

28-
public search = async (query: string): Promise<SearchItem[]> => {
29+
public search = async (query: string): Promise<SearchResults> => {
2930
this.logger.info({ message: `Searching for ${query}` });
30-
return [];
31+
return {
32+
results: [],
33+
};
3134
};
3235

33-
public index = async (
36+
public upsert = async <T extends BaseEntity>(
3437
index: SearchType,
35-
data: SearchItem[],
38+
data: T,
3639
): Promise<void> => {
3740
this.logger.info({
38-
message: `Indexing ${data.length} items in ${index}`,
41+
message: `Upserting "${data.id}" item to ${index}`,
42+
});
43+
await this.meilisearch.index(index).updateDocuments([data]);
44+
this.logger.info({ message: `Upserted "${data.id}" item to ${index}` });
45+
};
46+
47+
public deleteAllButWithRunId = async (
48+
index: SearchType,
49+
runId: string,
50+
): Promise<void> => {
51+
this.logger.info({
52+
message: `Deleting all ${index} but with runId ${runId}`,
53+
});
54+
await this.meilisearch.index(index).deleteDocuments({
55+
filter: `NOT runId=${runId}`,
3956
});
40-
await this.meilisearch.index(index).addDocuments(data);
4157
this.logger.info({
42-
message: `Indexed ${data.length} items in ${index}`,
58+
message: `Deleted all ${index} but with runId ${runId}`,
4359
});
4460
};
4561

46-
public ensureIndexes = async (): Promise<void> => {
47-
await this.meilisearch.createIndex("project");
48-
this.logger.info({ message: "project index created" });
49-
50-
await this.meilisearch.createIndex("contribution");
51-
this.logger.info({ message: "contribution index created" });
62+
public setupSearch = async (): Promise<void> => {
63+
await this.setupIndexes();
64+
await this.updateFilterableAttributes();
65+
};
5266

53-
await this.meilisearch.createIndex("contributor");
54-
this.logger.info({ message: "contributor index created" });
67+
private setupIndexes = async (): Promise<void> => {
68+
await this.upsertIndex("project");
69+
await this.upsertIndex("contribution");
70+
await this.upsertIndex("contributor");
5571
};
72+
73+
private async upsertIndex(index: SearchType): Promise<void> {
74+
try {
75+
await this.meilisearch.getIndex(index);
76+
this.logger.info({ message: `${index} index already exists` });
77+
} catch {
78+
await this.meilisearch.createIndex(index, {
79+
primaryKey: "id",
80+
});
81+
this.logger.info({ message: `${index} index created` });
82+
}
83+
}
84+
85+
private async updateFilterableAttributes(): Promise<void> {
86+
await this.meilisearch
87+
.index("project")
88+
.updateFilterableAttributes(["runId"]);
89+
await this.meilisearch
90+
.index("contribution")
91+
.updateFilterableAttributes(["runId"]);
92+
await this.meilisearch
93+
.index("contributor")
94+
.updateFilterableAttributes(["runId"]);
95+
}
5696
}

api/src/search/types.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
1+
import { ContributionEntity } from "@dzcode.io/models/dist/contribution";
2+
import { ContributorEntity } from "@dzcode.io/models/dist/contributor";
13
import { GeneralResponse } from "src/app/types";
4+
import { MultiSearchResponse } from "meilisearch";
5+
import { ProjectEntity } from "@dzcode.io/models/dist/project";
26

37
export interface GetSearchResponse extends GeneralResponse {
4-
searchResults: Array<SearchItem>;
8+
searchResults: SearchResults;
59
}
610

7-
export interface SearchItem {
8-
id: string;
9-
title: string;
10-
type: SearchType;
11-
}
11+
export type SearchResults = MultiSearchResponse<
12+
ProjectEntity | ContributionEntity | ContributorEntity
13+
>;
1214

1315
export type SearchType = "project" | "contribution" | "contributor";

0 commit comments

Comments
 (0)