Skip to content

Commit 4a1f209

Browse files
committed
feat/qg-253: WIP: добавлена обработка ошибки получения событий Google-календаря
1 parent a967099 commit 4a1f209

File tree

26 files changed

+226
-67
lines changed

26 files changed

+226
-67
lines changed

app/src/main/kotlin/pro/azhidkov/platform/kotlin/ResultExt.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ inline fun <R : Any, reified T : Any?> Result<T>.mapNull(transform: () -> R): Re
3939
inline fun <reified T : Throwable, V : R, R> Result<V>.recoverFailure(block: (T) -> R): Result<R> =
4040
if (this.exceptionOrNull() is T) success(block(this.exceptionOrNull() as T)) else this
4141

42+
inline fun <reified T : Throwable, V : R, R> Result<V>.tryRecover(block: (T) -> Result<R>): Result<R> =
43+
if (this.exceptionOrNull() is T) block(this.exceptionOrNull() as T) else this
44+
4245
inline fun <reified T : Throwable, R> Result<R>.mapFailure(block: (T) -> Throwable): Result<R> =
4346
if (this.exceptionOrNull() is T) failure(block(this.exceptionOrNull() as T)) else this
4447

app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/forms/CreateAppointmentForm.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package pro.qyoga.app.therapist.appointments.core.edit.forms
22

3-
import pro.qyoga.app.therapist.appointments.core.edit.view_model.toQueryParamStr
43
import pro.qyoga.core.appointments.core.model.AppointmentStatus
54
import pro.qyoga.core.appointments.types.model.AppointmentTypeRef
65
import pro.qyoga.core.calendar.api.CalendarItem
7-
import pro.qyoga.core.calendar.ical.model.ICalEventId
6+
import pro.qyoga.core.calendar.api.CalendarItemId
87
import pro.qyoga.core.clients.cards.model.ClientRef
98
import pro.qyoga.core.therapy.therapeutic_tasks.model.TherapeuticTaskRef
109
import java.time.Duration
@@ -33,7 +32,7 @@ data class CreateAppointmentForm(
3332
) {
3433

3534
constructor(
36-
iCalEvent: CalendarItem<ICalEventId, ZonedDateTime>?,
35+
iCalEvent: CalendarItem<out CalendarItemId, ZonedDateTime>?,
3736
dateTime: LocalDateTime?,
3837
timeZone: ZoneId,
3938
timeZoneTitle: String?

app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/ops/GetAppointmentPrefillDataOp.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,12 @@ import org.springframework.stereotype.Component
44
import pro.azhidkov.timezones.TimeZones
55
import pro.qyoga.app.therapist.appointments.core.edit.forms.CreateAppointmentForm
66
import pro.qyoga.app.therapist.appointments.core.edit.view_model.SourceItem
7+
import pro.qyoga.app.therapist.appointments.core.edit.view_model.googleEventId
78
import pro.qyoga.app.therapist.appointments.core.edit.view_model.icsEventId
9+
import pro.qyoga.core.calendar.google.GoogleCalendar
10+
import pro.qyoga.core.calendar.google.GoogleCalendarsService
811
import pro.qyoga.core.calendar.ical.ICalCalendarsRepo
12+
import pro.qyoga.core.calendar.ical.model.ICalCalendar
913
import pro.qyoga.core.users.auth.model.UserRef
1014
import pro.qyoga.core.users.settings.UserSettingsRepo
1115
import pro.qyoga.core.users.therapists.TherapistRef
@@ -15,6 +19,7 @@ import java.time.LocalDateTime
1519
@Component
1620
class GetAppointmentPrefillDataOp(
1721
private val iCalCalendarsRepo: ICalCalendarsRepo,
22+
private val googleCalendarsService: GoogleCalendarsService,
1823
private val userSettingsRepo: UserSettingsRepo,
1924
private val timeZones: TimeZones,
2025
) : (TherapistRef, SourceItem?, LocalDateTime?) -> CreateAppointmentForm {
@@ -26,14 +31,22 @@ class GetAppointmentPrefillDataOp(
2631
): CreateAppointmentForm {
2732
val currentUserTimeZone = userSettingsRepo.getUserTimeZone(UserRef(therapistRef))
2833

29-
val iCalEvent = sourceItem?.icsEventId()
30-
?.let { iCalCalendarsRepo.findById(therapistRef, it) }
34+
val sourceEvent = when (sourceItem?.type) {
35+
ICalCalendar.TYPE ->
36+
iCalCalendarsRepo.findById(therapistRef, sourceItem.icsEventId())
3137

32-
val timeZone = iCalEvent?.dateTime?.zone
38+
GoogleCalendar.TYPE ->
39+
googleCalendarsService.findById(therapistRef, sourceItem.googleEventId())
40+
41+
else ->
42+
null
43+
}
44+
45+
val timeZone = sourceEvent?.dateTime?.zone
3346
?: currentUserTimeZone
3447
val timeZoneTitle = timeZones.findById(timeZone)?.displayName
3548

36-
return CreateAppointmentForm(iCalEvent, dateTime, timeZone, timeZoneTitle)
49+
return CreateAppointmentForm(sourceEvent, dateTime, timeZone, timeZoneTitle)
3750
}
3851

3952
}

app/src/main/kotlin/pro/qyoga/app/therapist/appointments/core/edit/view_model/SourceItem.kt

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pro.qyoga.app.therapist.appointments.core.edit.view_model
22

3+
import pro.qyoga.core.calendar.google.GoogleCalendar
34
import pro.qyoga.core.calendar.google.GoogleCalendarItemId
45
import pro.qyoga.core.calendar.ical.model.ICalCalendar
56
import pro.qyoga.core.calendar.ical.model.ICalEventId
@@ -14,20 +15,24 @@ data class SourceItem(
1415
SourceItem(ICalCalendar.TYPE, eventId.toQueryParamStr())
1516

1617
fun googleEvent(eventId: GoogleCalendarItemId): SourceItem =
17-
SourceItem("Google", eventId.value)
18+
SourceItem("Google", eventId.toQueryParamStr())
1819

1920
}
2021

2122
}
2223

23-
fun ICalEventId.toQueryParamStr(): String =
24-
"uid=${uid},rid=${recurrenceId ?: ""}"
25-
2624
fun SourceItem.icsEventId(): ICalEventId {
2725
check(type == ICalCalendar.TYPE)
2826
val matcher = "uid=(.+),rid=(.*)".toRegex().matchEntire(id)
2927
check(matcher != null)
3028
val uid = matcher.groups[1]!!.value
3129
val rid = matcher.groups[2]!!.value.takeIf { it.isNotBlank() }
3230
return ICalEventId(uid, rid)
31+
}
32+
33+
fun SourceItem.googleEventId(): GoogleCalendarItemId {
34+
check(type == GoogleCalendar.TYPE)
35+
val matcher = "(.+),(.+)".toRegex().matchEntire(id)
36+
check(matcher != null)
37+
return GoogleCalendarItemId(matcher.groups[1]!!.value, matcher.groups[2]!!.value)
3338
}

app/src/main/kotlin/pro/qyoga/core/appointments/core/AppointmentsRepo.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AppointmentsRepo(
3535
Appointment::class,
3636
jdbcConverter,
3737
relationalMappingContext
38-
), CalendarsService {
38+
), CalendarsService<UUID> {
3939

4040
override fun findCalendarItemsInInterval(
4141
therapist: TherapistRef,
@@ -73,6 +73,13 @@ class AppointmentsRepo(
7373
return findAll(query, params, localizedAppointmentSummaryRowMapper)
7474
}
7575

76+
override fun findById(
77+
therapistRef: TherapistRef,
78+
eventId: UUID
79+
): CalendarItem<UUID, ZonedDateTime>? {
80+
TODO()
81+
}
82+
7683
}
7784

7885
fun AppointmentsRepo.findIntersectingAppointment(

app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarItem.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@ package pro.qyoga.core.calendar.api
33
import java.time.Duration
44
import java.time.temporal.Temporal
55

6+
interface CalendarItemId {
7+
8+
fun toQueryParamStr(): String
9+
10+
}
11+
612
interface CalendarItem<ID, DATE : Temporal> {
713
val id: ID
814
val title: String

app/src/main/kotlin/pro/qyoga/core/calendar/api/CalendarsService.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import java.time.LocalDateTime
66
import java.time.ZonedDateTime
77

88

9-
interface CalendarsService {
9+
interface CalendarsService<ID> {
1010

1111
fun findCalendarItemsInInterval(
1212
therapist: TherapistRef,
1313
interval: Interval<ZonedDateTime>
1414
): Iterable<CalendarItem<*, LocalDateTime>>
1515

16+
fun findById(therapistRef: TherapistRef, eventId: ID): CalendarItem<ID, ZonedDateTime>?
17+
1618
}

app/src/main/kotlin/pro/qyoga/core/calendar/google/GoogleCalendar.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ data class GoogleCalendar(
1010
override val name: String,
1111
) : Calendar {
1212

13-
override val type: String = "Google"
13+
override val type: String = TYPE
14+
15+
companion object {
16+
17+
const val TYPE = "Google"
18+
}
1419

1520
}

app/src/main/kotlin/pro/qyoga/core/calendar/google/GoogleCalendarItem.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,20 @@ package pro.qyoga.core.calendar.google
22

33
import pro.azhidkov.platform.java.time.toLocalDateTime
44
import pro.qyoga.core.calendar.api.CalendarItem
5+
import pro.qyoga.core.calendar.api.CalendarItemId
56
import java.time.Duration
67
import java.time.ZoneId
78
import java.time.temporal.Temporal
89

9-
@JvmInline
10-
value class GoogleCalendarItemId(val value: String)
10+
data class GoogleCalendarItemId(
11+
val calendarId: String,
12+
val itemId: String
13+
) : CalendarItemId {
14+
15+
override fun toQueryParamStr(): String =
16+
"$calendarId,$itemId"
17+
18+
}
1119

1220
data class GoogleCalendarItem<DATE : Temporal>(
1321
override val id: GoogleCalendarItemId,

app/src/main/kotlin/pro/qyoga/core/calendar/google/GoogleCalendarsClient.kt

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package pro.qyoga.core.calendar.google
22

3+
import com.google.api.client.googleapis.json.GoogleJsonResponseException
34
import com.google.api.client.util.DateTime
45
import com.google.api.services.calendar.Calendar
56
import com.google.api.services.calendar.model.Event
@@ -9,9 +10,12 @@ import org.slf4j.LoggerFactory
910
import org.springframework.beans.factory.annotation.Value
1011
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties
1112
import org.springframework.cache.annotation.Cacheable
13+
import org.springframework.http.HttpStatus
1214
import org.springframework.stereotype.Component
1315
import pro.azhidkov.platform.java.time.Interval
1416
import pro.azhidkov.platform.kotlin.tryExecute
17+
import pro.azhidkov.platform.kotlin.tryRecover
18+
import pro.qyoga.core.calendar.api.CalendarItem
1519
import pro.qyoga.core.users.therapists.TherapistRef
1620
import java.net.URI
1721
import java.time.Duration
@@ -54,29 +58,10 @@ class GoogleCalendarsClient(
5458
.setSingleEvents(true)
5559
.execute()
5660
.items
57-
.map {
58-
GoogleCalendarItem(
59-
GoogleCalendarItemId(it.id),
60-
it.summary,
61-
it.description ?: "",
62-
startDate(it),
63-
duration(it),
64-
it.location
65-
)
66-
}
61+
.map { mapToCalendarItem(calendarSettings.calendarId, it) }
6762
return events
6863
}
6964

70-
private fun startDate(event: Event): ZonedDateTime =
71-
ZonedDateTime.ofInstant(
72-
Instant.ofEpochMilli(event.start.dateTime?.value ?: event.start.date?.value ?: 0),
73-
ZoneId.of(event.start.timeZone)
74-
)
75-
76-
private fun duration(event: Event): Duration =
77-
Duration.ofMillis(event.end.dateTime?.value ?: event.end.date?.value ?: 0) -
78-
Duration.ofMillis(event.start.dateTime?.value ?: event.start.date?.value ?: 0)
79-
8065
@Cacheable(
8166
cacheNames = [GoogleCalendarConf.CacheNames.GOOGLE_ACCOUNT_CALENDARS],
8267
key = "#therapist.id + ':' + #account.id"
@@ -100,6 +85,31 @@ class GoogleCalendarsClient(
10085
return success(calendarsList)
10186
}
10287

88+
@Cacheable(
89+
cacheNames = [GoogleCalendarConf.CacheNames.CALENDAR_EVENTS],
90+
key = "#eventId"
91+
)
92+
fun findById(
93+
account: GoogleAccount,
94+
eventId: GoogleCalendarItemId
95+
): CalendarItem<GoogleCalendarItemId, ZonedDateTime>? {
96+
val service = servicesCache.getValue(account)
97+
98+
val getEventRequest = service.events().get(eventId.calendarId, eventId.itemId)
99+
100+
val event = tryExecute { getEventRequest.execute() }
101+
.tryRecover<GoogleJsonResponseException, _, _> {
102+
if (it.statusCode == HttpStatus.NOT_FOUND.value()) {
103+
success(null)
104+
} else {
105+
failure(it)
106+
}
107+
}
108+
.getOrThrow()
109+
110+
return event?.let { mapToCalendarItem(eventId.calendarId, it) }
111+
}
112+
103113
private fun createCalendarService(account: GoogleAccount): Calendar {
104114
val credentials = UserCredentials.newBuilder()
105115
.setClientId(googleOAuthProps.registration["google"]!!.clientId)
@@ -115,4 +125,23 @@ class GoogleCalendarsClient(
115125
return service
116126
}
117127

118-
}
128+
}
129+
130+
private fun mapToCalendarItem(calendarId: String, event: Event): GoogleCalendarItem<ZonedDateTime> = GoogleCalendarItem(
131+
GoogleCalendarItemId(calendarId, event.id),
132+
event.summary,
133+
event.description ?: "",
134+
startDate(event),
135+
duration(event),
136+
event.location
137+
)
138+
139+
private fun startDate(event: Event): ZonedDateTime =
140+
ZonedDateTime.ofInstant(
141+
Instant.ofEpochMilli(event.start.dateTime?.value ?: event.start.date?.value ?: 0),
142+
ZoneId.of(event.start.timeZone)
143+
)
144+
145+
private fun duration(event: Event): Duration =
146+
Duration.ofMillis(event.end.dateTime?.value ?: event.end.date?.value ?: 0) -
147+
Duration.ofMillis(event.start.dateTime?.value ?: event.start.date?.value ?: 0)

0 commit comments

Comments
 (0)