Skip to main content

max / ripgrow

7.2 KB · 261 lines History Blame Raw
1 //! Starter content for a fresh profile.
2 //!
3 //! A common set of tags and exercises so a new profile is not empty on
4 //! first open. The user is free to edit or delete anything here; the seed
5 //! runs once at profile creation and is never re-applied.
6
7 use crate::db::Db;
8 use crate::values::LoadUnit;
9 use crate::error::Error;
10 use crate::templates::ResistanceType;
11
12 struct SeedExercise {
13 name: &'static str,
14 resistance_type: ResistanceType,
15 tags: &'static [&'static str],
16 }
17
18 const SEED_TAGS: &[&str] = &[
19 "push",
20 "pull",
21 "squat",
22 "hinge",
23 "chest",
24 "back",
25 "shoulders",
26 "biceps",
27 "triceps",
28 "quads",
29 "hamstrings",
30 "glutes",
31 "calves",
32 "core",
33 ];
34
35 const SEED_EXERCISES: &[SeedExercise] = &[
36 // Barbell staples.
37 SeedExercise {
38 name: "back squat",
39 resistance_type: ResistanceType::Freeweight,
40 tags: &["squat", "quads", "glutes", "core"],
41 },
42 SeedExercise {
43 name: "front squat",
44 resistance_type: ResistanceType::Freeweight,
45 tags: &["squat", "quads", "core"],
46 },
47 SeedExercise {
48 name: "deadlift",
49 resistance_type: ResistanceType::Freeweight,
50 tags: &["hinge", "back", "hamstrings", "glutes"],
51 },
52 SeedExercise {
53 name: "romanian deadlift",
54 resistance_type: ResistanceType::Freeweight,
55 tags: &["hinge", "hamstrings", "glutes"],
56 },
57 SeedExercise {
58 name: "bench press",
59 resistance_type: ResistanceType::Freeweight,
60 tags: &["push", "chest", "triceps", "shoulders"],
61 },
62 SeedExercise {
63 name: "incline bench press",
64 resistance_type: ResistanceType::Freeweight,
65 tags: &["push", "chest", "shoulders", "triceps"],
66 },
67 SeedExercise {
68 name: "overhead press",
69 resistance_type: ResistanceType::Freeweight,
70 tags: &["push", "shoulders", "triceps"],
71 },
72 SeedExercise {
73 name: "barbell row",
74 resistance_type: ResistanceType::Freeweight,
75 tags: &["pull", "back", "biceps"],
76 },
77 // Machines.
78 SeedExercise {
79 name: "lat pulldown",
80 resistance_type: ResistanceType::Machine,
81 tags: &["pull", "back", "biceps"],
82 },
83 SeedExercise {
84 name: "seated row",
85 resistance_type: ResistanceType::Machine,
86 tags: &["pull", "back", "biceps"],
87 },
88 SeedExercise {
89 name: "leg press",
90 resistance_type: ResistanceType::Machine,
91 tags: &["squat", "quads", "glutes"],
92 },
93 SeedExercise {
94 name: "leg curl",
95 resistance_type: ResistanceType::Machine,
96 tags: &["hamstrings"],
97 },
98 SeedExercise {
99 name: "leg extension",
100 resistance_type: ResistanceType::Machine,
101 tags: &["quads"],
102 },
103 SeedExercise {
104 name: "calf raise",
105 resistance_type: ResistanceType::Machine,
106 tags: &["calves"],
107 },
108 SeedExercise {
109 name: "tricep pushdown",
110 resistance_type: ResistanceType::Machine,
111 tags: &["push", "triceps"],
112 },
113 SeedExercise {
114 name: "cable curl",
115 resistance_type: ResistanceType::Machine,
116 tags: &["pull", "biceps"],
117 },
118 // Bodyweight.
119 SeedExercise {
120 name: "pull-up",
121 resistance_type: ResistanceType::Bodyweight,
122 tags: &["pull", "back", "biceps"],
123 },
124 SeedExercise {
125 name: "chin-up",
126 resistance_type: ResistanceType::Bodyweight,
127 tags: &["pull", "back", "biceps"],
128 },
129 SeedExercise {
130 name: "dip",
131 resistance_type: ResistanceType::Bodyweight,
132 tags: &["push", "chest", "triceps"],
133 },
134 SeedExercise {
135 name: "push-up",
136 resistance_type: ResistanceType::Bodyweight,
137 tags: &["push", "chest", "triceps"],
138 },
139 SeedExercise {
140 name: "hanging leg raise",
141 resistance_type: ResistanceType::Bodyweight,
142 tags: &["core"],
143 },
144 ];
145
146 /// Insert the starter tag vocabulary and exercise library into a fresh
147 /// profile. Idempotent: pre-existing tags are reused via `upsert_tag`;
148 /// pre-existing exercises by name are skipped (`UNIQUE` constraint on
149 /// `exercises.name` would otherwise error). Load unit and increment are
150 /// derived from the profile's `LoadUnit`.
151 pub fn seed_starter_content(db: &Db, unit: LoadUnit) -> Result<(), Error> {
152 for tag in SEED_TAGS {
153 db.upsert_tag(tag)?;
154 }
155 let existing = db.list_exercises()?;
156 for seed in SEED_EXERCISES {
157 if existing.iter().any(|e| e.name == seed.name) {
158 continue;
159 }
160 let tag_ids = seed
161 .tags
162 .iter()
163 .map(|name| Ok(db.upsert_tag(name)?.id))
164 .collect::<Result<Vec<_>, Error>>()?;
165 let increment = increment_for(seed.resistance_type, unit);
166 db.create_exercise(
167 seed.name,
168 seed.resistance_type,
169 unit,
170 increment,
171 &tag_ids,
172 )?;
173 }
174 Ok(())
175 }
176
177 fn increment_for(rt: ResistanceType, unit: LoadUnit) -> f64 {
178 match rt {
179 ResistanceType::Bodyweight => 0.0,
180 ResistanceType::CardioTime | ResistanceType::CardioDistance => 0.0,
181 ResistanceType::Freeweight => match unit {
182 LoadUnit::Kg => 2.5,
183 LoadUnit::Lb => 5.0,
184 },
185 ResistanceType::Machine => match unit {
186 LoadUnit::Kg => 5.0,
187 LoadUnit::Lb => 10.0,
188 },
189 }
190 }
191
192 #[cfg(test)]
193 mod tests {
194 use super::*;
195
196 fn fresh() -> Db {
197 let db = Db::open_in_memory().unwrap();
198 db.init_profile("self", LoadUnit::Kg).unwrap();
199 db
200 }
201
202 #[test]
203 fn seed_creates_expected_counts() {
204 let db = fresh();
205 seed_starter_content(&db, LoadUnit::Kg).unwrap();
206 assert_eq!(db.list_tags().unwrap().len(), SEED_TAGS.len());
207 assert_eq!(db.list_exercises().unwrap().len(), SEED_EXERCISES.len());
208 }
209
210 #[test]
211 fn seed_is_idempotent_when_run_twice() {
212 let db = fresh();
213 seed_starter_content(&db, LoadUnit::Kg).unwrap();
214 seed_starter_content(&db, LoadUnit::Kg).unwrap();
215 assert_eq!(db.list_exercises().unwrap().len(), SEED_EXERCISES.len());
216 }
217
218 #[test]
219 fn seed_respects_unit_for_increments() {
220 let db = fresh();
221 seed_starter_content(&db, LoadUnit::Lb).unwrap();
222 let squat = db
223 .list_exercises()
224 .unwrap()
225 .into_iter()
226 .find(|e| e.name == "back squat")
227 .unwrap();
228 assert_eq!(squat.load_unit, LoadUnit::Lb);
229 assert_eq!(squat.increment, 5.0);
230
231 let leg_press = db
232 .list_exercises()
233 .unwrap()
234 .into_iter()
235 .find(|e| e.name == "leg press")
236 .unwrap();
237 assert_eq!(leg_press.increment, 10.0);
238
239 let pullup = db
240 .list_exercises()
241 .unwrap()
242 .into_iter()
243 .find(|e| e.name == "pull-up")
244 .unwrap();
245 assert_eq!(pullup.increment, 0.0);
246 }
247
248 #[test]
249 fn seed_wires_up_tags() {
250 let db = fresh();
251 seed_starter_content(&db, LoadUnit::Kg).unwrap();
252 let squat = db
253 .list_exercises()
254 .unwrap()
255 .into_iter()
256 .find(|e| e.name == "back squat")
257 .unwrap();
258 assert_eq!(squat.tag_ids.len(), 4);
259 }
260 }
261