import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import { Subject } from 'rxjs/Subject'; import 'rxjs/add/operator/map'; import { Calendar, CalEvent , Booking} from './calendar'; import { ApiProvider } from '../api/api.provider'; import * as _ from 'lodash'; import * as moment from 'moment'; @Injectable() export class CalendarProvider { public _calendar$: Subject; public _calendars$: Subject; public _upcomingEvents$: Subject; public _currUpcomingEvent$: Subject; public dataStore: { calendar: Calendar , calendars: Calendar[] , upcomingEvents: CalEvent[] , start: moment.Moment , end: moment.Moment }; // @datastore.calendar always retains cached the lastest calendar requested with its full data // , including its events OR a dummy calendar if no calendar has been requested. // @datastore.calendars always retains cached an array of all available calendars fetched, but NOT their events, // copying API's design structure. Make sure to specifically retrieveCalendarDetails() using // a calendarId found @calendars and access its events via _calendar$ on the consumer. get calendar$() { return this._calendar$.asObservable(); } // Is shown as ".calendar$" attribute to consumers get calendars$() { return this._calendars$.asObservable(); } // Is shown as ".calendars$" attribute to consumers get upcomingEvents$() { return this._upcomingEvents$.asObservable(); } // Is shown as ".upcomingEvents$" attribute to consumers get currentUpcomingEvent$() { return this._currUpcomingEvent$.asObservable(); } constructor( public apiProvider: ApiProvider ) { this._calendar$ = > new Subject(); this._calendars$ = > new Subject(); this._upcomingEvents$ = > new Subject(); this._currUpcomingEvent$ = > new Subject(); let dummyCal: Calendar = Calendar.createDummyCalendar(); let dummyEvent: CalEvent = Calendar.createDummyEvent(); this.dataStore = { calendar: dummyCal , calendars: [dummyCal] , upcomingEvents: [dummyEvent] , start: undefined // used to cache most recent start and end timestamps of retrieveUpcomingEvents() , end: undefined }; } /* * Loads all calendars from API for the provided @tenantId and @storeId. * If no calendar has ever been created, then it creates one and returns a promise of that. * If there are already existing calendars, then it returns a promise of of the first one. */ initCalendar(tenantId: string, storeId: string): Promise { if (this.dataStore.calendar.getCalendarId() !== '__DUMMY__') { return new Promise((resolve, reject) => { resolve(this.dataStore.calendar); }); } else { return new Promise( (resolve, reject) => { this.retrieveCalendars(tenantId, storeId) .then((cals: Calendar[]) => { if (cals.length === 0) { // If there are no calendars for this tenantId & storeId in the DB, then create a new one this.createCalendar('DEFAULT_CALENDAR_NAME', tenantId, storeId) .then((cal: Calendar) => { resolve(cal); }); } else { // If retrieveCalendars() found existing calendars in the DB resolve to the first of the list this.retrieveCalendarDetails(_.find(cals, function(o){ return o.tenantId === tenantId}).getCalendarId(), tenantId, storeId) .then((cal: Calendar) => { resolve(cal); }); } }) .catch((error: number) => { console.error(`Error: initCalendar() failed with response: error ${error}`); reject(error); }); }); } } createCalendar(title: string, tenantId: string, storeId: string): Promise { let payload = { 'title' : title, 'tenantId' : tenantId, 'storeId' : storeId }; return new Promise( (resolve, reject) => { this.apiProvider.api('POST', '/v1/calendar', payload) .then((response: any) => { let createdCalendar: Calendar = Calendar.fromJson(response); resolve(createdCalendar); this.dataStore.calendar = createdCalendar; this._calendar$.next(this.dataStore.calendar); }) .catch((error: number) => { console.error(`Error: createCalendar() API request failed with response: error ${error}`); reject(error); }); }); } retrieveCalendarDetails(calendarId: string, tenantId: string, storeId: string): Promise { let payload = { 'tenantId' : tenantId , 'storeId' : storeId }; return new Promise( (resolve, reject) => { this.apiProvider.api('GET', `/v1/calendar/${calendarId}`, payload) .then((response: any) => { let retrievedCalendar: Calendar = Calendar.fromJson(response); resolve(retrievedCalendar); this.dataStore.calendar = retrievedCalendar; Calendar.updateOrPushCalendar(retrievedCalendar, this.dataStore.calendars); this._calendar$.next(this.dataStore.calendar); this._calendars$.next(this.dataStore.calendars); }) .catch((error: number) => { console.error(`Error: retrieveCalendarDetails() API request failed with response: error ${error}`); reject(error); }); }); } retrieveCalendars(tenantId: string, storeId: string): Promise { let payload = { 'tenantId' : tenantId , 'storeId' : storeId }; return new Promise( (resolve, reject) => { this.apiProvider.api('GET', '/v1/calendars', payload) .then((response: any[]) => { if (response.length > 0) { // Clear calendars array before pushing in fresh data from api response this.dataStore.calendars.splice(0); // Parse and copy data fetched from api into our cache _(response).forEach( (cal: any) => { Calendar.updateOrPushCalendar(Calendar.fromJson(cal), this.dataStore.calendars); }); resolve(this.dataStore.calendars); this._calendars$.next(this.dataStore.calendars); } }) .catch((error: number) => { if (error === 700) { resolve([]); } else { console.error(`Error: retrieveCalendars() API request failed with response: error ${error}, ${error}`); reject(error); } }); }); } updateCalendar(calendarId: string, tenantId: string, storeId: string, title: string): Promise { let payload = { 'title': title , 'tenantId': tenantId , 'storeId': storeId }; return new Promise( (resolve, reject) => { this.apiProvider.api('PUT', `/v1/calendar/${calendarId}`, payload) .then((response: any) => { let updatedCalendar: Calendar = Calendar.fromJson(response); resolve(updatedCalendar); this.dataStore.calendar = updatedCalendar; Calendar.updateOrPushCalendar(updatedCalendar, this.dataStore.calendars); this._calendar$.next(this.dataStore.calendar); this._calendars$.next(this.dataStore.calendars); }) .catch((error: number) => { console.error(`Error: updateCalendar() API request failed with: error ${error}, ${error}`); reject(error); }); }); } deleteCalendar(calendarId: string, tenantId: string, storeId: string, title: string): Promise { let payload = { 'title': title , 'tenantId': tenantId , 'storeId': storeId }; return new Promise( (resolve, reject) => { this.apiProvider.api('DELETE', `/v1/calendar/${calendarId}`, payload) .then((response: any) => { let deletedCalendar: Calendar = Calendar.fromJson(response); resolve(deletedCalendar); this.dataStore.calendar = Calendar.createDummyCalendar(); Calendar.deleteCalendar(deletedCalendar, this.dataStore.calendars); this._calendar$.next(this.dataStore.calendar); this._calendars$.next(this.dataStore.calendars); }) .catch((error: number) => { console.error(`Error: deleteCalendar() API request failed with: error ${error}`); reject(error); }); }); } createEvent( calendarId: string , tenantId: string , storeId: string , title: string , start: moment.Moment , end: moment.Moment , notes: string // , touchpointId?: string , booking: Booking ): Promise { let payload = { 'tenantId': tenantId , 'storeId': storeId , 'title': title , 'startDate': start , 'endDate': end , 'notes': notes , 'booking': booking }; // if(touchpointId){ // (payload as any).touchpointId = touchpointId; // } // SEND THE WHOLE BOOKING MAYBE??? // if(booking){ // (payload as any).booking = booking; // } return new Promise( (resolve, reject) => { this.apiProvider.api('POST', `/v1/calendar/${calendarId}`, payload) .then((response: any) => { let newCalEvent: CalEvent = Calendar.eventFromJson(response); resolve(newCalEvent); Calendar.updateOrPushEvent(newCalEvent, this.dataStore.calendar.getCalEvents()); this._calendar$.next(this.dataStore.calendar); if (this.dataStore.start !== undefined) { // If @newCalEvent's start timestamp is within the latest known time range [start, end] used to request upcomingEvents, // then add it in the datastore for upcoming events if ( (this.dataStore.start.diff(newCalEvent.start, 'seconds') <= 0) && (this.dataStore.end.diff(newCalEvent.start, 'seconds') >= 0) ) { Calendar.updateOrPushEvent(newCalEvent, this.dataStore.upcomingEvents); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(newCalEvent); } } }) .catch((error: number) => { console.error(`Error: createCalEvent() API request failed with response: error ${error}`); reject(error); }); }); } retrieveEvent(calEventId: string, calendarId: string): Promise { let payload = {}; return new Promise( (resolve, reject) => { this.apiProvider.api('GET', `/v1/calendar/${calendarId}/${calEventId}`, payload) .then((response: any) => { let retrievedCalEvent: CalEvent = Calendar.eventFromJson(response); resolve(retrievedCalEvent); Calendar.updateOrPushEvent(retrievedCalEvent, this.dataStore.calendar.getCalEvents()); this._calendar$.next(this.dataStore.calendar); if (this.dataStore.start !== undefined) { // If @retrievedCalEvent's start timestamp is within the latest known time range [start, end] used to request upcomingEvents, // then add it in the datastore for upcoming events if ( (this.dataStore.start.diff(retrievedCalEvent.start, 'seconds') <= 0) && (this.dataStore.end.diff(retrievedCalEvent.start, 'seconds') >= 0) ) { Calendar.updateOrPushEvent(retrievedCalEvent, this.dataStore.upcomingEvents); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(retrievedCalEvent); } } }) .catch((error: number) => { console.error(`Error: retrieveCalEvent() API request failed with: error ${error}, ${error}`); reject(error); }); }); } updateEvent( calEventId: string , calendarId: string , tenantId: string , storeId: string , title: string , start: moment.Moment , end: moment.Moment , notes: string // , touchpointId?: string , booking: Booking ): Promise { let payload = { 'tenantId': tenantId , 'storeId': storeId , 'title': title , 'startDate': start , 'endDate': end , 'notes': notes , 'booking': booking }; // if(touchpointId){ // (payload as any).touchpointId = touchpointId; // } // SEND THE WHOLE BOOKING MAYBE??? // if(booking){ // (payload as any).booking = booking; // } return new Promise( (resolve, reject) => { this.apiProvider.api('PUT', `/v1/calendar/${calendarId}/${calEventId}`, payload) .then((response: any) => { let updatedCalEvent: CalEvent = Calendar.eventFromJson(response); resolve(updatedCalEvent); Calendar.updateOrPushEvent(updatedCalEvent, this.dataStore.calendar.getCalEvents()); this._calendar$.next(this.dataStore.calendar); if (this.dataStore.start !== undefined) { // If @updatedCalEvent's start timestamp is within the latest known time range [start, end] used to request upcomingEvents, // then update it in the datastore for upcoming events if ( (this.dataStore.start.diff(updatedCalEvent.start, 'seconds') <= 0) && (this.dataStore.end.diff(updatedCalEvent.end, 'seconds') >= 0) ) { Calendar.updateOrPushEvent(updatedCalEvent, this.dataStore.upcomingEvents); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(updatedCalEvent); } else{ Calendar.deleteEvent(updatedCalEvent, this.dataStore.upcomingEvents); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(updatedCalEvent); } } }) .catch((error: number) => { console.error(`Error: updateCalEvent() API request failed with: error ${error}`); reject(error); }); }); } deleteEvent(calEventId: string, calendarId: string): Promise { let payload = {}; return new Promise( (resolve, reject) => { this.apiProvider.api('DELETE', `/v1/calendar/${calendarId}/${calEventId}`, payload) .then((response: any) => { let deletedCalEvent: CalEvent = Calendar.eventFromJson(response); resolve(deletedCalEvent); Calendar.deleteEvent(deletedCalEvent, this.dataStore.calendar.getCalEvents()); Calendar.deleteEvent(deletedCalEvent, this.dataStore.upcomingEvents); this._calendar$.next(this.dataStore.calendar); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(deletedCalEvent); }) .catch((error: number) => { console.error(`Error: deleteCalEvent() API request failed with response: error ${error}, ${error}`); reject(error); }); }); } retrieveUpcomingEvents( calendarId: string , start: moment.Moment , end: moment.Moment ): Promise { this.dataStore.start = start; this.dataStore.end = end; let payload = { 'startDate': start , 'endDate': end }; return new Promise( (resolve, reject) => { this.apiProvider.api('POST', `/v1/calendar/${calendarId}/upcoming`, payload) .then((response: any[]) => { if (response.length > 0) { // Clear calendars array before pushing in fresh data from api response this.dataStore.upcomingEvents.splice(0); // Parse data fetched from api let retrievedUpcomingCalEvents: CalEvent[] = []; _(response).forEach( (event: any) => { retrievedUpcomingCalEvents.push(Calendar.eventFromJson(event)); }); resolve(retrievedUpcomingCalEvents); // Copy data fetched from API into our 2 caches this.dataStore.upcomingEvents = retrievedUpcomingCalEvents; _(retrievedUpcomingCalEvents).forEach( (event: CalEvent) => { Calendar.updateOrPushEvent(Calendar.eventFromJson(event), this.dataStore.calendar.getCalEvents()); }); // Update subscibers this._calendar$.next(this.dataStore.calendar); this._upcomingEvents$.next(this.dataStore.upcomingEvents); } }) .catch((error: number) => { if (error === 700) { resolve([]); } else { console.error(`Error: retrieveUpcomingEvents() API request failed with response: error ${error}.`); reject(error); } }); }); } updateEventStatus( calEventId: string , calendarId: string , confirmStatus: boolean ): Promise { let payload = { 'confirmed' : String(confirmStatus) }; return new Promise( (resolve, reject) => { this.apiProvider.api('PUT', `/v1/calendar/${calendarId}/${calEventId}/confirm`, payload) .then((response: any[]) => { let updatedCalEvent: CalEvent = Calendar.eventFromJson(response); resolve(updatedCalEvent); Calendar.updateOrPushEvent(updatedCalEvent, this.dataStore.calendar.getCalEvents()); this._calendar$.next(this.dataStore.calendar); if (this.dataStore.start !== undefined) { // If @updatedCalEvent's start timestamp is within the latest known time range [start, end] used to request upcomingEvents, // then update it in the datastore for upcoming events if ( (this.dataStore.start.diff(updatedCalEvent.start, 'seconds') <= 0) && (this.dataStore.end.diff(updatedCalEvent.start, 'seconds') >= 0) ) { Calendar.updateOrPushEvent(updatedCalEvent, this.dataStore.upcomingEvents); this._upcomingEvents$.next(this.dataStore.upcomingEvents); this._currUpcomingEvent$.next(updatedCalEvent); } } }) .catch((error: number) => { console.error(`Error: updateEventStatus() API request failed with response: error ${error}, ${error}`); reject(error); }); }); } }