@@ -82,8 +82,45 @@ class Station extends Model {
8282 onlyGmaxStationed,
8383 onlyInactiveStations,
8484 } = args . filters
85+
86+ const battleLevelFilters = new Set ( )
87+ const battleComboFilters = new Map ( )
88+ if ( onlyMaxBattles && onlyBattleTier === 'all' ) {
89+ Object . entries ( args . filters || { } ) . forEach ( ( [ key , value ] ) => {
90+ if ( ! value ) return
91+ if ( key . startsWith ( 'j' ) ) {
92+ const parsedLevel = Number ( key . slice ( 1 ) )
93+ if ( Number . isFinite ( parsedLevel ) ) {
94+ battleLevelFilters . add ( parsedLevel )
95+ }
96+ return
97+ }
98+ if ( / ^ \d + - / . test ( key ) ) {
99+ const [ idPart , formPart ] = key . split ( '-' , 2 )
100+ const pokemonId = Number ( idPart )
101+ if ( ! Number . isFinite ( pokemonId ) ) return
102+ let formValue = null
103+ if ( formPart && formPart !== 'null' ) {
104+ const parsedForm = Number ( formPart )
105+ if ( ! Number . isFinite ( parsedForm ) ) return
106+ formValue = parsedForm
107+ }
108+ const comboKey = `${ pokemonId } -${ formValue ?? 'null' } `
109+ if ( ! battleComboFilters . has ( comboKey ) ) {
110+ battleComboFilters . set ( comboKey , { pokemonId, form : formValue } )
111+ }
112+ }
113+ } )
114+ }
115+ const battleLevels = [ ...battleLevelFilters ]
116+ const battleCombos = [ ...battleComboFilters . values ( ) ]
117+
118+ if ( ! onlyAllStations && ! onlyInactiveStations && ! perms . dynamax ) {
119+ return [ ]
120+ }
121+
85122 const ts = getEpoch ( )
86- const select = [
123+ const baseSelect = [
87124 'id' ,
88125 'name' ,
89126 'lat' ,
@@ -93,8 +130,7 @@ class Station extends Model {
93130 'end_time' ,
94131 ]
95132
96- const query = this . query ( )
97- applyManualIdFilter ( query , {
133+ const manualFilterOptions = {
98134 manualId : args . filters . onlyManualId ,
99135 latColumn : 'lat' ,
100136 lonColumn : 'lon' ,
@@ -105,30 +141,16 @@ class Station extends Model {
105141 minLon : args . minLon ,
106142 maxLon : args . maxLon ,
107143 } ,
108- } )
144+ }
145+
146+ const select = [ ...baseSelect ]
147+
148+ const query = this . query ( )
149+ applyManualIdFilter ( query , manualFilterOptions )
109150 const now = Date . now ( ) / 1000
110151 const activeCutoff = now - stationUpdateLimit * 60 * 60
111152 const inactiveCutoff = now - stationInactiveLimitDays * 24 * 60 * 60
112153
113- if ( onlyInactiveStations ) {
114- query . andWhere ( ( builder ) => {
115- builder
116- . where ( ( active ) =>
117- active
118- . where ( 'end_time' , '>' , ts )
119- . andWhere ( 'updated' , '>' , activeCutoff ) ,
120- )
121- . orWhere ( ( inactive ) =>
122- inactive
123- . where ( 'end_time' , '<=' , ts )
124- . andWhere ( 'updated' , '>' , inactiveCutoff ) ,
125- )
126- } )
127- } else {
128- query . andWhere ( 'end_time' , '>' , ts ) . andWhere ( 'updated' , '>' , activeCutoff )
129- }
130- // .where('is_inactive', false)
131-
132154 if ( perms . dynamax && ( onlyMaxBattles || onlyGmaxStationed ) ) {
133155 select . push (
134156 'is_battle_available' ,
@@ -151,58 +173,116 @@ class Station extends Model {
151173 if ( hasBattlePokemonStats ) {
152174 select . push ( 'battle_pokemon_stamina' , 'battle_pokemon_cp_multiplier' )
153175 }
176+ }
154177
155- if ( ! onlyAllStations ) {
156- query . whereNotNull ( 'battle_pokemon_id' ) . andWhere ( 'battle_end' , '>' , ts )
178+ const applyStationFilters = ( builder ) => {
179+ if ( onlyAllStations ) return
180+ if ( ! perms . dynamax ) {
181+ builder . whereRaw ( '0 = 1' )
182+ return
183+ }
157184
158- query . andWhere ( ( station ) => {
159- if ( hasStationedGmax || ! onlyGmaxStationed )
160- station . where ( ( battle ) => {
185+ builder . andWhere ( ( station ) => {
186+ let applied = false
187+
188+ if ( onlyMaxBattles ) {
189+ const hasBattleConditions =
190+ onlyBattleTier !== 'all' ||
191+ battleLevels . length > 0 ||
192+ battleCombos . length > 0
193+ if ( hasBattleConditions ) {
194+ const method = applied ? 'orWhere' : 'where'
195+ station [ method ] ( ( battle ) => {
196+ battle
197+ . whereNotNull ( 'battle_pokemon_id' )
198+ . andWhere ( 'battle_end' , '>' , ts )
161199 if ( onlyBattleTier === 'all' ) {
162- const battleBosses = new Set ( )
163- const battleForms = new Set ( )
164- const battleLevels = new Set ( )
165-
166- Object . keys ( args . filters ) . forEach ( ( key ) => {
167- switch ( key . charAt ( 0 ) ) {
168- case 'o' :
169- break
170- case 'j' :
171- battleLevels . add ( key . slice ( 1 ) )
172- break
173- default :
174- {
175- const [ id , form ] = key . split ( '-' )
176- if ( id ) battleBosses . add ( id )
177- if ( form ) battleForms . add ( form )
200+ battle . andWhere ( ( match ) => {
201+ let matchApplied = false
202+ if ( battleLevels . length ) {
203+ const levelMethod = matchApplied ? 'orWhereIn' : 'whereIn'
204+ match [ levelMethod ] ( 'battle_level' , battleLevels )
205+ matchApplied = true
206+ }
207+ battleCombos . forEach ( ( { pokemonId, form } ) => {
208+ const comboMethod = matchApplied ? 'orWhere' : 'where'
209+ match [ comboMethod ] ( ( combo ) => {
210+ combo . where ( 'battle_pokemon_id' , pokemonId )
211+ if ( form === null ) {
212+ combo . andWhereNull ( 'battle_pokemon_form' )
213+ } else {
214+ combo . andWhere ( 'battle_pokemon_form' , form )
178215 }
179- break
216+ } )
217+ matchApplied = true
218+ } )
219+ if ( ! matchApplied ) {
220+ match . whereRaw ( '0 = 1' )
180221 }
181222 } )
182- if ( battleBosses . size ) {
183- battle . andWhere ( 'battle_pokemon_id' , 'in' , [ ...battleBosses ] )
184- }
185- if ( battleForms . size ) {
186- battle . andWhere ( 'battle_pokemon_form' , 'in' , [ ...battleForms ] )
187- }
188- if ( battleLevels . size ) {
189- battle . andWhere ( 'battle_level' , 'in' , [ ...battleLevels ] )
190- }
191223 } else {
192224 battle . andWhere ( 'battle_level' , onlyBattleTier )
193225 }
194226 } )
195- if ( hasStationedGmax && onlyGmaxStationed )
196- station . orWhere ( 'total_stationed_gmax' , '>' , 0 )
227+ applied = true
228+ }
229+ }
230+
231+ if ( onlyGmaxStationed ) {
232+ if ( hasStationedGmax ) {
233+ const method = applied ? 'orWhere' : 'where'
234+ station [ method ] ( 'total_stationed_gmax' , '>' , 0 )
235+ applied = true
236+ } else {
237+ const method = applied ? 'orWhere' : 'where'
238+ station [ method ] ( ( gmax ) => {
239+ gmax . whereRaw (
240+ "JSON_SEARCH(COALESCE(stationed_pokemon, '[]'), 'one', ?, NULL, '$[*].bread_mode') IS NOT NULL" ,
241+ [ '2' ] ,
242+ )
243+ gmax . orWhereRaw (
244+ "JSON_SEARCH(COALESCE(stationed_pokemon, '[]'), 'one', ?, NULL, '$[*].bread_mode') IS NOT NULL" ,
245+ [ '3' ] ,
246+ )
247+ } )
248+ applied = true
249+ }
250+ }
251+
252+ if ( ! applied ) {
253+ station . whereRaw ( '0 = 1' )
254+ }
255+ } )
256+ }
257+
258+ query . select ( select )
259+
260+ if ( onlyInactiveStations ) {
261+ query . andWhere ( ( builder ) => {
262+ builder . where ( ( active ) => {
263+ active
264+ . where ( 'end_time' , '>' , ts )
265+ . andWhere ( 'updated' , '>' , activeCutoff )
266+ applyStationFilters ( active )
197267 } )
198- }
268+ // Battle data etc of inactive stations should be ignored since they are outdated by design
269+ builder . orWhere ( ( inactive ) =>
270+ inactive
271+ . where ( 'end_time' , '<=' , ts )
272+ . andWhere ( 'updated' , '>' , inactiveCutoff ) ,
273+ )
274+ } )
275+ } else {
276+ query . andWhere ( 'end_time' , '>' , ts ) . andWhere ( 'updated' , '>' , activeCutoff )
277+ applyStationFilters ( query )
199278 }
200279
201280 if ( ! getAreaSql ( query , areaRestrictions , onlyAreas , isMad ) ) {
202281 return [ ]
203282 }
204283
205- const stations = await query . select ( select )
284+ /** @type {import('@rm/types').FullStation[] } */
285+ const stations = await query
206286
207287 let pokemonData = null
208288 if ( hasBattlePokemonStats && perms . dynamax ) {
@@ -224,56 +304,33 @@ class Station extends Model {
224304 }
225305 }
226306
227- return stations
228- . map ( ( station ) => {
229- if ( station . is_battle_available && station . battle_pokemon_id === null ) {
230- station . is_battle_available = false
231- }
232- if ( station . total_stationed_pokemon === null ) {
233- station . total_stationed_pokemon = 0
234- }
235- if (
236- station . stationed_pokemon &&
237- ( station . total_stationed_gmax === undefined ||
238- station . total_stationed_gmax === null )
239- ) {
240- const list =
241- typeof station . stationed_pokemon === 'string'
242- ? JSON . parse ( station . stationed_pokemon )
243- : station . stationed_pokemon || [ ]
244- let count = 0
245- if ( list )
246- for ( let i = 0 ; i < list . length ; ++ i )
247- if ( list [ i ] . bread_mode === 2 || list [ i ] . bread_mode === 3 ) ++ count
248- station . total_stationed_gmax = count
249- }
250- station . battle_pokemon_estimated_cp = pokemonData
251- ? estimateStationCp ( pokemonData , station )
252- : null
253- return station
254- } )
255- . filter ( ( station ) => {
256- if ( Number . isFinite ( station . end_time ) && station . end_time <= ts ) {
257- return onlyInactiveStations
258- }
259-
260- const matchesBattleFilter =
261- onlyMaxBattles &&
262- ( onlyBattleTier === 'all'
263- ? args . filters [ `j${ station . battle_level } ` ] ||
264- args . filters [
265- `${ station . battle_pokemon_id } -${ station . battle_pokemon_form } `
266- ]
267- : onlyBattleTier === station . battle_level )
268-
269- const matchesGmaxFilter =
270- onlyGmaxStationed && station . total_stationed_gmax > 0
271-
272- return (
273- onlyAllStations ||
274- ( perms . dynamax && ( matchesBattleFilter || matchesGmaxFilter ) )
275- )
276- } )
307+ return stations . map ( ( station ) => {
308+ if ( station . is_battle_available && station . battle_pokemon_id === null ) {
309+ station . is_battle_available = false
310+ }
311+ if ( station . total_stationed_pokemon === null ) {
312+ station . total_stationed_pokemon = 0
313+ }
314+ if (
315+ station . stationed_pokemon &&
316+ ( station . total_stationed_gmax === undefined ||
317+ station . total_stationed_gmax === null )
318+ ) {
319+ const list =
320+ typeof station . stationed_pokemon === 'string'
321+ ? JSON . parse ( station . stationed_pokemon )
322+ : station . stationed_pokemon || [ ]
323+ let count = 0
324+ if ( list )
325+ for ( let i = 0 ; i < list . length ; ++ i )
326+ if ( list [ i ] . bread_mode === 2 || list [ i ] . bread_mode === 3 ) ++ count
327+ station . total_stationed_gmax = count
328+ }
329+ station . battle_pokemon_estimated_cp = pokemonData
330+ ? estimateStationCp ( pokemonData , station )
331+ : null
332+ return station
333+ } )
277334 }
278335
279336 /**
0 commit comments