@@ -3,6 +3,7 @@ import * as admin from 'firebase-admin';
33import { isDateJSON , isJSON } from 'lib/model/json' ;
44import clone from 'lib/utils/clone' ;
55import construct from 'lib/model/construct' ;
6+ import definedVals from 'lib/model/defined-vals' ;
67
78/**
89 * This is a painful workaround as we then import the entire Firebase library
@@ -18,31 +19,36 @@ type Timestamp = admin.firestore.Timestamp;
1819
1920/**
2021 * A timeslot is a window of time and provides all the necessary scheduling data
21- * for any scenario.
22+ * for any scenario (including support for complex rrules used server-side).
23+ * @typedef {Object } TimeslotInterface
2224 * @property id - A unique identifier for this timeslot (used as React keys and
2325 * thus only stored client-side as we have no use for this on our server).
24- * @property from - The start time and date of this timeslot (typically
25- * represented by a `Date`, `Timestamp`, or UTC date string).
26- * @property to - The end time and date of this timeslot (represented in the
27- * same format as the `from` property).
26+ * @property from - The start time of this particular timeslot instance.
27+ * @property to - The end time of this particular timeslot instance.
28+ * @property recur - The timeslot's recurrence rule (uses the iCal RFC string).
29+ * @property [last] - The timeslot's last possible end time. Undefined
30+ * client-side; only used server-side for querying recurring timeslots.
2831 */
29- export interface TimeslotBase < T > {
32+ export interface TimeslotInterface < T = Date > {
3033 id : string ;
3134 from : T ;
3235 to : T ;
36+ recur : string ;
37+ last ?: T ;
3338}
3439
35- export type TimeslotInterface = TimeslotBase < Date > ;
36- export type TimeslotFirestore = TimeslotBase < Timestamp > ;
37- export type TimeslotJSON = TimeslotBase < string > ;
38- export type TimeslotSearchHit = TimeslotBase < number > ;
40+ export type TimeslotFirestore = TimeslotInterface < Timestamp > ;
41+ export type TimeslotJSON = TimeslotInterface < string > ;
42+ export type TimeslotSearchHit = TimeslotInterface < number > ;
3943export type TimeslotSegment = { from : Date ; to : Date } ;
4044
4145export function isTimeslotJSON ( json : unknown ) : json is TimeslotJSON {
4246 if ( ! isJSON ( json ) ) return false ;
47+ if ( typeof json . id !== 'string' ) return false ;
4348 if ( ! isDateJSON ( json . from ) ) return false ;
4449 if ( ! isDateJSON ( json . to ) ) return false ;
45- if ( typeof json . id !== 'string' ) return false ;
50+ if ( typeof json . recur !== 'string' ) return false ;
51+ if ( json . last && ! isDateJSON ( json . last ) ) return false ;
4652 return true ;
4753}
4854
@@ -53,6 +59,11 @@ export class Timeslot implements TimeslotInterface {
5359
5460 public to : Date = new Date ( ) ;
5561
62+ // TODO: Should I prefill this with `RRULE:COUNT=1` for single instances?
63+ public recur = '' ;
64+
65+ public last ?: Date ;
66+
5667 /**
5768 * Constructor that takes advantage of Typescript's shorthand assignment.
5869 * @see {@link https://bit.ly/2XjNmB5 }
@@ -77,6 +88,7 @@ export class Timeslot implements TimeslotInterface {
7788 * 1. (Contained) Timeslot contains the given timeslot, OR;
7889 * 2. (Overlap Start) Timeslot contains the given timeslot's start time, OR;
7990 * 3. (Overlap End) Timeslot contains the given timeslot's end time.
91+ * @todo Why can't we use this in the calendar positioning logic?
8092 */
8193 public overlaps ( other : { from : Date ; to : Date } ) : boolean {
8294 return (
@@ -143,45 +155,59 @@ export class Timeslot implements TimeslotInterface {
143155 }
144156
145157 public toFirestore ( ) : TimeslotFirestore {
146- const { from, to, ...rest } = this ;
147- return {
158+ const { from, to, last , ...rest } = this ;
159+ return definedVals ( {
148160 ...rest ,
149161 from : ( from as unknown ) as Timestamp ,
150162 to : ( to as unknown ) as Timestamp ,
151- } ;
163+ last : last ? ( ( last as unknown ) as Timestamp ) : undefined ,
164+ } ) ;
152165 }
153166
154167 public static fromFirestore ( data : TimeslotFirestore ) : Timeslot {
155168 return new Timeslot ( {
156169 ...data ,
157170 from : data . from . toDate ( ) ,
158171 to : data . to . toDate ( ) ,
172+ last : data . last ?. toDate ( ) ,
159173 } ) ;
160174 }
161175
162176 public toJSON ( ) : TimeslotJSON {
163- const { from, to, ...rest } = this ;
164- return { ...rest , from : from . toJSON ( ) , to : to . toJSON ( ) } ;
177+ const { from, to, last, ...rest } = this ;
178+ return definedVals ( {
179+ ...rest ,
180+ from : from . toJSON ( ) ,
181+ to : to . toJSON ( ) ,
182+ last : last ?. toJSON ( ) ,
183+ } ) ;
165184 }
166185
167186 public static fromJSON ( json : TimeslotJSON ) : Timeslot {
168187 return new Timeslot ( {
169188 ...json ,
170189 from : new Date ( json . from ) ,
171190 to : new Date ( json . to ) ,
191+ last : json . last ? new Date ( json . last ) : undefined ,
172192 } ) ;
173193 }
174194
175195 public toSearchHit ( ) : TimeslotSearchHit {
176- const { from, to, ...rest } = this ;
177- return { ...rest , from : from . valueOf ( ) , to : to . valueOf ( ) } ;
196+ const { from, to, last, ...rest } = this ;
197+ return definedVals ( {
198+ ...rest ,
199+ from : from . valueOf ( ) ,
200+ to : to . valueOf ( ) ,
201+ last : last ?. valueOf ( ) ,
202+ } ) ;
178203 }
179204
180205 public static fromSearchHit ( hit : TimeslotSearchHit ) : Timeslot {
181206 return new Timeslot ( {
182207 ...hit ,
183208 from : new Date ( hit . from ) ,
184209 to : new Date ( hit . to ) ,
210+ last : hit . last ? new Date ( hit . last ) : undefined ,
185211 } ) ;
186212 }
187213
@@ -192,9 +218,13 @@ export class Timeslot implements TimeslotInterface {
192218 public static fromURLParam ( param : string ) : Timeslot {
193219 const params : URLSearchParams = new URLSearchParams ( param ) ;
194220 return new Timeslot ( {
221+ id : params . get ( 'id' ) || undefined ,
195222 from : new Date ( params . get ( 'from' ) as string ) ,
196223 to : new Date ( params . get ( 'to' ) as string ) ,
197- id : params . get ( 'id' ) || undefined ,
224+ recur : params . get ( 'recur' ) || undefined ,
225+ last : params . get ( 'last' )
226+ ? new Date ( params . get ( 'last' ) as string )
227+ : undefined ,
198228 } ) ;
199229 }
200230
0 commit comments