Skip to content

Commit 0e26918

Browse files
snomiaoclaude
andcommitted
refactor: update TaskMeta API and improve gh-service configuration
- Rename TaskMeta.$set to $upsert for clearer semantics - Update Docker configuration to use latest Bun image - Add gh-service npm script for easier development - Replace run/index.tsx with run/index.ts (TypeScript migration) - Fix code formatting issues across multiple files 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <[email protected]>
1 parent e98b3c4 commit 0e26918

File tree

6 files changed

+131
-112
lines changed

6 files changed

+131
-112
lines changed

app/api/router.ts

Lines changed: 98 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export const router = t.router({
2121
.meta({ openapi: { method: "GET", path: "/version", description: "Get version of ComfyPR" } })
2222
.input(z.object({}))
2323
.output(z.object({ version: z.string() }))
24-
.query(({ }) => ({ version: pkg.version })),
24+
.query(({}) => ({ version: pkg.version })),
2525
dumpCsv: t.procedure
2626
.meta({ openapi: { method: "GET", path: "/dump.csv", description: "Get csv dump" } })
2727
.input(z.object({}))
@@ -40,20 +40,22 @@ export const router = t.router({
4040
analyzePullsStatus: t.procedure
4141
.meta({ openapi: { method: "GET", path: "/analyze-pulls-status", description: "Get current worker" } })
4242
.input(z.object({ skip: z.number(), limit: z.number() }).partial())
43-
.output(z.object({
44-
updated: z.string(), // deprecated
45-
pull_updated: z.string(),
46-
repo_updated: z.string(),
47-
on_registry: z.boolean(),
48-
state: z.enum(["OPEN", "MERGED", "CLOSED"]),
49-
url: z.string(),
50-
head: z.string(),
51-
comments: z.number(),
52-
lastcomment: z.string(),
53-
ownername: z.string().optional(),
54-
repository: z.string().optional(),
55-
author_email: z.string().optional(),
56-
}))
43+
.output(
44+
z.object({
45+
updated: z.string(), // deprecated
46+
pull_updated: z.string(),
47+
repo_updated: z.string(),
48+
on_registry: z.boolean(),
49+
state: z.enum(["OPEN", "MERGED", "CLOSED"]),
50+
url: z.string(),
51+
head: z.string(),
52+
comments: z.number(),
53+
lastcomment: z.string(),
54+
ownername: z.string().optional(),
55+
repository: z.string().optional(),
56+
author_email: z.string().optional(),
57+
}),
58+
)
5759
.query(async ({ input: { limit = 0, skip = 0 } }) => (await analyzePullsStatus({ limit, skip })) as any),
5860
getRepoUrls: t.procedure
5961
.meta({ openapi: { method: "GET", path: "/repo-urls", description: "Get repo urls" } })
@@ -100,15 +102,19 @@ export const router = t.router({
100102
openapi: { method: "GET", path: "/github-contributor-analyze", description: "Get github contributor analyze" },
101103
})
102104
.input(z.object({ url: z.string() }))
103-
.output(z.object({
104-
repoUrl: z.string(),
105-
contributors: z.array(z.object({
106-
count: z.number(),
107-
name: z.string(),
108-
email: z.string(),
109-
})),
110-
updatedAt: z.date(),
111-
}))
105+
.output(
106+
z.object({
107+
repoUrl: z.string(),
108+
contributors: z.array(
109+
z.object({
110+
count: z.number(),
111+
name: z.string(),
112+
email: z.string(),
113+
}),
114+
),
115+
updatedAt: z.date(),
116+
}),
117+
)
112118
.query(async ({ input: { url } }) => {
113119
// await import { githubContributorAnalyze } from "../tasks/github-contributor-analyze/githubContributorAnalyze";
114120
const { githubContributorAnalyze } = await import("../tasks/github-contributor-analyze/githubContributorAnalyze");
@@ -121,21 +127,25 @@ export const router = t.router({
121127
openapi: { method: "GET", path: "/tasks/gh-design/meta", description: "Get github design task metadata" },
122128
})
123129
.input(z.object({}))
124-
.output(z.object({
125-
meta: z.object({
126-
name: z.string().optional(),
127-
description: z.string().optional(),
128-
slackChannelName: z.string().optional(),
129-
slackMessageTemplate: z.string().optional(),
130-
repoUrls: z.array(z.string()).optional(),
131-
requestReviewers: z.array(z.string()).optional(),
132-
matchLabels: z.string().optional(),
133-
slackChannelId: z.string().optional(),
134-
lastRunAt: z.date().optional(),
135-
lastStatus: z.enum(["success", "error", "running"]).optional(),
136-
lastError: z.string().optional(),
137-
}).nullable()
138-
}))
130+
.output(
131+
z.object({
132+
meta: z
133+
.object({
134+
name: z.string().optional(),
135+
description: z.string().optional(),
136+
slackChannelName: z.string().optional(),
137+
slackMessageTemplate: z.string().optional(),
138+
repoUrls: z.array(z.string()).optional(),
139+
requestReviewers: z.array(z.string()).optional(),
140+
matchLabels: z.string().optional(),
141+
slackChannelId: z.string().optional(),
142+
lastRunAt: z.date().optional(),
143+
lastStatus: z.enum(["success", "error", "running"]).optional(),
144+
lastError: z.string().optional(),
145+
})
146+
.nullable(),
147+
}),
148+
)
139149
.query(async () => {
140150
try {
141151
const meta = await GithubDesignTaskMeta.findOne({ coll: "GithubDesignTask" });
@@ -150,58 +160,65 @@ export const router = t.router({
150160
.meta({
151161
openapi: { method: "PATCH", path: "/tasks/gh-design/meta", description: "Update github design task metadata" },
152162
})
153-
.input(z.object({
154-
slackMessageTemplate: z.string()
155-
.min(1, "Slack message template cannot be empty")
156-
.refine(
157-
(template) => template.includes("{{ITEM_TYPE}}"),
158-
"Slack message template must include {{ITEM_TYPE}} placeholder"
159-
)
160-
.refine(
161-
(template) => template.includes("{{URL}}"),
162-
"Slack message template must include {{URL}} placeholder"
163-
)
164-
.refine(
165-
(template) => template.includes("{{TITLE}}"),
166-
"Slack message template must include {{TITLE}} placeholder"
167-
)
168-
.optional(),
169-
requestReviewers: z.array(z.string().min(1, "Reviewer username cannot be empty")).optional(),
170-
repoUrls: z.array(
171-
z.string()
172-
.url("Repository URL must be a valid URL")
163+
.input(
164+
z.object({
165+
slackMessageTemplate: z
166+
.string()
167+
.min(1, "Slack message template cannot be empty")
173168
.refine(
174-
(url) => url.startsWith("https://github.com"),
175-
"Repository URL must start with https://github.com"
169+
(template) => template.includes("{{ITEM_TYPE}}"),
170+
"Slack message template must include {{ITEM_TYPE}} placeholder",
176171
)
177-
).optional(),
178-
}))
179-
.output(z.object({
180-
success: z.boolean(),
181-
meta: z.object({
182-
name: z.string().optional(),
183-
description: z.string().optional(),
184-
slackChannelName: z.string().optional(),
185-
slackMessageTemplate: z.string().optional(),
186-
repoUrls: z.array(z.string()).optional(),
187-
requestReviewers: z.array(z.string()).optional(),
188-
matchLabels: z.string().optional(),
189-
slackChannelId: z.string().optional(),
190-
lastRunAt: z.date().optional(),
191-
lastStatus: z.enum(["success", "error", "running"]).optional(),
192-
lastError: z.string().optional(),
193-
}).nullable()
194-
}))
172+
.refine((template) => template.includes("{{URL}}"), "Slack message template must include {{URL}} placeholder")
173+
.refine(
174+
(template) => template.includes("{{TITLE}}"),
175+
"Slack message template must include {{TITLE}} placeholder",
176+
)
177+
.optional(),
178+
requestReviewers: z.array(z.string().min(1, "Reviewer username cannot be empty")).optional(),
179+
repoUrls: z
180+
.array(
181+
z
182+
.string()
183+
.url("Repository URL must be a valid URL")
184+
.refine(
185+
(url) => url.startsWith("https://github.com"),
186+
"Repository URL must start with https://github.com",
187+
),
188+
)
189+
.optional(),
190+
}),
191+
)
192+
.output(
193+
z.object({
194+
success: z.boolean(),
195+
meta: z
196+
.object({
197+
name: z.string().optional(),
198+
description: z.string().optional(),
199+
slackChannelName: z.string().optional(),
200+
slackMessageTemplate: z.string().optional(),
201+
repoUrls: z.array(z.string()).optional(),
202+
requestReviewers: z.array(z.string()).optional(),
203+
matchLabels: z.string().optional(),
204+
slackChannelId: z.string().optional(),
205+
lastRunAt: z.date().optional(),
206+
lastStatus: z.enum(["success", "error", "running"]).optional(),
207+
lastError: z.string().optional(),
208+
})
209+
.nullable(),
210+
}),
211+
)
195212
.mutation(async ({ input }) => {
196-
throw new Error("Meta editing functionality is temporarily disabled. This feature is under maintenance.");
213+
throw new Error("Meta editing functionality is temporarily disabled. This feature is under maintenance.");
197214
// TODO: add back later
198215
try {
199216
const updateData: any = {};
200217
if (input.slackMessageTemplate !== undefined) updateData.slackMessageTemplate = input.slackMessageTemplate;
201218
if (input.requestReviewers !== undefined) updateData.requestReviewers = input.requestReviewers;
202219
if (input.repoUrls !== undefined) updateData.repoUrls = input.repoUrls;
203220

204-
const meta = await GithubDesignTaskMeta.$set(updateData);
221+
const meta = await GithubDesignTaskMeta.$upsert(updateData);
205222
return { success: true, meta };
206223
} catch (error) {
207224
console.error("Failed to update metadata:", error);

app/tasks/gh-design/gh-design.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export async function runGithubDesignTask() {
101101
const dryRun = process.argv.includes("--dry");
102102

103103
tlog("Running gh design task...");
104-
let meta = await GithubDesignTaskMeta.$set({
104+
let meta = await GithubDesignTaskMeta.$upsert({
105105
name: "Github Design Issues Tracking Task",
106106
description:
107107
"Task to scan for [Design] labeled issues and PRs in specified repositories and notify product channel",
@@ -115,16 +115,16 @@ export async function runGithubDesignTask() {
115115

116116
// save default values if not set
117117
if (!meta.slackMessageTemplate)
118-
meta = await GithubDesignTaskMeta.$set({ slackMessageTemplate: ghDesignDefaultConfig.SLACK_MESSAGE_TEMPLATE });
118+
meta = await GithubDesignTaskMeta.$upsert({ slackMessageTemplate: ghDesignDefaultConfig.SLACK_MESSAGE_TEMPLATE });
119119
if (!meta.requestReviewers)
120-
meta = await GithubDesignTaskMeta.$set({ requestReviewers: ghDesignDefaultConfig.REQUEST_REVIEWERS });
121-
if (!meta.repoUrls) meta = await GithubDesignTaskMeta.$set({ repoUrls: ghDesignDefaultConfig.REPOS_TO_SCAN_URLS });
122-
if (!meta.matchLabels) meta = await GithubDesignTaskMeta.$set({ matchLabels: ghDesignDefaultConfig.MATCH_LABEL });
120+
meta = await GithubDesignTaskMeta.$upsert({ requestReviewers: ghDesignDefaultConfig.REQUEST_REVIEWERS });
121+
if (!meta.repoUrls) meta = await GithubDesignTaskMeta.$upsert({ repoUrls: ghDesignDefaultConfig.REPOS_TO_SCAN_URLS });
122+
if (!meta.matchLabels) meta = await GithubDesignTaskMeta.$upsert({ matchLabels: ghDesignDefaultConfig.MATCH_LABEL });
123123
if (!meta.slackChannelName)
124-
meta = await GithubDesignTaskMeta.$set({ slackChannelName: ghDesignDefaultConfig.SLACK_CHANNEL_NAME });
124+
meta = await GithubDesignTaskMeta.$upsert({ slackChannelName: ghDesignDefaultConfig.SLACK_CHANNEL_NAME });
125125
if (!meta.slackChannelId) {
126126
tlog("Fetching Slack product channel...");
127-
meta = await GithubDesignTaskMeta.$set({
127+
meta = await GithubDesignTaskMeta.$upsert({
128128
slackChannelId: (await getSlackChannel(meta.slackChannelName!)).id,
129129
});
130130
}
@@ -266,7 +266,7 @@ export async function runGithubDesignTask() {
266266
.run();
267267

268268
tlog("Github Design Task completed successfully.");
269-
await GithubDesignTaskMeta.$set({
269+
await GithubDesignTaskMeta.$upsert({
270270
lastRunAt: new Date(),
271271
lastStatus: "success",
272272
lastError: "",

docker-compose.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,11 @@ services:
3333
# - .env.local
3434

3535
gh-service:
36-
image: oven/bun
36+
image: oven/bun:latest
3737
# command: bun --hot run/index.tsx
3838
# dev environment
3939
# docker compose run gh-service
40-
command: bun run/index.tsx
40+
command: sh -c "bun run/index.ts"
4141
working_dir: /app
4242
volumes: [./:/app]
4343
ports: [14215:14215]

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
"predev": "bun i",
3030
"dev": "next dev",
3131
"dev:tsc": "tsc -w",
32+
"gh-service": "bun run/index.ts",
3233
"lint": "next lint",
3334
"prepare": "husky",
3435
"prerelease": "bun run build && bun run test && vercel --prod",
File renamed without changes.

src/db/TaskMeta.ts

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,48 +6,49 @@ import { db } from ".";
66

77
export const TaskMetaCollection = <S extends z.ZodObject<any>, const COLLECTION_NAME extends string = string>(
88
coll: COLLECTION_NAME,
9-
schema: S
9+
schema: S,
1010
) => {
11-
const c = db.collection<{ coll: COLLECTION_NAME; } & z.infer<S>>("TaskMeta");
11+
const c = db.collection<{ coll: COLLECTION_NAME } & z.infer<S>>("TaskMeta");
1212
return Object.assign(c, {
13-
$set: async (data: Partial<z.infer<S>>) => {
14-
13+
$upsert: async (data: Partial<z.infer<S>>) => {
1514
// Validate data with schema
1615
try {
1716
schema.partial().parse(data);
1817
} catch (error: unknown | z.ZodError) {
1918
if (error instanceof z.ZodError) {
20-
throw new Error(`Validation failed: ${error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
19+
throw new Error(
20+
`Validation failed: ${error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`,
21+
);
2122
}
2223
throw error;
2324
}
2425

25-
return await c.findOneAndUpdate(
26-
{ coll },
27-
{ $set: omit('coll', data) },
28-
{ upsert: true, returnDocument: "after" }
29-
) || DIE('never')
26+
return (
27+
(await c.findOneAndUpdate({ coll }, { $set: omit("coll", data) }, { upsert: true, returnDocument: "after" })) ||
28+
DIE("never")
29+
);
3030
},
3131
save: async (data: z.infer<S>) => {
3232
// Validate data with schema
3333
try {
3434
schema.parse(data);
3535
} catch (error: unknown | z.ZodError) {
3636
if (error instanceof z.ZodError) {
37-
throw new Error(`Validation failed: ${error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
37+
throw new Error(
38+
`Validation failed: ${error.issues.map((e) => `${e.path.join(".")}: ${e.message}`).join(", ")}`,
39+
);
3840
}
3941
throw error;
4042
}
4143

42-
return await c.findOneAndUpdate(
43-
{ coll },
44-
{ $set: omit('coll', data) },
45-
{ upsert: true, returnDocument: "after" }
46-
) || DIE('never');
44+
return (
45+
(await c.findOneAndUpdate({ coll }, { $set: omit("coll", data) }, { upsert: true, returnDocument: "after" })) ||
46+
DIE("never")
47+
);
4748
},
4849
});
4950
};
50-
await TaskMetaCollection('TaskMeta', z.object({})).createIndex({ coll: 1 }, { unique: true }); // Ensure unique collection names
51+
await TaskMetaCollection("TaskMeta", z.object({})).createIndex({ coll: 1 }, { unique: true }); // Ensure unique collection names
5152

5253
if (import.meta.main) {
5354
// Example usage with schema
@@ -57,19 +58,19 @@ if (import.meta.main) {
5758
optional: z.string().optional(), // Optional field
5859
});
5960

60-
const TaskMeta = TaskMetaCollection('example', exampleSchema);
61+
const TaskMeta = TaskMetaCollection("example", exampleSchema);
6162

62-
const meta = await TaskMeta.$set({
63+
const meta = await TaskMeta.$upsert({
6364
key: "value",
6465
updatedAt: new Date(),
6566
// asd: '123' // will throw validation error if uncommented
6667
});
6768
meta._id satisfies ObjectId; // Access the _id field
68-
meta.coll satisfies ('example'); // = 'example'
69+
meta.coll satisfies "example"; // = 'example'
6970
// meta.key; // Access the key field
7071
meta.updatedAt satisfies Date; // Access the updatedAt field
7172
// meta.optional; // Access the optional field, will be undefined if not set
7273

7374
console.log("Meta updated:", meta);
7475
console.log("Config collection initialized.");
75-
}
76+
}

0 commit comments

Comments
 (0)