Skip to content

Commit 31d8bcb

Browse files
prazgaitisclaude
andcommitted
Add setup2027Challenge action to create next year's challenge
Creates March Fitness 2027 (Mar 1-30) and clones ALL activity types from 2026 including weekly specials, bonus types, and achievements. Only skips runtime-generated types (Mini-Game Bonus, Category Leader Bonus, Achievement Bonus) since those are created automatically. validWeeks, maxPerChallenge, availableInFinalDays all carry over since 2027 has the same 4-week + final days structure. Includes safety check to prevent duplicate creation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8166d93 commit 31d8bcb

2 files changed

Lines changed: 129 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: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"use node";
2+
3+
/**
4+
* Create March Fitness 2027 challenge and clone all activity types from 2026.
5+
*
6+
* Clones everything including weekly specials (burpee challenges, etc.)
7+
* and bonus activities. Skips only runtime-generated types (mini-game bonus,
8+
* category leader bonus, achievement bonus) since those are created
9+
* automatically when needed.
10+
*
11+
* validWeeks and other week-relative config carry over as-is since 2027
12+
* has the same 4-week + final days structure as 2026.
13+
*
14+
* Run with:
15+
* npx convex run actions/setup2027Challenge:setup2027Challenge --prod
16+
*/
17+
18+
import { action } from "../_generated/server";
19+
import { internal, api } from "../_generated/api";
20+
import type { Id } from "../_generated/dataModel";
21+
22+
const SOURCE_CHALLENGE = "March Fitness 2026";
23+
24+
// These are created at runtime by the system — don't clone them
25+
const RUNTIME_GENERATED_NAMES = new Set([
26+
"Mini-Game Bonus",
27+
"Category Leader Bonus",
28+
"Achievement Bonus",
29+
]);
30+
31+
export const setup2027Challenge = action({
32+
args: {},
33+
handler: async (ctx) => {
34+
const now = Date.now();
35+
36+
// Find the source challenge
37+
const challenges = await ctx.runQuery(api.queries.challenges.listPublic, {
38+
limit: 100,
39+
offset: 0,
40+
});
41+
42+
const source = challenges.find((c: any) => c.name === SOURCE_CHALLENGE);
43+
if (!source) throw new Error(`${SOURCE_CHALLENGE} not found`);
44+
45+
// Check if 2027 already exists
46+
const existing = challenges.find(
47+
(c: any) => c.name === "March Fitness 2027",
48+
);
49+
if (existing) {
50+
throw new Error(
51+
"March Fitness 2027 already exists! Delete it first if you want to re-create.",
52+
);
53+
}
54+
55+
// Get source activity types
56+
const sourceTypes = await ctx.runQuery(
57+
api.queries.activityTypes.getByChallengeId,
58+
{ challengeId: source.id as Id<"challenges"> },
59+
);
60+
61+
console.log(
62+
`Found ${sourceTypes.length} activity types in ${SOURCE_CHALLENGE}`,
63+
);
64+
65+
// Create March Fitness 2027: Mar 1-30 (same structure as 2026)
66+
console.log("Creating March Fitness 2027...");
67+
const challengeId = await ctx.runMutation(
68+
internal.mutations.challenges.create,
69+
{
70+
name: "March Fitness 2027",
71+
description:
72+
"The ultimate fitness challenge! Join hundreds of participants for 30 days of fitness, competition, and community.",
73+
creatorId: source.creatorId as Id<"users">,
74+
startDate: "2027-03-01",
75+
endDate: "2027-03-30",
76+
durationDays: 29,
77+
streakMinPoints: 10,
78+
weekCalcMethod: "sunday",
79+
createdAt: now,
80+
updatedAt: now,
81+
},
82+
);
83+
console.log(`✅ Created challenge: ${challengeId}`);
84+
85+
// Clone activity types
86+
let cloned = 0;
87+
let skipped = 0;
88+
89+
for (const at of sourceTypes) {
90+
if (RUNTIME_GENERATED_NAMES.has(at.name)) {
91+
console.log(` ⏭ Skipping (runtime-generated): ${at.name}`);
92+
skipped++;
93+
continue;
94+
}
95+
96+
const kind = (at as any).kind ?? "core";
97+
98+
await ctx.runMutation(internal.mutations.activityTypes.create, {
99+
challengeId,
100+
name: at.name,
101+
description: at.description,
102+
scoringConfig: at.scoringConfig as any,
103+
contributesToStreak: at.contributesToStreak,
104+
isNegative: at.isNegative,
105+
categoryId: (at as any).categoryId,
106+
bonusThresholds: (at as any).bonusThresholds,
107+
displayOrder: (at as any).displayOrder,
108+
sortOrder: (at as any).sortOrder,
109+
kind,
110+
availableInFinalDays: (at as any).availableInFinalDays,
111+
maxPerChallenge: (at as any).maxPerChallenge,
112+
validWeeks: (at as any).validWeeks,
113+
createdAt: now,
114+
updatedAt: now,
115+
});
116+
117+
const weekInfo = (at as any).validWeeks?.length
118+
? ` (weeks: ${(at as any).validWeeks.join(",")})`
119+
: "";
120+
console.log(` ✅ Cloned: ${at.name}${weekInfo}`);
121+
cloned++;
122+
}
123+
124+
console.log(`\n🎉 Done! Cloned ${cloned}, skipped ${skipped}`);
125+
return { challengeId, cloned, skipped };
126+
},
127+
});

0 commit comments

Comments
 (0)