Skip to content

Commit 14e82c0

Browse files
prazgaitisclaude
andcommitted
Add setup2027Challenge action to create next year's challenge
Creates March Fitness 2027 (Mar 1-30), clones ALL activity types from 2026, and creates 3 draft mini-games: - Week 2 (Mar 8-14): Partner Week - Week 3 (Mar 15-21): Hunt Week - Week 4 (Mar 22-28): PR Week Adds createInternal mutation to miniGames for script use. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 8166d93 commit 14e82c0

3 files changed

Lines changed: 190 additions & 1 deletion

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: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
"use node";
2+
3+
/**
4+
* Create March Fitness 2027 challenge, clone all activity types from 2026,
5+
* and create draft mini-games for weeks 2-4.
6+
*
7+
* Run with:
8+
* npx convex run actions/setup2027Challenge:setup2027Challenge --prod
9+
*/
10+
11+
import { action } from "../_generated/server";
12+
import { internal, api } from "../_generated/api";
13+
import type { Id } from "../_generated/dataModel";
14+
15+
const SOURCE_CHALLENGE = "March Fitness 2026";
16+
const CHALLENGE_START = "2027-03-01";
17+
const CHALLENGE_END = "2027-03-30";
18+
const DAY_MS = 24 * 60 * 60 * 1000;
19+
20+
export const setup2027Challenge = action({
21+
args: {},
22+
handler: async (ctx) => {
23+
const now = Date.now();
24+
25+
// Find the source challenge
26+
const challenges = await ctx.runQuery(api.queries.challenges.listPublic, {
27+
limit: 100,
28+
offset: 0,
29+
});
30+
31+
const source = challenges.find((c: any) => c.name === SOURCE_CHALLENGE);
32+
if (!source) throw new Error(`${SOURCE_CHALLENGE} not found`);
33+
34+
const existing = challenges.find(
35+
(c: any) => c.name === "March Fitness 2027",
36+
);
37+
if (existing) {
38+
throw new Error(
39+
"March Fitness 2027 already exists! Delete it first if you want to re-create.",
40+
);
41+
}
42+
43+
// Get source activity types
44+
const sourceTypes = await ctx.runQuery(
45+
api.queries.activityTypes.getByChallengeId,
46+
{ challengeId: source.id as Id<"challenges"> },
47+
);
48+
49+
console.log(
50+
`Found ${sourceTypes.length} activity types in ${SOURCE_CHALLENGE}`,
51+
);
52+
53+
// ── Create challenge ──────────────────────────────────────
54+
console.log("Creating March Fitness 2027...");
55+
const challengeId = await ctx.runMutation(
56+
internal.mutations.challenges.create,
57+
{
58+
name: "March Fitness 2027",
59+
description:
60+
"The ultimate fitness challenge! Join hundreds of participants for 30 days of fitness, competition, and community.",
61+
creatorId: source.creatorId as Id<"users">,
62+
startDate: CHALLENGE_START,
63+
endDate: CHALLENGE_END,
64+
durationDays: 29,
65+
streakMinPoints: 10,
66+
weekCalcMethod: "sunday",
67+
createdAt: now,
68+
updatedAt: now,
69+
},
70+
);
71+
console.log(`✅ Created challenge: ${challengeId}`);
72+
73+
// ── Clone all activity types ──────────────────────────────
74+
let cloned = 0;
75+
76+
for (const at of sourceTypes) {
77+
const kind = (at as any).kind ?? "core";
78+
79+
await ctx.runMutation(internal.mutations.activityTypes.create, {
80+
challengeId,
81+
name: at.name,
82+
description: at.description,
83+
scoringConfig: at.scoringConfig as any,
84+
contributesToStreak: at.contributesToStreak,
85+
isNegative: at.isNegative,
86+
categoryId: (at as any).categoryId,
87+
bonusThresholds: (at as any).bonusThresholds,
88+
displayOrder: (at as any).displayOrder,
89+
sortOrder: (at as any).sortOrder,
90+
kind,
91+
availableInFinalDays: (at as any).availableInFinalDays,
92+
maxPerChallenge: (at as any).maxPerChallenge,
93+
validWeeks: (at as any).validWeeks,
94+
createdAt: now,
95+
updatedAt: now,
96+
});
97+
98+
const weekInfo = (at as any).validWeeks?.length
99+
? ` (weeks: ${(at as any).validWeeks.join(",")})`
100+
: "";
101+
console.log(` ✅ ${at.name}${weekInfo}`);
102+
cloned++;
103+
}
104+
105+
console.log(`\nCloned ${cloned} activity types`);
106+
107+
// ── Create draft mini-games ───────────────────────────────
108+
// Week 1 (Mar 1-7): nothing
109+
// Week 2 (Mar 8-14): Partner Week
110+
// Week 3 (Mar 15-21): Hunt Week
111+
// Week 4 (Mar 22-28): PR Week
112+
const startMs = new Date(CHALLENGE_START + "T00:00:00Z").getTime();
113+
114+
const miniGames = [
115+
{
116+
type: "partner_week" as const,
117+
name: "Partner Week",
118+
startsAt: startMs + 7 * DAY_MS, // Mar 8
119+
endsAt: startMs + 14 * DAY_MS, // Mar 15 (exclusive)
120+
config: { bonusPercentage: 10 },
121+
},
122+
{
123+
type: "hunt_week" as const,
124+
name: "Hunt Week",
125+
startsAt: startMs + 14 * DAY_MS, // Mar 15
126+
endsAt: startMs + 21 * DAY_MS, // Mar 22 (exclusive)
127+
config: { catchBonus: 75, caughtPenalty: 25 },
128+
},
129+
{
130+
type: "pr_week" as const,
131+
name: "PR Week",
132+
startsAt: startMs + 21 * DAY_MS, // Mar 22
133+
endsAt: startMs + 28 * DAY_MS, // Mar 29 (exclusive)
134+
config: { prBonus: 100 },
135+
},
136+
];
137+
138+
for (const game of miniGames) {
139+
await ctx.runMutation(internal.mutations.miniGames.createInternal, {
140+
challengeId,
141+
type: game.type,
142+
name: game.name,
143+
startsAt: game.startsAt,
144+
endsAt: game.endsAt,
145+
config: game.config,
146+
});
147+
const startDate = new Date(game.startsAt).toISOString().slice(0, 10);
148+
const endDate = new Date(game.endsAt).toISOString().slice(0, 10);
149+
console.log(` ✅ ${game.name} (${startDate}${endDate}) [draft]`);
150+
}
151+
152+
console.log(`\n🎉 Done! ${cloned} activity types + 3 mini-games created`);
153+
return { challengeId, cloned, miniGames: 3 };
154+
},
155+
});

packages/backend/mutations/miniGames.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { mutation, type MutationCtx } from "../_generated/server";
1+
import { mutation, internalMutation, type MutationCtx } from "../_generated/server";
22
import { v } from "convex/values";
33
import type { Id } from "../_generated/dataModel";
44
import { requireCurrentUser } from "../lib/ids";
@@ -40,6 +40,38 @@ async function requireChallengeAdmin(
4040
return { user, challenge };
4141
}
4242

43+
/**
44+
* Internal: create a mini-game without auth (for setup scripts).
45+
*/
46+
export const createInternal = internalMutation({
47+
args: {
48+
challengeId: v.id("challenges"),
49+
type: v.union(
50+
v.literal("partner_week"),
51+
v.literal("hunt_week"),
52+
v.literal("pr_week"),
53+
),
54+
name: v.string(),
55+
startsAt: v.number(),
56+
endsAt: v.number(),
57+
config: v.optional(v.any()),
58+
},
59+
handler: async (ctx, args) => {
60+
const now = Date.now();
61+
return await ctx.db.insert("miniGames", {
62+
challengeId: args.challengeId,
63+
type: args.type,
64+
name: args.name,
65+
startsAt: args.startsAt,
66+
endsAt: args.endsAt,
67+
status: "draft",
68+
config: args.config ?? getDefaultConfig(args.type),
69+
createdAt: now,
70+
updatedAt: now,
71+
});
72+
},
73+
});
74+
4375
/**
4476
* Create a new mini-game (draft status)
4577
*/

0 commit comments

Comments
 (0)