Skip to content

Commit 2cafa5f

Browse files
prazgaitisclaude
andcommitted
Add setup2027Challenge action to create next year's challenge
Creates March Fitness 2027 (Mar 1-30) and clones core activity types from 2026. Skips yearly specials (burpee challenges, etc.) and bonus types (mini-game, category leader, achievement) which are configured per-year by admins. Run with: npx convex run actions/setup2027Challenge:setup2027Challenge --prod Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent c48af49 commit 2cafa5f

2 files changed

Lines changed: 123 additions & 0 deletions

File tree

packages/backend/_generated/api.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import type * as actions_setMarch2026ActivityOrder from "../actions/setMarch2026
2828
import type * as actions_setMarch2026FinalDays from "../actions/setMarch2026FinalDays.js";
2929
import type * as actions_setup2026ActivityTypes from "../actions/setup2026ActivityTypes.js";
3030
import type * as actions_setup2026Challenges from "../actions/setup2026Challenges.js";
31+
import type * as actions_setup2027Challenge from "../actions/setup2027Challenge.js";
3132
import type * as actions_strava from "../actions/strava.js";
3233
import type * as auth from "../auth.js";
3334
import type * as crons from "../crons.js";
@@ -174,6 +175,7 @@ declare const fullApi: ApiFromModules<{
174175
"actions/setMarch2026FinalDays": typeof actions_setMarch2026FinalDays;
175176
"actions/setup2026ActivityTypes": typeof actions_setup2026ActivityTypes;
176177
"actions/setup2026Challenges": typeof actions_setup2026Challenges;
178+
"actions/setup2027Challenge": typeof actions_setup2027Challenge;
177179
"actions/strava": typeof actions_strava;
178180
auth: typeof auth;
179181
crons: typeof crons;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
"use node";
2+
3+
/**
4+
* Create March Fitness 2027 challenge and clone activity types from 2026.
5+
*
6+
* Only copies core activity types (the ones people can log from day 1).
7+
* Weekly specials, mini-game bonuses, and category leader bonuses are
8+
* NOT cloned — those are configured each year by the admins.
9+
*
10+
* Run with:
11+
* npx convex run actions/setup2027Challenge:setup2027Challenge --prod
12+
*/
13+
14+
import { action } from "../_generated/server";
15+
import { internal, api } from "../_generated/api";
16+
import type { Id } from "../_generated/dataModel";
17+
18+
const SOURCE_CHALLENGE = "March Fitness 2026";
19+
20+
// Activity types to clone (by kind). Skip "bonus" kind since those are
21+
// mini-game/category-leader/achievement bonuses created at runtime.
22+
const CLONE_KINDS = new Set(["core", "special", "penalty"]);
23+
24+
// Activity types to skip by name (weekly specials that change each year)
25+
const SKIP_NAMES = new Set([
26+
"Burpee Challenge Week 1",
27+
"Burpee Challenge Week 2",
28+
"Burpee Challenge Week 3",
29+
"Burpee Challenge Week 4",
30+
"Workout with a Friend",
31+
"Retro Abs Bonus",
32+
"Card Workout (Partner or Solo)",
33+
"Active Recovery + Breath Work",
34+
]);
35+
36+
export const setup2027Challenge = action({
37+
args: {},
38+
handler: async (ctx) => {
39+
const now = Date.now();
40+
41+
// Find the source challenge
42+
const challenges = await ctx.runQuery(api.queries.challenges.listPublic, {
43+
limit: 100,
44+
offset: 0,
45+
});
46+
47+
const source = challenges.find((c: any) => c.name === SOURCE_CHALLENGE);
48+
if (!source) throw new Error(`${SOURCE_CHALLENGE} not found`);
49+
50+
// Get source activity types
51+
const sourceTypes = await ctx.runQuery(
52+
api.queries.activityTypes.getByChallengeId,
53+
{ challengeId: source.id as Id<"challenges"> },
54+
);
55+
56+
console.log(
57+
`Found ${sourceTypes.length} activity types in ${SOURCE_CHALLENGE}`,
58+
);
59+
60+
// Create March Fitness 2027: Mar 1-30
61+
console.log("Creating March Fitness 2027...");
62+
const challengeId = await ctx.runMutation(
63+
internal.mutations.challenges.create,
64+
{
65+
name: "March Fitness 2027",
66+
description:
67+
"The ultimate fitness challenge! Join hundreds of participants for 30 days of fitness, competition, and community.",
68+
creatorId: source.creatorId as Id<"users">,
69+
startDate: "2027-03-01",
70+
endDate: "2027-03-30",
71+
durationDays: 29,
72+
streakMinPoints: 10,
73+
weekCalcMethod: "sunday",
74+
createdAt: now,
75+
updatedAt: now,
76+
},
77+
);
78+
console.log(`✅ Created challenge: ${challengeId}`);
79+
80+
// Clone activity types
81+
let cloned = 0;
82+
let skipped = 0;
83+
84+
for (const at of sourceTypes) {
85+
const kind = (at as any).kind ?? "core";
86+
if (!CLONE_KINDS.has(kind)) {
87+
console.log(` ⏭ Skipping (kind=${kind}): ${at.name}`);
88+
skipped++;
89+
continue;
90+
}
91+
if (SKIP_NAMES.has(at.name)) {
92+
console.log(` ⏭ Skipping (yearly special): ${at.name}`);
93+
skipped++;
94+
continue;
95+
}
96+
97+
await ctx.runMutation(internal.mutations.activityTypes.create, {
98+
challengeId,
99+
name: at.name,
100+
description: at.description,
101+
scoringConfig: at.scoringConfig as any,
102+
contributesToStreak: at.contributesToStreak,
103+
isNegative: at.isNegative,
104+
categoryId: (at as any).categoryId,
105+
bonusThresholds: (at as any).bonusThresholds,
106+
displayOrder: (at as any).displayOrder,
107+
sortOrder: (at as any).sortOrder,
108+
kind: kind,
109+
availableInFinalDays: (at as any).availableInFinalDays,
110+
createdAt: now,
111+
updatedAt: now,
112+
});
113+
114+
console.log(` ✅ Cloned: ${at.name}`);
115+
cloned++;
116+
}
117+
118+
console.log(`\n🎉 Done! Cloned ${cloned}, skipped ${skipped}`);
119+
return { challengeId, cloned, skipped };
120+
},
121+
});

0 commit comments

Comments
 (0)