From f8c4a57e78e64f857c64ac5c0ac5615593b8b7c4 Mon Sep 17 00:00:00 2001 From: Claudiu Farcas Date: Sat, 20 Jun 2026 01:49:15 +0000 Subject: [PATCH] feat: implement backend API with models and seed data for OctoFit application --- octofit-tracker/backend/dist/index.js | 93 ++++++++++ .../backend/dist/models/Activity.js | 15 ++ .../backend/dist/models/Leaderboard.js | 18 ++ octofit-tracker/backend/dist/models/Team.js | 13 ++ octofit-tracker/backend/dist/models/User.js | 15 ++ .../backend/dist/models/Workout.js | 15 ++ octofit-tracker/backend/dist/scripts/seed.js | 161 +++++++++++++++++ octofit-tracker/backend/package-lock.json | 6 +- octofit-tracker/backend/package.json | 4 +- octofit-tracker/backend/src/index.ts | 72 +++++++- .../backend/src/models/Activity.ts | 18 ++ .../backend/src/models/Leaderboard.ts | 25 +++ octofit-tracker/backend/src/models/Team.ts | 16 ++ octofit-tracker/backend/src/models/User.ts | 18 ++ octofit-tracker/backend/src/models/Workout.ts | 18 ++ octofit-tracker/backend/src/scripts/seed.ts | 166 ++++++++++++++++++ octofit-tracker/backend/tsconfig.json | 1 + 17 files changed, 669 insertions(+), 5 deletions(-) create mode 100644 octofit-tracker/backend/dist/index.js create mode 100644 octofit-tracker/backend/dist/models/Activity.js create mode 100644 octofit-tracker/backend/dist/models/Leaderboard.js create mode 100644 octofit-tracker/backend/dist/models/Team.js create mode 100644 octofit-tracker/backend/dist/models/User.js create mode 100644 octofit-tracker/backend/dist/models/Workout.js create mode 100644 octofit-tracker/backend/dist/scripts/seed.js create mode 100644 octofit-tracker/backend/src/models/Activity.ts create mode 100644 octofit-tracker/backend/src/models/Leaderboard.ts create mode 100644 octofit-tracker/backend/src/models/Team.ts create mode 100644 octofit-tracker/backend/src/models/User.ts create mode 100644 octofit-tracker/backend/src/models/Workout.ts create mode 100644 octofit-tracker/backend/src/scripts/seed.ts diff --git a/octofit-tracker/backend/dist/index.js b/octofit-tracker/backend/dist/index.js new file mode 100644 index 0000000..ada4f73 --- /dev/null +++ b/octofit-tracker/backend/dist/index.js @@ -0,0 +1,93 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const express_1 = __importDefault(require("express")); +const mongoose_1 = __importDefault(require("mongoose")); +const Activity_1 = require("./models/Activity"); +const Leaderboard_1 = require("./models/Leaderboard"); +const Team_1 = require("./models/Team"); +const User_1 = require("./models/User"); +const Workout_1 = require("./models/Workout"); +const app = (0, express_1.default)(); +const port = Number(process.env.PORT) || 8000; +const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/octofit_db"; +const codespaceName = process.env.CODESPACE_NAME; +const baseUrl = codespaceName + ? `https://${codespaceName}-8000.app.github.dev` + : `http://localhost:${port}`; +app.use(express_1.default.json()); +app.get("/health", (_req, res) => { + res.status(200).json({ status: "ok", baseUrl }); +}); +app.get("/api/users/", async (_req, res) => { + try { + const items = await User_1.UserModel.find().sort({ createdAt: -1 }).lean(); + res.status(200).json({ resource: "users", count: items.length, items }); + } + catch (error) { + res.status(500).json({ message: "Failed to fetch users", error }); + } +}); +app.get("/api/teams/", async (_req, res) => { + try { + const items = await Team_1.TeamModel.find() + .populate("captain", "username email") + .populate("members", "username email") + .sort({ createdAt: -1 }) + .lean(); + res.status(200).json({ resource: "teams", count: items.length, items }); + } + catch (error) { + res.status(500).json({ message: "Failed to fetch teams", error }); + } +}); +app.get("/api/activities/", async (_req, res) => { + try { + const items = await Activity_1.ActivityModel.find() + .populate("user", "username email") + .populate("team", "name city") + .sort({ completedAt: -1 }) + .lean(); + res.status(200).json({ resource: "activities", count: items.length, items }); + } + catch (error) { + res.status(500).json({ message: "Failed to fetch activities", error }); + } +}); +app.get("/api/leaderboard/", async (_req, res) => { + try { + const items = await Leaderboard_1.LeaderboardModel.find() + .populate("entries.user", "username email") + .populate("entries.team", "name city") + .sort({ generatedAt: -1 }) + .lean(); + res.status(200).json({ resource: "leaderboard", count: items.length, items }); + } + catch (error) { + res.status(500).json({ message: "Failed to fetch leaderboard", error }); + } +}); +app.get("/api/workouts/", async (_req, res) => { + try { + const items = await Workout_1.WorkoutModel.find().sort({ createdAt: -1 }).lean(); + res.status(200).json({ resource: "workouts", count: items.length, items }); + } + catch (error) { + res.status(500).json({ message: "Failed to fetch workouts", error }); + } +}); +const start = async () => { + try { + await mongoose_1.default.connect(mongoUri); + app.listen(port, () => { + console.log(`OctoFit backend listening on ${baseUrl}`); + }); + } + catch (error) { + console.error("Failed to start backend:", error); + process.exit(1); + } +}; +void start(); diff --git a/octofit-tracker/backend/dist/models/Activity.js b/octofit-tracker/backend/dist/models/Activity.js new file mode 100644 index 0000000..ceed461 --- /dev/null +++ b/octofit-tracker/backend/dist/models/Activity.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ActivityModel = void 0; +const mongoose_1 = require("mongoose"); +const activitySchema = new mongoose_1.Schema({ + user: { type: mongoose_1.Types.ObjectId, ref: "User", required: true }, + team: { type: mongoose_1.Types.ObjectId, ref: "Team", required: true }, + type: { type: String, required: true, trim: true }, + durationMinutes: { type: Number, required: true, min: 1 }, + caloriesBurned: { type: Number, required: true, min: 1 }, + completedAt: { type: Date, required: true }, +}, { + timestamps: true, +}); +exports.ActivityModel = (0, mongoose_1.model)("Activity", activitySchema); diff --git a/octofit-tracker/backend/dist/models/Leaderboard.js b/octofit-tracker/backend/dist/models/Leaderboard.js new file mode 100644 index 0000000..11672fc --- /dev/null +++ b/octofit-tracker/backend/dist/models/Leaderboard.js @@ -0,0 +1,18 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.LeaderboardModel = void 0; +const mongoose_1 = require("mongoose"); +const leaderboardEntrySchema = new mongoose_1.Schema({ + user: { type: mongoose_1.Types.ObjectId, ref: "User", required: true }, + team: { type: mongoose_1.Types.ObjectId, ref: "Team", required: true }, + points: { type: Number, required: true, min: 0 }, + rank: { type: Number, required: true, min: 1 }, +}, { _id: false }); +const leaderboardSchema = new mongoose_1.Schema({ + period: { type: String, required: true, trim: true }, + generatedAt: { type: Date, required: true }, + entries: { type: [leaderboardEntrySchema], required: true }, +}, { + timestamps: true, +}); +exports.LeaderboardModel = (0, mongoose_1.model)("Leaderboard", leaderboardSchema); diff --git a/octofit-tracker/backend/dist/models/Team.js b/octofit-tracker/backend/dist/models/Team.js new file mode 100644 index 0000000..bebc8b7 --- /dev/null +++ b/octofit-tracker/backend/dist/models/Team.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.TeamModel = void 0; +const mongoose_1 = require("mongoose"); +const teamSchema = new mongoose_1.Schema({ + name: { type: String, required: true, trim: true }, + city: { type: String, required: true, trim: true }, + captain: { type: mongoose_1.Types.ObjectId, ref: "User", required: true }, + members: [{ type: mongoose_1.Types.ObjectId, ref: "User", required: true }], +}, { + timestamps: true, +}); +exports.TeamModel = (0, mongoose_1.model)("Team", teamSchema); diff --git a/octofit-tracker/backend/dist/models/User.js b/octofit-tracker/backend/dist/models/User.js new file mode 100644 index 0000000..e72ceff --- /dev/null +++ b/octofit-tracker/backend/dist/models/User.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.UserModel = void 0; +const mongoose_1 = require("mongoose"); +const userSchema = new mongoose_1.Schema({ + username: { type: String, required: true, trim: true }, + email: { type: String, required: true, unique: true, lowercase: true, trim: true }, + age: { type: Number, required: true, min: 13 }, + heightCm: { type: Number, required: true, min: 100 }, + weightKg: { type: Number, required: true, min: 30 }, + fitnessGoal: { type: String, required: true, trim: true }, +}, { + timestamps: true, +}); +exports.UserModel = (0, mongoose_1.model)("User", userSchema); diff --git a/octofit-tracker/backend/dist/models/Workout.js b/octofit-tracker/backend/dist/models/Workout.js new file mode 100644 index 0000000..35b3fb4 --- /dev/null +++ b/octofit-tracker/backend/dist/models/Workout.js @@ -0,0 +1,15 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.WorkoutModel = void 0; +const mongoose_1 = require("mongoose"); +const workoutSchema = new mongoose_1.Schema({ + title: { type: String, required: true, trim: true }, + category: { type: String, required: true, trim: true }, + difficulty: { type: String, required: true, trim: true }, + durationMinutes: { type: Number, required: true, min: 1 }, + equipment: { type: [String], required: true }, + targetMuscles: { type: [String], required: true }, +}, { + timestamps: true, +}); +exports.WorkoutModel = (0, mongoose_1.model)("Workout", workoutSchema); diff --git a/octofit-tracker/backend/dist/scripts/seed.js b/octofit-tracker/backend/dist/scripts/seed.js new file mode 100644 index 0000000..9b1ca94 --- /dev/null +++ b/octofit-tracker/backend/dist/scripts/seed.js @@ -0,0 +1,161 @@ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +const mongoose_1 = __importDefault(require("mongoose")); +const Activity_1 = require("../models/Activity"); +const Leaderboard_1 = require("../models/Leaderboard"); +const Team_1 = require("../models/Team"); +const User_1 = require("../models/User"); +const Workout_1 = require("../models/Workout"); +const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/octofit_db"; +const seed = async () => { + try { + await mongoose_1.default.connect(mongoUri); + console.log("Seed the octofit_db database with test data"); + await Promise.all([ + Activity_1.ActivityModel.deleteMany({}), + Leaderboard_1.LeaderboardModel.deleteMany({}), + Team_1.TeamModel.deleteMany({}), + User_1.UserModel.deleteMany({}), + Workout_1.WorkoutModel.deleteMany({}), + ]); + const users = await User_1.UserModel.insertMany([ + { + username: "alex_runner", + email: "alex.runner@example.com", + age: 29, + heightCm: 178, + weightKg: 74, + fitnessGoal: "Half marathon in under 1h40", + }, + { + username: "maya_lifter", + email: "maya.lifter@example.com", + age: 33, + heightCm: 165, + weightKg: 62, + fitnessGoal: "Increase deadlift max to 140kg", + }, + { + username: "liam_hiker", + email: "liam.hiker@example.com", + age: 26, + heightCm: 182, + weightKg: 79, + fitnessGoal: "Improve endurance for alpine trekking", + }, + { + username: "sofia_cycle", + email: "sofia.cycle@example.com", + age: 31, + heightCm: 170, + weightKg: 66, + fitnessGoal: "Ride a 100km gran fondo", + }, + ]); + const teams = await Team_1.TeamModel.insertMany([ + { + name: "Pulse Pacers", + city: "Cluj-Napoca", + captain: users[0]._id, + members: [users[0]._id, users[2]._id], + }, + { + name: "Iron Orbit", + city: "Bucharest", + captain: users[1]._id, + members: [users[1]._id, users[3]._id], + }, + ]); + await Workout_1.WorkoutModel.insertMany([ + { + title: "Tempo Run 8K", + category: "Cardio", + difficulty: "Intermediate", + durationMinutes: 50, + equipment: ["Running shoes", "Sports watch"], + targetMuscles: ["Quadriceps", "Hamstrings", "Calves", "Core"], + }, + { + title: "Barbell Strength Circuit", + category: "Strength", + difficulty: "Advanced", + durationMinutes: 60, + equipment: ["Barbell", "Weight plates", "Bench"], + targetMuscles: ["Glutes", "Back", "Chest", "Shoulders"], + }, + { + title: "Mobility and Recovery Flow", + category: "Mobility", + difficulty: "Beginner", + durationMinutes: 30, + equipment: ["Yoga mat", "Resistance band"], + targetMuscles: ["Hips", "Lower back", "Shoulders"], + }, + ]); + await Activity_1.ActivityModel.insertMany([ + { + user: users[0]._id, + team: teams[0]._id, + type: "Interval Run", + durationMinutes: 42, + caloriesBurned: 520, + completedAt: new Date("2026-06-18T06:40:00.000Z"), + }, + { + user: users[1]._id, + team: teams[1]._id, + type: "Heavy Strength Session", + durationMinutes: 64, + caloriesBurned: 610, + completedAt: new Date("2026-06-18T18:20:00.000Z"), + }, + { + user: users[2]._id, + team: teams[0]._id, + type: "Hill Hike", + durationMinutes: 95, + caloriesBurned: 780, + completedAt: new Date("2026-06-19T07:10:00.000Z"), + }, + { + user: users[3]._id, + team: teams[1]._id, + type: "Road Cycling", + durationMinutes: 88, + caloriesBurned: 845, + completedAt: new Date("2026-06-19T16:05:00.000Z"), + }, + ]); + await Leaderboard_1.LeaderboardModel.insertMany([ + { + period: "weekly-2026-W25", + generatedAt: new Date("2026-06-20T00:00:00.000Z"), + entries: [ + { user: users[3]._id, team: teams[1]._id, points: 1280, rank: 1 }, + { user: users[2]._id, team: teams[0]._id, points: 1225, rank: 2 }, + { user: users[1]._id, team: teams[1]._id, points: 1160, rank: 3 }, + { user: users[0]._id, team: teams[0]._id, points: 1095, rank: 4 }, + ], + }, + ]); + const counts = { + users: await User_1.UserModel.countDocuments(), + teams: await Team_1.TeamModel.countDocuments(), + activities: await Activity_1.ActivityModel.countDocuments(), + leaderboard: await Leaderboard_1.LeaderboardModel.countDocuments(), + workouts: await Workout_1.WorkoutModel.countDocuments(), + }; + console.log("Seed complete", counts); + } + catch (error) { + console.error("Seed failed", error); + process.exitCode = 1; + } + finally { + await mongoose_1.default.disconnect(); + } +}; +void seed(); diff --git a/octofit-tracker/backend/package-lock.json b/octofit-tracker/backend/package-lock.json index 7c97538..4b1b58d 100644 --- a/octofit-tracker/backend/package-lock.json +++ b/octofit-tracker/backend/package-lock.json @@ -1,9 +1,12 @@ { - "name": "backend", + "name": "octofit-tracker-backend", + "version": "1.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "octofit-tracker-backend", + "version": "1.0.0", "dependencies": { "express": "^5.2.1", "mongoose": "^9.7.1" @@ -11,6 +14,7 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/node": "^26.0.0", + "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typescript": "^6.0.3" } diff --git a/octofit-tracker/backend/package.json b/octofit-tracker/backend/package.json index b8f9d65..7e3b195 100644 --- a/octofit-tracker/backend/package.json +++ b/octofit-tracker/backend/package.json @@ -6,7 +6,8 @@ "scripts": { "dev": "ts-node-dev --respawn --transpile-only src/index.ts", "build": "tsc -p tsconfig.json", - "start": "node dist/index.js" + "start": "node dist/index.js", + "seed": "ts-node src/scripts/seed.ts" }, "dependencies": { "express": "^5.2.1", @@ -15,6 +16,7 @@ "devDependencies": { "@types/express": "^5.0.6", "@types/node": "^26.0.0", + "ts-node": "^10.9.2", "ts-node-dev": "^2.0.0", "typescript": "^6.0.3" } diff --git a/octofit-tracker/backend/src/index.ts b/octofit-tracker/backend/src/index.ts index 4c50bc5..66df6eb 100644 --- a/octofit-tracker/backend/src/index.ts +++ b/octofit-tracker/backend/src/index.ts @@ -1,21 +1,87 @@ import express from "express"; import mongoose from "mongoose"; +import { ActivityModel } from "./models/Activity"; +import { LeaderboardModel } from "./models/Leaderboard"; +import { TeamModel } from "./models/Team"; +import { UserModel } from "./models/User"; +import { WorkoutModel } from "./models/Workout"; const app = express(); const port = Number(process.env.PORT) || 8000; -const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/octofit_tracker"; +const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/octofit_db"; +const codespaceName = process.env.CODESPACE_NAME; +const baseUrl = codespaceName + ? `https://${codespaceName}-8000.app.github.dev` + : `http://localhost:${port}`; app.use(express.json()); app.get("/health", (_req, res) => { - res.status(200).json({ status: "ok" }); + res.status(200).json({ status: "ok", baseUrl }); +}); + +app.get("/api/users/", async (_req, res) => { + try { + const items = await UserModel.find().sort({ createdAt: -1 }).lean(); + res.status(200).json({ resource: "users", count: items.length, items }); + } catch (error) { + res.status(500).json({ message: "Failed to fetch users", error }); + } +}); + +app.get("/api/teams/", async (_req, res) => { + try { + const items = await TeamModel.find() + .populate("captain", "username email") + .populate("members", "username email") + .sort({ createdAt: -1 }) + .lean(); + res.status(200).json({ resource: "teams", count: items.length, items }); + } catch (error) { + res.status(500).json({ message: "Failed to fetch teams", error }); + } +}); + +app.get("/api/activities/", async (_req, res) => { + try { + const items = await ActivityModel.find() + .populate("user", "username email") + .populate("team", "name city") + .sort({ completedAt: -1 }) + .lean(); + res.status(200).json({ resource: "activities", count: items.length, items }); + } catch (error) { + res.status(500).json({ message: "Failed to fetch activities", error }); + } +}); + +app.get("/api/leaderboard/", async (_req, res) => { + try { + const items = await LeaderboardModel.find() + .populate("entries.user", "username email") + .populate("entries.team", "name city") + .sort({ generatedAt: -1 }) + .lean(); + res.status(200).json({ resource: "leaderboard", count: items.length, items }); + } catch (error) { + res.status(500).json({ message: "Failed to fetch leaderboard", error }); + } +}); + +app.get("/api/workouts/", async (_req, res) => { + try { + const items = await WorkoutModel.find().sort({ createdAt: -1 }).lean(); + res.status(200).json({ resource: "workouts", count: items.length, items }); + } catch (error) { + res.status(500).json({ message: "Failed to fetch workouts", error }); + } }); const start = async () => { try { await mongoose.connect(mongoUri); app.listen(port, () => { - console.log(`OctoFit backend listening on http://localhost:${port}`); + console.log(`OctoFit backend listening on ${baseUrl}`); }); } catch (error) { console.error("Failed to start backend:", error); diff --git a/octofit-tracker/backend/src/models/Activity.ts b/octofit-tracker/backend/src/models/Activity.ts new file mode 100644 index 0000000..9dd1608 --- /dev/null +++ b/octofit-tracker/backend/src/models/Activity.ts @@ -0,0 +1,18 @@ +import { Schema, model, type InferSchemaType, Types } from "mongoose"; + +const activitySchema = new Schema( + { + user: { type: Types.ObjectId, ref: "User", required: true }, + team: { type: Types.ObjectId, ref: "Team", required: true }, + type: { type: String, required: true, trim: true }, + durationMinutes: { type: Number, required: true, min: 1 }, + caloriesBurned: { type: Number, required: true, min: 1 }, + completedAt: { type: Date, required: true }, + }, + { + timestamps: true, + }, +); + +export type ActivityDocument = InferSchemaType; +export const ActivityModel = model("Activity", activitySchema); diff --git a/octofit-tracker/backend/src/models/Leaderboard.ts b/octofit-tracker/backend/src/models/Leaderboard.ts new file mode 100644 index 0000000..ec9593e --- /dev/null +++ b/octofit-tracker/backend/src/models/Leaderboard.ts @@ -0,0 +1,25 @@ +import { Schema, model, type InferSchemaType, Types } from "mongoose"; + +const leaderboardEntrySchema = new Schema( + { + user: { type: Types.ObjectId, ref: "User", required: true }, + team: { type: Types.ObjectId, ref: "Team", required: true }, + points: { type: Number, required: true, min: 0 }, + rank: { type: Number, required: true, min: 1 }, + }, + { _id: false }, +); + +const leaderboardSchema = new Schema( + { + period: { type: String, required: true, trim: true }, + generatedAt: { type: Date, required: true }, + entries: { type: [leaderboardEntrySchema], required: true }, + }, + { + timestamps: true, + }, +); + +export type LeaderboardDocument = InferSchemaType; +export const LeaderboardModel = model("Leaderboard", leaderboardSchema); diff --git a/octofit-tracker/backend/src/models/Team.ts b/octofit-tracker/backend/src/models/Team.ts new file mode 100644 index 0000000..4783bac --- /dev/null +++ b/octofit-tracker/backend/src/models/Team.ts @@ -0,0 +1,16 @@ +import { Schema, model, type InferSchemaType, Types } from "mongoose"; + +const teamSchema = new Schema( + { + name: { type: String, required: true, trim: true }, + city: { type: String, required: true, trim: true }, + captain: { type: Types.ObjectId, ref: "User", required: true }, + members: [{ type: Types.ObjectId, ref: "User", required: true }], + }, + { + timestamps: true, + }, +); + +export type TeamDocument = InferSchemaType; +export const TeamModel = model("Team", teamSchema); diff --git a/octofit-tracker/backend/src/models/User.ts b/octofit-tracker/backend/src/models/User.ts new file mode 100644 index 0000000..fc2b2a9 --- /dev/null +++ b/octofit-tracker/backend/src/models/User.ts @@ -0,0 +1,18 @@ +import { Schema, model, type InferSchemaType } from "mongoose"; + +const userSchema = new Schema( + { + username: { type: String, required: true, trim: true }, + email: { type: String, required: true, unique: true, lowercase: true, trim: true }, + age: { type: Number, required: true, min: 13 }, + heightCm: { type: Number, required: true, min: 100 }, + weightKg: { type: Number, required: true, min: 30 }, + fitnessGoal: { type: String, required: true, trim: true }, + }, + { + timestamps: true, + }, +); + +export type UserDocument = InferSchemaType; +export const UserModel = model("User", userSchema); diff --git a/octofit-tracker/backend/src/models/Workout.ts b/octofit-tracker/backend/src/models/Workout.ts new file mode 100644 index 0000000..e8d4b6f --- /dev/null +++ b/octofit-tracker/backend/src/models/Workout.ts @@ -0,0 +1,18 @@ +import { Schema, model, type InferSchemaType } from "mongoose"; + +const workoutSchema = new Schema( + { + title: { type: String, required: true, trim: true }, + category: { type: String, required: true, trim: true }, + difficulty: { type: String, required: true, trim: true }, + durationMinutes: { type: Number, required: true, min: 1 }, + equipment: { type: [String], required: true }, + targetMuscles: { type: [String], required: true }, + }, + { + timestamps: true, + }, +); + +export type WorkoutDocument = InferSchemaType; +export const WorkoutModel = model("Workout", workoutSchema); diff --git a/octofit-tracker/backend/src/scripts/seed.ts b/octofit-tracker/backend/src/scripts/seed.ts new file mode 100644 index 0000000..6b51055 --- /dev/null +++ b/octofit-tracker/backend/src/scripts/seed.ts @@ -0,0 +1,166 @@ +import mongoose from "mongoose"; +import { ActivityModel } from "../models/Activity"; +import { LeaderboardModel } from "../models/Leaderboard"; +import { TeamModel } from "../models/Team"; +import { UserModel } from "../models/User"; +import { WorkoutModel } from "../models/Workout"; + +const mongoUri = process.env.MONGO_URI || "mongodb://127.0.0.1:27017/octofit_db"; + +const seed = async () => { + try { + await mongoose.connect(mongoUri); + + console.log("Seed the octofit_db database with test data"); + + await Promise.all([ + ActivityModel.deleteMany({}), + LeaderboardModel.deleteMany({}), + TeamModel.deleteMany({}), + UserModel.deleteMany({}), + WorkoutModel.deleteMany({}), + ]); + + const users = await UserModel.insertMany([ + { + username: "alex_runner", + email: "alex.runner@example.com", + age: 29, + heightCm: 178, + weightKg: 74, + fitnessGoal: "Half marathon in under 1h40", + }, + { + username: "maya_lifter", + email: "maya.lifter@example.com", + age: 33, + heightCm: 165, + weightKg: 62, + fitnessGoal: "Increase deadlift max to 140kg", + }, + { + username: "liam_hiker", + email: "liam.hiker@example.com", + age: 26, + heightCm: 182, + weightKg: 79, + fitnessGoal: "Improve endurance for alpine trekking", + }, + { + username: "sofia_cycle", + email: "sofia.cycle@example.com", + age: 31, + heightCm: 170, + weightKg: 66, + fitnessGoal: "Ride a 100km gran fondo", + }, + ]); + + const teams = await TeamModel.insertMany([ + { + name: "Pulse Pacers", + city: "Cluj-Napoca", + captain: users[0]._id, + members: [users[0]._id, users[2]._id], + }, + { + name: "Iron Orbit", + city: "Bucharest", + captain: users[1]._id, + members: [users[1]._id, users[3]._id], + }, + ]); + + await WorkoutModel.insertMany([ + { + title: "Tempo Run 8K", + category: "Cardio", + difficulty: "Intermediate", + durationMinutes: 50, + equipment: ["Running shoes", "Sports watch"], + targetMuscles: ["Quadriceps", "Hamstrings", "Calves", "Core"], + }, + { + title: "Barbell Strength Circuit", + category: "Strength", + difficulty: "Advanced", + durationMinutes: 60, + equipment: ["Barbell", "Weight plates", "Bench"], + targetMuscles: ["Glutes", "Back", "Chest", "Shoulders"], + }, + { + title: "Mobility and Recovery Flow", + category: "Mobility", + difficulty: "Beginner", + durationMinutes: 30, + equipment: ["Yoga mat", "Resistance band"], + targetMuscles: ["Hips", "Lower back", "Shoulders"], + }, + ]); + + await ActivityModel.insertMany([ + { + user: users[0]._id, + team: teams[0]._id, + type: "Interval Run", + durationMinutes: 42, + caloriesBurned: 520, + completedAt: new Date("2026-06-18T06:40:00.000Z"), + }, + { + user: users[1]._id, + team: teams[1]._id, + type: "Heavy Strength Session", + durationMinutes: 64, + caloriesBurned: 610, + completedAt: new Date("2026-06-18T18:20:00.000Z"), + }, + { + user: users[2]._id, + team: teams[0]._id, + type: "Hill Hike", + durationMinutes: 95, + caloriesBurned: 780, + completedAt: new Date("2026-06-19T07:10:00.000Z"), + }, + { + user: users[3]._id, + team: teams[1]._id, + type: "Road Cycling", + durationMinutes: 88, + caloriesBurned: 845, + completedAt: new Date("2026-06-19T16:05:00.000Z"), + }, + ]); + + await LeaderboardModel.insertMany([ + { + period: "weekly-2026-W25", + generatedAt: new Date("2026-06-20T00:00:00.000Z"), + entries: [ + { user: users[3]._id, team: teams[1]._id, points: 1280, rank: 1 }, + { user: users[2]._id, team: teams[0]._id, points: 1225, rank: 2 }, + { user: users[1]._id, team: teams[1]._id, points: 1160, rank: 3 }, + { user: users[0]._id, team: teams[0]._id, points: 1095, rank: 4 }, + ], + }, + ]); + + const counts = { + users: await UserModel.countDocuments(), + teams: await TeamModel.countDocuments(), + activities: await ActivityModel.countDocuments(), + leaderboard: await LeaderboardModel.countDocuments(), + workouts: await WorkoutModel.countDocuments(), + }; + + console.log("Seed complete", counts); + } catch (error) { + console.error("Seed failed", error); + process.exitCode = 1; + } finally { + await mongoose.disconnect(); + } +}; + +void seed(); diff --git a/octofit-tracker/backend/tsconfig.json b/octofit-tracker/backend/tsconfig.json index 7666a5d..d6773da 100644 --- a/octofit-tracker/backend/tsconfig.json +++ b/octofit-tracker/backend/tsconfig.json @@ -3,6 +3,7 @@ "target": "ES2020", "module": "Node16", "moduleResolution": "Node16", + "types": ["node"], "outDir": "dist", "rootDir": "src", "strict": true,