1
- import { z } from 'zod' ;
1
+ import {
2
+ ScheduleSchema ,
3
+ AvailabilitySchema ,
4
+ WarningsSchema ,
5
+ TalkSchema ,
6
+ Talk ,
7
+ Schedule ,
8
+ Availability ,
9
+ Warnings
10
+ } from './schemas' ;
2
11
3
12
const basePath = process . env . BASE_PATH || '/talk' ;
4
13
5
- const SpeakerSchema = z . object ( {
6
- code : z . string ( ) ,
7
- name : z . string ( ) ,
8
- } ) ;
9
-
10
- // Title can be a string or a language object like {en: "Title"}
11
- const toTitleRecord = ( val : unknown ) : Record < string , string > => {
12
- if ( val !== null && typeof val === 'object' ) {
13
- return val as Record < string , string > ;
14
- }
15
- if ( typeof val === 'string' ) {
16
- return { en : val } ;
14
+ const calculateDuration = ( start ?: string , end ?: string ) : number | undefined => {
15
+ if ( ! start || ! end ) return undefined ;
16
+ try {
17
+ const startTime = new Date ( start ) . getTime ( ) ;
18
+ const endTime = new Date ( end ) . getTime ( ) ;
19
+ return ( endTime - startTime ) / ( 1000 * 60 ) ;
20
+ } catch {
21
+ return undefined ;
17
22
}
18
- return { en : '' } ;
19
23
} ;
20
24
21
- // Room schema
22
- const RoomSchema = z . object ( {
23
- id : z . number ( ) ,
24
- name : z . record ( z . string ( ) , z . string ( ) ) . default ( { } ) ,
25
- description : z . record ( z . string ( ) , z . string ( ) ) . default ( { } )
26
- } ) ;
27
-
28
- const TrackSchema = z . object ( {
29
- id : z . number ( ) ,
30
- name : z . record ( z . string ( ) , z . string ( ) ) . default ( { } )
31
- } ) ;
32
-
33
- // Talk schema
34
- const TalkSchema = z . object ( {
35
- id : z . number ( ) ,
36
- // Some talks have code, some don't
37
- code : z . string ( ) . optional ( ) ,
38
- title : z . union ( [
39
- z . string ( ) ,
40
- z . record ( z . string ( ) , z . string ( ) )
41
- ] ) . transform ( toTitleRecord ) ,
42
- abstract : z . string ( ) . optional ( ) ,
43
- speakers : z . array ( z . string ( ) ) . optional ( ) . default ( [ ] ) ,
44
- // Room and track come as numbers in the API
45
- room : z . union ( [
46
- z . number ( ) ,
47
- z . string ( ) . transform ( val => parseInt ( val , 10 ) || null )
48
- ] ) . nullable ( ) . optional ( ) ,
49
- track : z . union ( [
50
- z . number ( ) ,
51
- z . string ( ) . transform ( val => parseInt ( val , 10 ) || null )
52
- ] ) . nullable ( ) . optional ( ) ,
53
-
54
- start : z . string ( ) . nullable ( ) . optional ( ) ,
55
- end : z . string ( ) . nullable ( ) . optional ( ) ,
56
- state : z . string ( ) . optional ( ) ,
57
- updated : z . string ( ) . optional ( ) ,
58
- uncreated : z . boolean ( ) . optional ( ) ,
59
- availabilities : z . array ( z . unknown ( ) ) . optional ( ) . default ( [ ] ) ,
60
- // Duration field - it appears some talks don't have this field
61
- duration : z . number ( ) . optional ( )
62
- } ) ;
63
-
64
- // Main schedule schema
65
- const ScheduleSchema = z . object ( {
66
- version : z . nullable ( z . string ( ) . nullable ( ) ) ,
67
- event_start : z . string ( ) ,
68
- event_end : z . string ( ) ,
69
- timezone : z . string ( ) ,
70
- locales : z . array ( z . string ( ) ) . default ( [ ] ) ,
71
- rooms : z . array ( RoomSchema ) . default ( [ ] ) ,
72
- tracks : z . array ( TrackSchema ) . default ( [ ] ) ,
73
- speakers : z . array ( SpeakerSchema ) . default ( [ ] ) ,
74
- talks : z . array ( TalkSchema ) . default ( [ ] ) ,
75
- now : z . string ( ) . optional ( ) ,
76
- warnings : z . record ( z . string ( ) , z . any ( ) ) . optional ( ) . default ( { } )
77
- } ) ;
78
-
79
- const AvailabilitySchema = z . object ( {
80
- rooms : z . record ( z . string ( ) , z . array ( z . object ( {
81
- start : z . string ( ) ,
82
- end : z . string ( )
83
- } ) ) ) . optional ( ) ,
84
- talks : z . record ( z . string ( ) , z . array ( z . object ( {
85
- start : z . string ( ) ,
86
- end : z . string ( )
87
- } ) ) ) . optional ( ) ,
88
- } ) ;
89
-
90
- const WarningSchema = z . object ( {
91
- message : z . string ( ) ,
92
- } ) ;
93
-
94
- const WarningsSchema = z . record ( z . string ( ) , z . array ( WarningSchema ) ) . optional ( ) ;
95
25
interface TalkPayload {
96
26
id ?: number ;
97
27
code ?: string ;
@@ -103,30 +33,15 @@ interface TalkPayload {
103
33
duration ?: number ;
104
34
}
105
35
106
- // Helper to calculate duration in minutes from start and end time strings
107
- const calculateDuration = ( start ?: string , end ?: string ) : number | undefined => {
108
- if ( ! start || ! end ) return undefined ;
109
-
110
- try {
111
- const startTime = new Date ( start ) . getTime ( ) ;
112
- const endTime = new Date ( end ) . getTime ( ) ;
113
- return ( endTime - startTime ) / ( 1000 * 60 ) ; // Duration in minutes
114
- } catch ( e ) {
115
- return undefined ;
116
- }
117
- } ;
118
-
119
36
const api = {
120
37
eventSlug : basePath ? window . location . pathname . split ( "/" ) [ 4 ] : window . location . pathname . split ( "/" ) [ 3 ] ,
121
38
122
39
async http < T > ( verb : string , url : string , body : unknown ) : Promise < T > {
123
40
const headers : Record < string , string > = { } ;
124
- if ( body ) {
125
- headers [ 'Content-Type' ] = 'application/json' ;
126
- }
41
+ if ( body ) headers [ 'Content-Type' ] = 'application/json' ;
127
42
128
43
const options : RequestInit = {
129
- method : verb || 'GET' ,
44
+ method : verb ,
130
45
headers,
131
46
body : body ? JSON . stringify ( body ) : undefined ,
132
47
credentials : 'include' ,
@@ -147,68 +62,54 @@ const api = {
147
62
return json as T ;
148
63
} ,
149
64
150
- async fetchTalks ( options ?: { since ?: string ; warnings ?: boolean } ) : Promise < z . infer < typeof ScheduleSchema > > {
65
+ async fetchTalks ( options ?: { since ?: string ; warnings ?: boolean } ) : Promise < Schedule > {
151
66
let url = `${ basePath } /orga/event/${ this . eventSlug } /schedule/api/talks/` ;
152
-
153
- // Build query parameters
154
- const params = new URLSearchParams ( ) ;
155
- if ( window . location . search ) {
156
- new URLSearchParams ( window . location . search ) . forEach ( ( value , key ) => {
157
- params . append ( key , value ) ;
158
- } ) ;
159
- }
67
+ const params = new URLSearchParams ( window . location . search ) ;
160
68
if ( options ?. since ) params . append ( 'since' , options . since ) ;
161
69
if ( options ?. warnings ) params . append ( 'warnings' , 'true' ) ;
162
-
163
70
url += `?${ params . toString ( ) } ` ;
164
71
165
- const data = await this . http ( 'GET' , url , null ) ;
72
+ const data = await this . http < unknown > ( 'GET' , url , null ) ;
166
73
return ScheduleSchema . parse ( data ) ;
167
74
} ,
168
75
169
- async fetchAvailabilities ( ) : Promise < z . infer < typeof AvailabilitySchema > > {
76
+ async fetchAvailabilities ( ) : Promise < Availability > {
170
77
const url = `${ basePath } /orga/event/${ this . eventSlug } /schedule/api/availabilities/` ;
171
- const data = await this . http ( 'GET' , url , null ) ;
78
+ const data = await this . http < unknown > ( 'GET' , url , null ) ;
172
79
return AvailabilitySchema . parse ( data ) ;
173
80
} ,
174
81
175
- async fetchWarnings ( ) : Promise < z . infer < typeof WarningsSchema > > {
82
+ async fetchWarnings ( ) : Promise < Warnings > {
176
83
const url = `${ basePath } /orga/event/${ this . eventSlug } /schedule/api/warnings/` ;
177
- const data = await this . http ( 'GET' , url , null ) ;
84
+ const data = await this . http < unknown > ( 'GET' , url , null ) ;
178
85
return WarningsSchema . parse ( data ) ;
179
86
} ,
180
87
181
88
async saveTalk (
182
89
talk : TalkPayload ,
183
90
{ action = 'PATCH' } : { action ?: string } = { }
184
- ) : Promise < z . infer < typeof TalkSchema > | void > {
91
+ ) : Promise < Talk | void > {
185
92
const url = new URL ( window . location . href ) ;
186
93
url . pathname = `${ url . pathname } api/talks/${ talk . id ? `${ talk . id } /` : '' } ` ;
187
94
url . search = window . location . search ;
188
95
189
96
let payload : unknown = undefined ;
190
97
if ( action !== 'DELETE' ) {
191
- const roomId = talk . room && typeof talk . room === 'object'
192
- ? talk . room . id
193
- : talk . room ;
194
-
195
- // Calculate duration if not provided but we have start and end times
98
+ const roomId = typeof talk . room === 'object' ? talk . room . id : talk . room ;
196
99
const duration = talk . duration ?? calculateDuration ( talk . start , talk . end ) ;
197
100
198
101
payload = {
199
102
room : roomId ,
200
103
start : talk . start ,
201
104
end : talk . end ,
202
- duration : duration ,
105
+ duration,
203
106
title : talk . title ,
204
107
description : talk . description ,
205
108
} ;
206
109
}
207
110
208
- // Make request
209
111
const response = await this . http < unknown > ( action , url . toString ( ) , payload ) ;
210
112
211
- // Validate and return response
212
113
if ( action !== 'DELETE' ) {
213
114
return TalkSchema . parse ( response ) ;
214
115
}
@@ -218,10 +119,13 @@ const api = {
218
119
await this . saveTalk ( { id : talk . id } , { action : 'DELETE' } ) ;
219
120
} ,
220
121
221
- async createTalk ( talk : Omit < TalkPayload , 'id' > ) : Promise < z . infer < typeof TalkSchema > > {
222
- const response = await this . saveTalk ( talk , { action : 'POST' } ) as z . infer < typeof TalkSchema > ;
223
- return TalkSchema . parse ( response ) ;
122
+ async createTalk ( talk : Omit < TalkPayload , 'id' > ) : Promise < Talk > {
123
+ const response = await this . saveTalk ( talk , { action : 'POST' } ) ;
124
+ if ( ! response ) {
125
+ throw new Error ( 'Failed to create talk: No response from server' ) ;
126
+ }
127
+ return response ;
224
128
}
225
129
} ;
226
130
227
- export default api
131
+ export default api
0 commit comments