-
-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: adding tick restriction for attempt type #444
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -81,14 +81,14 @@ type TickType { | |
If this is a native tick, you can enforce updated values here by referencing | ||
the climb document (climbId -> climbs:uuid) | ||
""" | ||
style: String | ||
style: TickStyle | ||
""" | ||
Describe the type of successful attempt that was made here. | ||
Fell/Hung, Flash, Redpoint, Onsight, would be examples of values you might find here. | ||
Attempt, Flash, Redpoint, Onsight, would be examples of values you might find here. | ||
This is again a free-form field. Data of practically any descriptive nature may find | ||
itself here. | ||
""" | ||
attemptType: String | ||
attemptType: TickAttemptType | ||
""" | ||
Not the same as date created. Ticks can be back-filled by the user, and do | ||
not need to be logged at the time that the tick is created inside the mongo | ||
|
@@ -120,6 +120,40 @@ enum TickSource { | |
MP | ||
} | ||
|
||
"""The type of attempt the user wants to tick for a route.""" | ||
enum TickAttemptType { | ||
"Onsight: Sending a route fist try with no prior knowledge" | ||
Onsight | ||
"Flash: Sending a route first try with prior knowledge" | ||
Flash | ||
"Pinkpoint: Sending a route with no falls on pre-placed gear" | ||
Pinkpoint | ||
"French Free: Doing the route with pulling on gear or draws" | ||
FrenchFree | ||
"Attempt" | ||
Attempt | ||
"Send: Sending a route with no falls" | ||
Send | ||
"Redpoint" | ||
Redpoint | ||
|
||
} | ||
|
||
enum TickStyle { | ||
"Lead" | ||
Lead | ||
"Solo" | ||
Solo | ||
"tr" | ||
TR | ||
"Follow" | ||
Follow | ||
"Aid" | ||
Aid | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we add 'Boulder'? It's a little counter-intuitive to me that this field is blank for boulders, IMHO setting the style to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know if I agree with that. The UI (at least on the mobile app) just won't show this field. So for boulders this field doesn't make sense at all. This is similar to how MP shows ticks for boulders |
||
|
||
|
||
|
||
""" | ||
This is our tick type input, containing the name, | ||
notes climbId, etc of the ticked climb, all fields are required | ||
|
@@ -131,8 +165,8 @@ input Tick { | |
notes: String | ||
climbId: String! | ||
userId: String! | ||
style: String | ||
attemptType: String | ||
style: TickStyle | ||
attemptType: TickAttemptType | ||
dateClimbed: Date! | ||
grade: String! | ||
source: TickSource! | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,20 +1,23 @@ | ||
import { MongoDataSource } from 'apollo-datasource-mongodb' | ||
import type { DeleteResult } from 'mongodb' | ||
import mongoose from 'mongoose' | ||
import muuid from 'uuid-mongodb' | ||
|
||
import { TickEditFilterType, TickInput, TickType, TickUserSelectors } from '../db/TickTypes' | ||
import { getTickModel, getUserModel } from '../db/index.js' | ||
import type { User } from '../db/UserTypes' | ||
import { getClimbModel } from '../db/ClimbSchema.js' | ||
|
||
export default class TickDataSource extends MongoDataSource<TickType> { | ||
tickModel = getTickModel() | ||
userModel = getUserModel() | ||
|
||
climbModel = getClimbModel() | ||
/** | ||
* @param tick takes in a new tick | ||
* @returns new tick | ||
*/ | ||
async addTick (tick: TickInput): Promise<TickType> { | ||
await this.validateTick(tick) | ||
return await this.tickModel.create({ ...tick }) | ||
} | ||
|
||
|
@@ -57,14 +60,15 @@ export default class TickDataSource extends MongoDataSource<TickType> { | |
* @returns the new/updated tick | ||
*/ | ||
async editTick (filter: TickEditFilterType, updatedTick: TickInput): Promise<TickType | null> { | ||
await this.validateTick(updatedTick) | ||
const rs = await this.tickModel.findOneAndUpdate(filter, updatedTick, { new: true }) | ||
return await rs?.toObject() ?? null | ||
} | ||
|
||
/** | ||
* @param ticks an array of ticks, with the Mountain Project id already hashed to the OpenTacos id | ||
* @returns an array of ticks, just created in the database | ||
*/ | ||
* @param ticks an array of ticks, with the Mountain Project id already hashed to the OpenTacos id | ||
* @returns an array of ticks, just created in the database | ||
*/ | ||
async importTicks (ticks: TickInput[]): Promise<TickType[]> { | ||
if (ticks.length > 0) { | ||
const res: TickType[] = await this.tickModel.insertMany(ticks) | ||
|
@@ -74,6 +78,62 @@ export default class TickDataSource extends MongoDataSource<TickType> { | |
} | ||
} | ||
|
||
private async validateTick (tick: TickInput): Promise<void> { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a request, could we make code a bit more readable, i.e. :
|
||
const climbIdAsUUID = muuid.from(tick.climbId) | ||
const climb = await this.climbModel | ||
.findOne({ _id: climbIdAsUUID, _deleting: { $eq: null } }) | ||
.lean() | ||
if (climb == null) { | ||
throw new Error('Climb not found') | ||
} | ||
// Getting the climb singular type to verifiy, some climbs have multiple types such as the heart route on elcap (13b/v10) | ||
const isDWSOnly = | ||
(climb.type.deepwatersolo === true) && | ||
Object.keys(climb.type).every( | ||
key => key === 'deepwatersolo' || climb.type[key] === false) | ||
|
||
const isBoulderingOnly = | ||
(climb.type.bouldering === true) && | ||
Object.keys(climb.type).every( | ||
key => key === 'bouldering' || climb.type[key] === false) | ||
|
||
const isTROnly = | ||
(climb.type.tr === true) && | ||
Object.keys(climb.type).every( | ||
key => key === 'tr' || climb.type[key] === false) | ||
|
||
const isAidOnly = | ||
(climb.type.aid === true) && | ||
Object.keys(climb.type).every( | ||
key => key === 'aid' || climb.type[key] === false) | ||
|
||
const isTradSportAlpineIceMixedAid = | ||
['trad', 'sport', 'alpine', 'ice', 'mixed', 'aid'].some( | ||
type => climb.type[type] === true) | ||
|
||
const tickStyle = tick.style ?? 'null' // Provide a default value if tick.style is undefined | ||
const attemptType = tick.attemptType ?? 'null' // Provide a default value if tick.attempy is undefined | ||
if (isDWSOnly || isBoulderingOnly) { // bouldering and dws can only have attempt types: 'Send', 'Flash', 'Attempt', 'Onsight' and should have no sytle | ||
if ((['Lead', 'Solo', 'Tr', 'Follow', 'Aid'].includes(tickStyle)) || ['Pinkpoint', 'Frenchfree', 'Redpoint'].includes(attemptType)) { | ||
throw new Error('Invalid attempt type or style for DWS/Bouldering') | ||
} | ||
} else if (isTROnly) { // TopRope can only have attempt types: 'Send', 'Flash', 'Attempt', 'Onsight' and styles: 'TR' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one doesn't quite make sense to me.
|
||
if (!['TR', 'null'].includes(tickStyle) || ['Pinkpoint', 'Frenchfree', 'Redpoint'].includes(attemptType)) { | ||
throw new Error('Invalid attempt type or style for TR only') | ||
} | ||
} else if (isAidOnly) { // Aid can only have attempt types: 'Send', 'Attempt' and styles: 'Aid', 'Follow' | ||
if (!['Aid', 'Follow', 'null'].includes(tickStyle) || ['Onsight', 'Flash', 'Pinkpoint', 'Frenchfree'].includes(attemptType)) { | ||
throw new Error('Invalid attempt type or style for Aid only') | ||
} | ||
} else if (isTradSportAlpineIceMixedAid) { // roped climbs that aren't lead must have attempt types: 'Send', 'Flash', 'Attempt', 'Onsight' | ||
if (['Solo', 'TR', 'Follow'].includes(tickStyle) && ['Pinkpoint', 'Frenchfree', 'Redpoint'].includes(attemptType)) { | ||
throw new Error('Invalid attempt type for Solo/TR/Follow style') | ||
} | ||
} else { | ||
throw new Error('Invalid climb type') | ||
} | ||
} | ||
|
||
/** | ||
* Retrieve ticks of a user given their details | ||
* @param userSelectors Attributes that can be used to identify the user | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Frenchfree
(instead ofFrenchFree
) to match above.