1
- import type { Actions } from '@sveltejs/kit' ;
2
1
import type { PageServerLoad } from './$types'
3
2
import { redirect } from '@sveltejs/kit' ;
4
3
import { type CampaignResult , prefixesToCampaignResults } from '$lib/utils/groupCampaigns' ;
5
4
import { findLeafPrefixes } from '$lib/server/s3' ;
6
5
import { logger } from '$lib/server/logger' ;
7
6
7
+ import { z } from 'zod' ;
8
+ import { fail } from '@sveltejs/kit' ;
9
+
10
+ // Define the validation schema with Zod
11
+ const filterSchema = z . object ( {
12
+ year : z . string ( )
13
+ . regex ( / ^ \d { 4 } $ / , { message : "Year must be a 4-digit number" } )
14
+ . optional ( )
15
+ . or ( z . literal ( '' ) ) , // Allow empty string
16
+
17
+ month : z . preprocess (
18
+ // If the input is an empty string, convert it to `undefined`.
19
+ // Otherwise, pass it through unchanged.
20
+ ( val ) => ( val === "" ? undefined : val ) ,
21
+ // Now, validate the processed value.
22
+ z . coerce . number ( { invalid_type_error : "Month must be a number" } )
23
+ . int ( )
24
+ . min ( 1 , { message : "Month must be between 1 and 12" } )
25
+ . max ( 12 , { message : "Month must be between 1 and 12" } )
26
+ . optional ( )
27
+ ) ,
28
+
29
+ day : z . preprocess (
30
+ // Same preprocessing for the day field.
31
+ ( val ) => ( val === "" ? undefined : val ) ,
32
+ // Validate the processed day value.
33
+ z . coerce . number ( { invalid_type_error : "Day must be a number" } )
34
+ . int ( )
35
+ . min ( 1 , { message : "Day must be between 1 and 31" } )
36
+ . max ( 31 , { message : "Day must be between 1 and 31" } )
37
+ . optional ( )
38
+ ) ,
39
+
40
+ number : z . string ( )
41
+ . regex ( / ^ \d { 1 , 2 } $ / , { message : "Number must be a 1 or 2-digit number" } )
42
+ . optional ( )
43
+ . or ( z . literal ( '' ) ) ,
44
+ } )
45
+ // Your dependency rules are still correct and important!
46
+ . superRefine ( ( data , ctx ) => {
47
+ if ( data . month && ! data . year ) {
48
+ ctx . addIssue ( {
49
+ code : z . ZodIssueCode . custom ,
50
+ path : [ 'year' ] ,
51
+ message : 'Year is required to specify a month' ,
52
+ } ) ;
53
+ }
54
+ if ( data . day && ! data . month ) {
55
+ ctx . addIssue ( {
56
+ code : z . ZodIssueCode . custom ,
57
+ path : [ 'month' ] ,
58
+ message : 'Month is required to specify a day' ,
59
+ } ) ;
60
+ }
61
+ if ( data . number && ! data . day ) {
62
+ ctx . addIssue ( {
63
+ code : z . ZodIssueCode . custom ,
64
+ path : [ 'day' ] ,
65
+ message : 'Day is required to specify a number' ,
66
+ } ) ;
67
+ }
68
+ } ) ;
69
+
8
70
export const load : PageServerLoad = async ( { locals, url } ) => {
9
71
try {
10
- const prefix = url . searchParams . get ( 'prefix' ) || 'batch/' ;
72
+ const year = url . searchParams . get ( 'year' ) || undefined ;
73
+ const rawMonth = url . searchParams . get ( 'month' ) || undefined ;
74
+ const rawDay = url . searchParams . get ( 'day' ) || undefined ;
75
+ const number = url . searchParams . get ( 'number' ) || undefined ;
76
+ const month = rawMonth ? rawMonth . padStart ( 2 , '0' ) : undefined ;
77
+ const day = rawDay ? rawDay . padStart ( 2 , '0' ) : undefined ;
78
+ const pathParts = [ 'batch' , year , month , day , number ] . filter ( part => part ) ;
79
+ const prefix = pathParts . join ( '/' ) ;
80
+ logger . info ( { prefix} , `Search campaigns with prefix ${ prefix } ` )
11
81
12
82
const { prefixes, count } = await findLeafPrefixes ( prefix , 5 ) ;
13
83
logger . info ( { prefixes, count} , `got campaign prefixes for start prefix ${ prefix } ` )
@@ -24,16 +94,35 @@ export const load: PageServerLoad = async ({ locals, url }) => {
24
94
}
25
95
} ;
26
96
27
- export const actions : Actions = {
28
- filter : async ( { request, url } ) => {
29
- const formData = await request . formData ( ) ;
30
- const prefix = formData . get ( 'prefix' ) || 'batch/' ;
97
+ export const actions = {
98
+ filter : async ( { request, url } ) => {
99
+ const formData = await request . formData ( ) ;
100
+ const data = Object . fromEntries ( formData ) ;
101
+
102
+ const result = filterSchema . safeParse ( data ) ;
103
+
104
+ if ( ! result . success ) {
105
+ // The `fail` function sends back a 400 status code
106
+ // and the validation errors, along with the original data.
107
+ return fail ( 400 , {
108
+ data : data ,
109
+ errors : result . error . flatten ( ) . fieldErrors ,
110
+ } ) ;
111
+ }
31
112
32
- // Create a URL object based on the current page's URL
33
- const targetUrl = new URL ( url . origin + url . pathname ) ;
34
- targetUrl . searchParams . set ( 'prefix' , prefix ) ;
113
+ logger . info ( 'Validation successful! Applying filter with:' , result . data ) ;
35
114
36
- // Use status 303 (See Other) for the redirect status code
115
+ const targetUrl = new URL ( url . origin + url . pathname )
116
+ logger . debug ( result . data , 'result.data' ) ;
117
+
118
+ for ( const [ key , value ] of Object . entries ( result . data ) ) {
119
+ if ( value ) {
120
+ targetUrl . searchParams . set ( key , value as string ) ;
121
+ }
122
+ }
123
+ logger . info ( `Redirecting to ${ targetUrl } ` ) ;
124
+
125
+ // Use status 303
37
126
throw redirect ( 303 , targetUrl ) ;
38
- }
127
+ }
39
128
} ;
0 commit comments