From f935f71d0ddd716d98382c21b75c06c108ccba3d Mon Sep 17 00:00:00 2001 From: Eugene Zadyra Date: Thu, 7 Jun 2018 12:40:49 +0200 Subject: [PATCH] Multiple account support --- .../scala/com/lbs/server/BootConfig.scala | 6 +- .../scala/com/lbs/server/actor/Account.scala | 103 +++++++++++++ .../scala/com/lbs/server/actor/Auth.scala | 16 +- .../scala/com/lbs/server/actor/Book.scala | 26 ++-- .../scala/com/lbs/server/actor/Chat.scala | 22 ++- .../scala/com/lbs/server/actor/History.scala | 2 +- .../scala/com/lbs/server/actor/Login.scala | 11 +- .../com/lbs/server/actor/Monitorings.scala | 2 +- .../scala/com/lbs/server/actor/Router.scala | 23 ++- .../scala/com/lbs/server/actor/Visits.scala | 4 +- .../main/scala/com/lbs/server/lang/En.scala | 53 ++++--- .../main/scala/com/lbs/server/lang/Lang.scala | 52 ++++--- .../main/scala/com/lbs/server/lang/Ua.scala | 53 ++++--- .../server/repository/DataRepository.scala | 140 ++++++++++------- .../lbs/server/repository/model/Account.scala | 31 ++++ .../server/repository/model/CityHistory.scala | 8 +- .../repository/model/ClinicHistory.scala | 8 +- .../server/repository/model/Credentials.scala | 54 +++---- .../repository/model/DoctorHistory.scala | 8 +- .../server/repository/model/Monitoring.scala | 50 ++++--- .../repository/model/ServiceHistory.scala | 8 +- .../server/repository/model/SystemUser.scala | 62 ++++---- .../com/lbs/server/service/ApiService.scala | 52 +++---- .../com/lbs/server/service/DataService.scala | 141 ++++++++++++------ .../server/service/MonitoringService.scala | 23 +-- .../lbs/server/service/SessionSupport.scala | 22 +-- .../scala/com/lbs/server/util/package.scala | 45 +++--- .../scala/com/lbs/server/actor/AuthSpec.scala | 8 +- 28 files changed, 665 insertions(+), 368 deletions(-) create mode 100644 server/src/main/scala/com/lbs/server/actor/Account.scala create mode 100644 server/src/main/scala/com/lbs/server/repository/model/Account.scala diff --git a/server/src/main/scala/com/lbs/server/BootConfig.scala b/server/src/main/scala/com/lbs/server/BootConfig.scala index 6ee1c65..71edf75 100644 --- a/server/src/main/scala/com/lbs/server/BootConfig.scala +++ b/server/src/main/scala/com/lbs/server/BootConfig.scala @@ -102,10 +102,14 @@ class BootConfig { def settingsActorFactory: ByUserIdActorFactory = userId => actorSystem.actorOf(Settings.props(userId, bot, dataService, localization)) + @Bean + def accountActorFactory: ByUserIdActorFactory = + userId => actorSystem.actorOf(Account.props(userId, bot, dataService, localization, router)) + @Bean def chatActorFactory: ByUserIdActorFactory = userId => actorSystem.actorOf(Chat.props(userId, dataService, monitoringService, bookingActorFactory, helpActorFactory, - monitoringsActorFactory, historyActorFactory, visitsActorFactory, settingsActorFactory, bugActorFactory)) + monitoringsActorFactory, historyActorFactory, visitsActorFactory, settingsActorFactory, bugActorFactory, accountActorFactory)) @Bean def datePickerFactory: ByUserIdWithOriginatorActorFactory = (userId, originator) => diff --git a/server/src/main/scala/com/lbs/server/actor/Account.scala b/server/src/main/scala/com/lbs/server/actor/Account.scala new file mode 100644 index 0000000..9f9e312 --- /dev/null +++ b/server/src/main/scala/com/lbs/server/actor/Account.scala @@ -0,0 +1,103 @@ +/** + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lbs.server.actor + +import akka.actor.{ActorRef, Props} +import com.lbs.bot.model.{Button, Command} +import com.lbs.bot.{Bot, _} +import com.lbs.server.actor.Account._ +import com.lbs.server.actor.Chat.Init +import com.lbs.server.actor.Login._ +import com.lbs.server.lang.{Localizable, Localization} +import com.lbs.server.service.DataService + +class Account(val userId: UserId, bot: Bot, dataService: DataService, val localization: Localization, router: ActorRef) extends SafeFSM[FSMState, FSMData] with Localizable { + + startWith(AskAction, null) + + whenSafe(AskAction) { + case Event(Next, _) => + val credentials = dataService.getUserCredentials(userId.userId) + val buttons = Seq(Button(lang.addAccount, -1L), Button(lang.deleteAccount, -2L)) ++ credentials.map(c => Button(s"🔐️ ${c.username}", c.accountId)) + bot.sendMessage(userId.source, lang.pleaseChooseAccount, inlineKeyboard = createInlineKeyboard(buttons, columns = 1)) + goto(AwaitAction) + } + + whenSafe(AwaitAction) { + case Event(cmd@Command(_, _, Some(actionStr)), _) => + val action = actionStr.toLong + action match { + case -1L => + router ! cmd.copy(message = cmd.message.copy(text = Some("/login"))) + goto(AskAction) using null + case -2L => + bot.sendMessage(userId.source, "Not implemented yet") + goto(AskAction) using null + case accountId => + val accountMaybe = dataService.findUserCredentialsByAccountId(userId.userId, accountId) + accountMaybe match { + case Some(account) => //account was found + val userMaybe = dataService.findUser(userId.userId) + userMaybe.foreach { user => + user.activeAccountId = accountId + dataService.saveUser(user) + router ! SwitchUser(UserId(account.userId, account.accountId, userId.source)) + bot.sendMessage(userId.source, lang.accountSwitched(account.username)) + } + goto(AskAction) using null + case None => + LOG.error(s"This is not user [#${userId.userId}] account [#$accountId]") + goto(AskAction) using null + } + } + } + + whenUnhandledSafe { + case Event(Init, _) => + invokeNext() + goto(AskAction) using null + case e: Event => + LOG.error(s"Unhandled event in state:$stateName. Event: $e") + stay() + } + + initialize() + +} + +object Account { + def props(userId: UserId, bot: Bot, dataService: DataService, localization: Localization, router: ActorRef): Props = + Props(new Account(userId, bot, dataService, localization, router)) + + object AskAction extends FSMState + + object AwaitAction extends FSMState + + object AddAccount extends FSMState + + object RemoveAccount extends FSMState + + case class SwitchUser(userId: UserId) + +} \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/actor/Auth.scala b/server/src/main/scala/com/lbs/server/actor/Auth.scala index 03319f5..6d3dbce 100644 --- a/server/src/main/scala/com/lbs/server/actor/Auth.scala +++ b/server/src/main/scala/com/lbs/server/actor/Auth.scala @@ -54,22 +54,22 @@ class Auth(val source: MessageSource, dataService: DataService, unauthorizedHelp case cmd: Command if userId.nonEmpty => chatActor = getChatActor(userId.get) chatActor ! cmd - case LoggedIn(forwardCommand, id) => - val uId = UserId(id, source) + case LoggedIn(forwardCommand, uId, aId) => + val id = UserId(uId, aId, source) val cmd = forwardCommand.cmd - userId = Some(uId) - chatActor = getChatActor(uId, reinit = true) + userId = Some(id) + chatActor = getChatActor(id, reInit = true) if (!cmd.message.text.contains("/login")) chatActor ! cmd case cmd: Command => chatActor ! cmd } - private def getChatActor(userId: UserId, reinit: Boolean = false): ActorRef = { + private def getChatActor(userId: UserId, reInit: Boolean = false): ActorRef = { if (chatActor == null) { chatActorFactory(userId) } else { - if (reinit) { + if (reInit) { chatActor ! PoisonPill chatActorFactory(userId) } else chatActor @@ -77,8 +77,8 @@ class Auth(val source: MessageSource, dataService: DataService, unauthorizedHelp } def getUserId: Option[UserId] = { - val userIdMaybe = dataService.findUserIdBySource(source) - userIdMaybe.map(id => UserId(id, source)) + val userIdMaybe = dataService.findUserAndAccountIdBySource(source) + userIdMaybe.map { case (uId, aId) => UserId(uId, aId, source) } } override def postStop(): Unit = { diff --git a/server/src/main/scala/com/lbs/server/actor/Book.scala b/server/src/main/scala/com/lbs/server/actor/Book.scala index 9f0c77b..b5877a9 100644 --- a/server/src/main/scala/com/lbs/server/actor/Book.scala +++ b/server/src/main/scala/com/lbs/server/actor/Book.scala @@ -53,29 +53,29 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da requestStaticData(RequestCity, AwaitCity, cityConfig) { bd: BookingData => withFunctions( - latestOptions = dataService.getLatestCities(userId.userId), - staticOptions = apiService.getAllCities(userId.userId), + latestOptions = dataService.getLatestCities(userId.accountId), + staticOptions = apiService.getAllCities(userId.accountId), applyId = id => bd.copy(cityId = id)) }(requestNext = RequestClinic) requestStaticData(RequestClinic, AwaitClinic, clinicConfig) { bd: BookingData => withFunctions( - latestOptions = dataService.getLatestClinicsByCityId(userId.userId, bd.cityId.id), - staticOptions = apiService.getAllClinics(userId.userId, bd.cityId.id), + latestOptions = dataService.getLatestClinicsByCityId(userId.accountId, bd.cityId.id), + staticOptions = apiService.getAllClinics(userId.accountId, bd.cityId.id), applyId = id => bd.copy(clinicId = id)) }(requestNext = RequestService) requestStaticData(RequestService, AwaitService, serviceConfig) { bd: BookingData => withFunctions( - latestOptions = dataService.getLatestServicesByCityIdAndClinicId(userId.userId, bd.cityId.id, bd.clinicId.optionalId), - staticOptions = apiService.getAllServices(userId.userId, bd.cityId.id, bd.clinicId.optionalId), + latestOptions = dataService.getLatestServicesByCityIdAndClinicId(userId.accountId, bd.cityId.id, bd.clinicId.optionalId), + staticOptions = apiService.getAllServices(userId.accountId, bd.cityId.id, bd.clinicId.optionalId), applyId = id => bd.copy(serviceId = id)) }(requestNext = RequestDoctor) requestStaticData(RequestDoctor, AwaitDoctor, doctorConfig) { bd: BookingData => withFunctions( - latestOptions = dataService.getLatestDoctorsByCityIdAndClinicIdAndServiceId(userId.userId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id), - staticOptions = apiService.getAllDoctors(userId.userId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id), + latestOptions = dataService.getLatestDoctorsByCityIdAndClinicIdAndServiceId(userId.accountId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id), + staticOptions = apiService.getAllDoctors(userId.accountId, bd.cityId.id, bd.clinicId.optionalId, bd.serviceId.id), applyId = id => bd.copy(doctorId = id)) }(requestNext = RequestDateFrom) @@ -128,7 +128,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da whenSafe(RequestAction) { case Event(Next, bookingData: BookingData) => - dataService.storeAppointment(userId.userId, bookingData) + dataService.storeAppointment(userId.accountId, bookingData) bot.sendMessage(userId.source, lang.bookingSummary(bookingData), inlineKeyboard = createInlineKeyboard(Seq(Button(lang.findTerms, Tags.FindTerms), Button(lang.modifyDate, Tags.ModifyDate)))) @@ -146,7 +146,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da whenSafe(RequestTerm) { case Event(Next, bookingData: BookingData) => - val availableTerms = apiService.getAvailableTerms(userId.userId, bookingData.cityId.id, + val availableTerms = apiService.getAvailableTerms(userId.accountId, bookingData.cityId.id, bookingData.clinicId.optionalId, bookingData.serviceId.id, bookingData.doctorId.optionalId, bookingData.dateFrom, Some(bookingData.dateTo), timeOfDay = bookingData.timeOfDay) termsPager ! availableTerms @@ -174,7 +174,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da whenSafe(RequestReservation) { case Event(term: AvailableVisitsTermPresentation, bookingData: BookingData) => - val response = apiService.temporaryReservation(userId.userId, term.mapTo[TemporaryReservationRequest], term.mapTo[ValuationsRequest]) + val response = apiService.temporaryReservation(userId.accountId, term.mapTo[TemporaryReservationRequest], term.mapTo[ValuationsRequest]) response match { case Left(ex) => bot.sendMessage(userId.source, ex.getMessage) @@ -189,7 +189,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da whenSafe(AwaitReservation) { case Event(Command(_, _, Some(Tags.Cancel)), bookingData: BookingData) => - apiService.deleteTemporaryReservation(userId.userId, bookingData.temporaryReservationId.get) + apiService.deleteTemporaryReservation(userId.accountId, bookingData.temporaryReservationId.get) stay() case Event(Command(_, _, Some(Tags.Book)), bookingData: BookingData) => val reservationRequestMaybe = for { @@ -201,7 +201,7 @@ class Book(val userId: UserId, bot: Bot, apiService: ApiService, dataService: Da reservationRequestMaybe match { case Some(reservationRequest) => - apiService.reservation(userId.userId, reservationRequest) match { + apiService.reservation(userId.accountId, reservationRequest) match { case Left(ex) => bot.sendMessage(userId.source, ex.getMessage) invokeNext() diff --git a/server/src/main/scala/com/lbs/server/actor/Chat.scala b/server/src/main/scala/com/lbs/server/actor/Chat.scala index 6b70c73..a3bba33 100644 --- a/server/src/main/scala/com/lbs/server/actor/Chat.scala +++ b/server/src/main/scala/com/lbs/server/actor/Chat.scala @@ -36,7 +36,7 @@ import scala.util.matching.Regex class Chat(val userId: UserId, dataService: DataService, monitoringService: MonitoringService, bookingActorFactory: ByUserIdActorFactory, helpActorFactory: ByUserIdActorFactory, monitoringsActorFactory: ByUserIdActorFactory, historyActorFactory: ByUserIdActorFactory, visitsActorFactory: ByUserIdActorFactory, settingsActorFactory: ByUserIdActorFactory, - bugActorFactory: ByUserIdActorFactory) extends SafeFSM[FSMState, FSMData] with Logger { + bugActorFactory: ByUserIdActorFactory, accountActorFactory: ByUserIdActorFactory) extends SafeFSM[FSMState, FSMData] with Logger { private val bookingActor = bookingActorFactory(userId) private val helpActor = helpActorFactory(userId) @@ -45,6 +45,7 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni private val visitsActor = visitsActorFactory(userId) private val settingsActor = settingsActorFactory(userId) private val bugActor = bugActorFactory(userId) + private val accountActor = accountActorFactory(userId) startWith(HelpChat, null) @@ -93,6 +94,12 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni stay() } + when(AccountChat, accountActor) { + case Event(Command(_, Text("/accounts"), _), _) => + accountActor ! Init + stay() + } + private def when(state: FSMState, actor: ActorRef)(mainStateFunction: StateFunction): Unit = { whenSafe(state) { case event: Event => @@ -130,11 +137,14 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni case Event(cmd@Command(_, Text("/settings"), _), _) => self ! cmd goto(SettingsChat) + case Event(cmd@Command(_, Text("/accounts"), _), _) => + self ! cmd + goto(AccountChat) case Event(cmd@Command(_, Text(MonitoringId(monitoringIdStr, scheduleIdStr, timeStr)), _), _) => val monitoringId = monitoringIdStr.toLong val scheduleId = scheduleIdStr.toLong val time = timeStr.toLong - monitoringService.bookAppointmentByScheduleId(userId.userId, monitoringId, scheduleId, time) + monitoringService.bookAppointmentByScheduleId(userId.accountId, monitoringId, scheduleId, time) stay() case Event(cmd: Command, _) => actor ! cmd @@ -157,6 +167,7 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni visitsActor ! PoisonPill settingsActor ! PoisonPill bugActor ! PoisonPill + accountActor ! PoisonPill super.postStop() } } @@ -164,9 +175,10 @@ class Chat(val userId: UserId, dataService: DataService, monitoringService: Moni object Chat { def props(userId: UserId, dataService: DataService, monitoringService: MonitoringService, bookingActorFactory: ByUserIdActorFactory, helpActorFactory: ByUserIdActorFactory, monitoringsActorFactory: ByUserIdActorFactory, historyActorFactory: ByUserIdActorFactory, - visitsActorFactory: ByUserIdActorFactory, settingsActorFactory: ByUserIdActorFactory, bugActorFactory: ByUserIdActorFactory): Props = + visitsActorFactory: ByUserIdActorFactory, settingsActorFactory: ByUserIdActorFactory, bugActorFactory: ByUserIdActorFactory, + accountActorFactory: ByUserIdActorFactory): Props = Props(new Chat(userId, dataService, monitoringService, bookingActorFactory, helpActorFactory, monitoringsActorFactory, - historyActorFactory, visitsActorFactory, settingsActorFactory, bugActorFactory)) + historyActorFactory, visitsActorFactory, settingsActorFactory, bugActorFactory, accountActorFactory)) object HelpChat extends FSMState @@ -182,6 +194,8 @@ object Chat { object BugChat extends FSMState + object AccountChat extends FSMState + object Init val MonitoringId: Regex = s"/reserve_(\\d+)_(\\d+)_(\\d+)".r diff --git a/server/src/main/scala/com/lbs/server/actor/History.scala b/server/src/main/scala/com/lbs/server/actor/History.scala index 2748939..5b4e1c3 100644 --- a/server/src/main/scala/com/lbs/server/actor/History.scala +++ b/server/src/main/scala/com/lbs/server/actor/History.scala @@ -41,7 +41,7 @@ class History(val userId: UserId, bot: Bot, apiService: ApiService, val localiza whenSafe(RequestData) { case Event(Next, _) => - val visits = apiService.visitsHistory(userId.userId) + val visits = apiService.visitsHistory(userId.accountId) historyPager ! visits goto(AwaitPage) } diff --git a/server/src/main/scala/com/lbs/server/actor/Login.scala b/server/src/main/scala/com/lbs/server/actor/Login.scala index 9d63735..e0c80d9 100644 --- a/server/src/main/scala/com/lbs/server/actor/Login.scala +++ b/server/src/main/scala/com/lbs/server/actor/Login.scala @@ -26,7 +26,6 @@ package com.lbs.server.actor import akka.actor.{ActorRef, Props} import com.lbs.bot.Bot import com.lbs.bot.model.{Command, MessageSource} -import com.lbs.bot.telegram.TelegramBot import com.lbs.server.actor.Chat.Init import com.lbs.server.actor.Login._ import com.lbs.server.lang.{Localizable, Localization} @@ -56,10 +55,10 @@ class Login(source: MessageSource, bot: Bot, dataService: DataService, apiServic goto(RequestUsername) using LoginData() case Right(loggedIn) => val credentials = dataService.saveCredentials(source, username, password) - userId = UserId(credentials.userId, source) - apiService.addSession(credentials.userId, loggedIn.accessToken, loggedIn.tokenType) + userId = UserId(credentials.userId, credentials.accountId, source) + apiService.addSession(credentials.accountId, loggedIn.accessToken, loggedIn.tokenType) bot.sendMessage(source, lang.loginAndPasswordAreOk) - originator ! LoggedIn(forwardCommand, credentials.userId) + originator ! LoggedIn(forwardCommand, credentials.userId, credentials.accountId) stay() using null } } @@ -119,8 +118,8 @@ object Login { case class ForwardCommand(cmd: Command) - case class UserId(userId: Long, source: MessageSource) + case class UserId(userId: Long, accountId: Long, source: MessageSource) - case class LoggedIn(forwardCommand: ForwardCommand, userId: Long) + case class LoggedIn(forwardCommand: ForwardCommand, userId: Long, accountId: Long) } \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/actor/Monitorings.scala b/server/src/main/scala/com/lbs/server/actor/Monitorings.scala index 9eb80dc..34bc8d4 100644 --- a/server/src/main/scala/com/lbs/server/actor/Monitorings.scala +++ b/server/src/main/scala/com/lbs/server/actor/Monitorings.scala @@ -41,7 +41,7 @@ class Monitorings(val userId: UserId, bot: Bot, monitoringService: MonitoringSer whenSafe(RequestData) { case Event(Next, _) => - val monitorings = monitoringService.getActiveMonitorings(userId.userId) + val monitorings = monitoringService.getActiveMonitorings(userId.accountId) monitoringsPager ! Right[Throwable, Seq[Monitoring]](monitorings) goto(AwaitPage) } diff --git a/server/src/main/scala/com/lbs/server/actor/Router.scala b/server/src/main/scala/com/lbs/server/actor/Router.scala index 63602b3..813d46a 100644 --- a/server/src/main/scala/com/lbs/server/actor/Router.scala +++ b/server/src/main/scala/com/lbs/server/actor/Router.scala @@ -26,6 +26,7 @@ package com.lbs.server.actor import akka.actor.{Actor, ActorRef, Cancellable, PoisonPill, Props} import com.lbs.bot.model.{Command, MessageSource} import com.lbs.common.Logger +import com.lbs.server.actor.Account.SwitchUser import com.lbs.server.actor.Router.DestroyChat import scala.collection.mutable @@ -47,20 +48,34 @@ class Router(authActorFactory: ByMessageSourceActorFactory) extends Actor with L scheduleIdleChatDestroyer(source) val chat = chats.get(source) match { case Some(actor) => actor - case None => - val actor = authActorFactory(source) - chats += source -> actor - actor + case None => addNewChatActor(source) } chat ! cmd case DestroyChat(source) => destroyChat(source) + case SwitchUser(userId) => + switchUser(userId) case what => LOG.info(s"Unknown message: $what") } + private def addNewChatActor(source: MessageSource): ActorRef = { + val actor = authActorFactory(source) + chats += source -> actor + actor + } + private def destroyChat(source: MessageSource): Unit = { LOG.info(s"Destroying chat for $source due to $idleTimeout inactivity") timers.remove(source) + removeChat(source) + } + + private def switchUser(userId: Login.UserId): Unit = { + removeChat(userId.source) + addNewChatActor(userId.source) + } + + private def removeChat(source: MessageSource): Unit = { chats.remove(source).foreach(_ ! PoisonPill) } diff --git a/server/src/main/scala/com/lbs/server/actor/Visits.scala b/server/src/main/scala/com/lbs/server/actor/Visits.scala index 457b592..bbbc6fb 100644 --- a/server/src/main/scala/com/lbs/server/actor/Visits.scala +++ b/server/src/main/scala/com/lbs/server/actor/Visits.scala @@ -42,7 +42,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat whenSafe(RequestData) { case Event(Next, _) => - val visits = apiService.reservedVisits(userId.userId) + val visits = apiService.reservedVisits(userId.accountId) reservedVisitsPager ! visits goto(AwaitPage) } @@ -65,7 +65,7 @@ class Visits(val userId: UserId, bot: Bot, apiService: ApiService, val localizat bot.sendMessage(userId.source, lang.appointmentWasNotCancelled) goto(RequestData) case Event(Command(_, _, Some(Tags.Yes)), visit: ReservedVisit) => - apiService.deleteReservation(userId.userId, visit.reservationId) match { + apiService.deleteReservation(userId.accountId, visit.reservationId) match { case Left(ex) => bot.sendMessage(userId.source, lang.unableToCancelUpcomingVisit(ex.getMessage)) case Right(r) => bot.sendMessage(userId.source, lang.appointmentHasBeenCancelled) } diff --git a/server/src/main/scala/com/lbs/server/lang/En.scala b/server/src/main/scala/com/lbs/server/lang/En.scala index 3d79000..362e8ca 100644 --- a/server/src/main/scala/com/lbs/server/lang/En.scala +++ b/server/src/main/scala/com/lbs/server/lang/En.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.lang import java.time.ZonedDateTime @@ -353,4 +353,13 @@ object En extends Lang { override def allDay: String = "All day" override def preferredTimeIs(time: Int): String = s"⏱ Preferred time is ${timeOfDay(time)}" + + override def deleteAccount: String = "➖ Delete account" + + override def addAccount: String = "➕ Add account" + + override def accountSwitched(username: String): String = + s"✅ Account has been switched to $username" + + override def pleaseChooseAccount: String = " Please choose an action or select account" } diff --git a/server/src/main/scala/com/lbs/server/lang/Lang.scala b/server/src/main/scala/com/lbs/server/lang/Lang.scala index 268fe8f..7cc7527 100644 --- a/server/src/main/scala/com/lbs/server/lang/Lang.scala +++ b/server/src/main/scala/com/lbs/server/lang/Lang.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.lang import java.time.ZonedDateTime @@ -233,4 +233,12 @@ trait Lang { def allDay: String def preferredTimeIs(time: Int): String + + def deleteAccount: String + + def addAccount: String + + def pleaseChooseAccount: String + + def accountSwitched(username: String): String } diff --git a/server/src/main/scala/com/lbs/server/lang/Ua.scala b/server/src/main/scala/com/lbs/server/lang/Ua.scala index 3d872ef..4857029 100644 --- a/server/src/main/scala/com/lbs/server/lang/Ua.scala +++ b/server/src/main/scala/com/lbs/server/lang/Ua.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.lang import java.time.ZonedDateTime @@ -352,4 +352,13 @@ object Ua extends Lang { override def allDay: String = "Весь день" override def preferredTimeIs(time: Int): String = s"⏱ Бажаний час ${timeOfDay(time)}" + + override def deleteAccount: String = "➖ Видалити акаунт" + + override def addAccount: String = "➕ Додати акаунт" + + override def accountSwitched(username: String): String = + s"✅ Аккаунт було переключено на $username" + + override def pleaseChooseAccount: String = " Будь ласка, оберіть дію або виберіть акаунт" } diff --git a/server/src/main/scala/com/lbs/server/repository/DataRepository.scala b/server/src/main/scala/com/lbs/server/repository/DataRepository.scala index d6dfea7..96f4671 100644 --- a/server/src/main/scala/com/lbs/server/repository/DataRepository.scala +++ b/server/src/main/scala/com/lbs/server/repository/DataRepository.scala @@ -1,31 +1,31 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.repository import java.time.ZonedDateTime -import com.lbs.server.repository.model.{Bug, CityHistory, ClinicHistory, Credentials, DoctorHistory, JLong, Monitoring, ServiceHistory, Settings, Source} +import com.lbs.server.repository.model.{Bug, CityHistory, ClinicHistory, Credentials, DoctorHistory, JLong, Monitoring, ServiceHistory, Settings, Source, SystemUser} import javax.persistence.EntityManager import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository @@ -37,48 +37,48 @@ class DataRepository(@Autowired em: EntityManager) { private val maxHistory = 2 - def getCityHistory(userId: Long): Seq[CityHistory] = { + def getCityHistory(accountId: Long): Seq[CityHistory] = { em.createQuery( """select city from CityHistory city where city.recordId in - | (select max(c.recordId) from CityHistory c where c.userId = :userId group by c.name order by MAX(c.time) desc) + | (select max(c.recordId) from CityHistory c where c.accountId = :accountId group by c.name order by MAX(c.time) desc) | order by city.time desc""".stripMargin, classOf[CityHistory]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setMaxResults(maxHistory) .getResultList.asScala } - def getClinicHistory(userId: Long, cityId: Long): Seq[ClinicHistory] = { + def getClinicHistory(accountId: Long, cityId: Long): Seq[ClinicHistory] = { em.createQuery( """select clinic from ClinicHistory clinic where clinic.recordId in - | (select max(c.recordId) from ClinicHistory c where c.userId = :userId and c.cityId = :cityId group by c.name order by MAX(c.time) desc) + | (select max(c.recordId) from ClinicHistory c where c.accountId = :accountId and c.cityId = :cityId group by c.name order by MAX(c.time) desc) | order by clinic.time desc""".stripMargin, classOf[ClinicHistory]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setParameter("cityId", cityId) .setMaxResults(maxHistory) .getResultList.asScala } - def getServiceHistory(userId: Long, cityId: Long, clinicId: Option[Long]): Seq[ServiceHistory] = { + def getServiceHistory(accountId: Long, cityId: Long, clinicId: Option[Long]): Seq[ServiceHistory] = { val query = em.createQuery( s"""select service from ServiceHistory service where service.recordId in - | (select max(s.recordId) from ServiceHistory s where s.userId = :userId and s.cityId = :cityId + | (select max(s.recordId) from ServiceHistory s where s.accountId = :accountId and s.cityId = :cityId | and s.clinicId ${clinicId.map(_ => "= :clinicId").getOrElse("IS NULL")} group by s.name order by MAX(s.time) desc) | order by service.time desc""".stripMargin, classOf[ServiceHistory]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setParameter("cityId", cityId) .setMaxResults(maxHistory) clinicId.map(id => query.setParameter("clinicId", id)).getOrElse(query).getResultList.asScala } - def getDoctorHistory(userId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Seq[DoctorHistory] = { + def getDoctorHistory(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Seq[DoctorHistory] = { val query = em.createQuery( s"""select doctor from DoctorHistory doctor where doctor.recordId in - | (select max(d.recordId) from DoctorHistory d where d.userId = :userId + | (select max(d.recordId) from DoctorHistory d where d.accountId = :accountId | and d.cityId = :cityId and d.clinicId ${clinicId.map(_ => "= :clinicId").getOrElse("IS NULL")} | and d.serviceId = :serviceId group by d.name order by MAX(d.time) desc) | order by doctor.time desc""".stripMargin, classOf[DoctorHistory]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setParameter("cityId", cityId) .setParameter("serviceId", serviceId) .setMaxResults(maxHistory) @@ -86,10 +86,10 @@ class DataRepository(@Autowired em: EntityManager) { clinicId.map(id => query.setParameter("clinicId", id)).getOrElse(query).getResultList.asScala } - def findCredentials(userId: Long): Option[Credentials] = { + def findCredentials(accountId: Long): Option[Credentials] = { em.createQuery( - "select credentials from Credentials credentials where credentials.userId = :userId", classOf[Credentials]) - .setParameter("userId", userId) + "select credentials from Credentials credentials where credentials.accountId = :accountId", classOf[Credentials]) + .setParameter("accountId", accountId) .getResultList.asScala.headOption } @@ -107,29 +107,29 @@ class DataRepository(@Autowired em: EntityManager) { .getResultList.asScala } - def getActiveMonitoringsCount(userId: Long): JLong = { + def getActiveMonitoringsCount(accountId: Long): JLong = { em.createQuery( """select count(monitoring) from Monitoring monitoring where monitoring.active = true - | and monitoring.userId = :userId""".stripMargin, classOf[JLong]) - .setParameter("userId", userId) + | and monitoring.accountId = :accountId""".stripMargin, classOf[JLong]) + .setParameter("accountId", accountId) .getSingleResult } - def getActiveMonitorings(userId: Long): Seq[Monitoring] = { + def getActiveMonitorings(accountId: Long): Seq[Monitoring] = { em.createQuery( """select monitoring from Monitoring monitoring where monitoring.active = true - | and monitoring.userId = :userId order by monitoring.dateTo asc""".stripMargin, classOf[Monitoring]) - .setParameter("userId", userId) + | and monitoring.accountId = :accountId order by monitoring.dateTo asc""".stripMargin, classOf[Monitoring]) + .setParameter("accountId", accountId) .getResultList.asScala } - def findActiveMonitoring(userId: Long, cityId: Long, serviceId: Long): Option[Monitoring] = { + def findActiveMonitoring(accountId: Long, cityId: Long, serviceId: Long): Option[Monitoring] = { em.createQuery( """select monitoring from Monitoring monitoring where monitoring.active = true - | and monitoring.userId = :userId + | and monitoring.accountId = :accountId | and monitoring.cityId = :cityId | and monitoring.serviceId = :serviceId""".stripMargin, classOf[Monitoring]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setParameter("cityId", cityId) .setParameter("serviceId", serviceId) .getResultList.asScala.headOption @@ -143,11 +143,11 @@ class DataRepository(@Autowired em: EntityManager) { .getResultList.asScala } - def findMonitoring(userId: Long, monitoringId: Long): Option[Monitoring] = { + def findMonitoring(accountId: Long, monitoringId: Long): Option[Monitoring] = { em.createQuery( - """select monitoring from Monitoring monitoring where monitoring.userId = :userId + """select monitoring from Monitoring monitoring where monitoring.accountId = :accountId | and monitoring.recordId = :monitoringId""".stripMargin, classOf[Monitoring]) - .setParameter("userId", userId) + .setParameter("accountId", accountId) .setParameter("monitoringId", monitoringId) .getResultList.asScala.headOption } @@ -186,6 +186,46 @@ class DataRepository(@Autowired em: EntityManager) { .getResultList.asScala.headOption } + def findUserIdBySource(chatId: String, sourceSystemId: Long): Option[JLong] = { + em.createQuery( + "select source.userId from Source source where source.chatId = :chatId" + + " and source.sourceSystemId = :sourceSystemId", classOf[JLong]) + .setParameter("chatId", chatId) + .setParameter("sourceSystemId", sourceSystemId) + .getResultList.asScala.headOption + } + + def findAccountId(userId: Long): Option[JLong] = { + em.createQuery( + "select systemUser.activeAccountId from SystemUser systemUser where systemUser.recordId = :recordId", classOf[JLong]) + .setParameter("recordId", userId) + .getResultList.asScala.headOption + } + + def findUser(userId: Long): Option[SystemUser] = { + em.createQuery( + "select systemUser from SystemUser systemUser where systemUser.recordId = :recordId", classOf[SystemUser]) + .setParameter("recordId", userId) + .getResultList.asScala.headOption + } + + def getUserCredentials(userId: Long): Seq[Credentials] = { + em.createQuery( + "select credentials from Credentials credentials where credentials.userId = :userId", classOf[Credentials]) + .setParameter("userId", userId) + .getResultList.asScala + } + + def findUserCredentialsByUserIdAndAccountId(userId: Long, accountId: Long): Option[Credentials] = { + em.createQuery( + """select credentials from Credentials credentials where credentials.userId = :userId + | and credentials.accountId = :accountId + """.stripMargin, classOf[Credentials]) + .setParameter("userId", userId) + .setParameter("accountId", accountId) + .getResultList.asScala.headOption + } + def saveEntity[T](entity: T): T = { em.merge(entity) } diff --git a/server/src/main/scala/com/lbs/server/repository/model/Account.scala b/server/src/main/scala/com/lbs/server/repository/model/Account.scala new file mode 100644 index 0000000..ff4c084 --- /dev/null +++ b/server/src/main/scala/com/lbs/server/repository/model/Account.scala @@ -0,0 +1,31 @@ +/** + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.lbs.server.repository.model + +import javax.persistence._ + +@Entity +@Access(AccessType.FIELD) +//just a sequence generator +class Account extends RecordId \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/repository/model/CityHistory.scala b/server/src/main/scala/com/lbs/server/repository/model/CityHistory.scala index d373ced..d434e8a 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/CityHistory.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/CityHistory.scala @@ -41,8 +41,8 @@ class CityHistory extends History with RecordId { var name: String = _ @BeanProperty - @Column(name = "user_id", nullable = false) - var userId: JLong = _ + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ @BeanProperty @Column(nullable = false) @@ -50,9 +50,9 @@ class CityHistory extends History with RecordId { } object CityHistory { - def apply(userId: Long, id: Long, name: String, time: ZonedDateTime): CityHistory = { + def apply(accountId: Long, id: Long, name: String, time: ZonedDateTime): CityHistory = { val city = new CityHistory - city.userId = userId + city.accountId = accountId city.id = id city.name = name city.time = time diff --git a/server/src/main/scala/com/lbs/server/repository/model/ClinicHistory.scala b/server/src/main/scala/com/lbs/server/repository/model/ClinicHistory.scala index 9995348..8fcf6e7 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/ClinicHistory.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/ClinicHistory.scala @@ -41,8 +41,8 @@ class ClinicHistory extends History with RecordId { var name: String = _ @BeanProperty - @Column(name = "user_id", nullable = false) - var userId: JLong = _ + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ @BeanProperty @Column(name = "city_id", nullable = false) @@ -54,9 +54,9 @@ class ClinicHistory extends History with RecordId { } object ClinicHistory { - def apply(userId: Long, id: Long, name: String, cityId: Long, time: ZonedDateTime): ClinicHistory = { + def apply(accountId: Long, id: Long, name: String, cityId: Long, time: ZonedDateTime): ClinicHistory = { val clinic = new ClinicHistory - clinic.userId = userId + clinic.accountId = accountId clinic.id = id clinic.name = name clinic.time = time diff --git a/server/src/main/scala/com/lbs/server/repository/model/Credentials.scala b/server/src/main/scala/com/lbs/server/repository/model/Credentials.scala index e0cd62c..2db8832 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/Credentials.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/Credentials.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.repository.model import javax.persistence._ @@ -29,12 +29,15 @@ import scala.beans.BeanProperty @Entity @Access(AccessType.FIELD) -class Credentials { - @Id +class Credentials extends RecordId { @BeanProperty @Column(name = "user_id", nullable = false) var userId: JLong = _ + @BeanProperty + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ + @BeanProperty @Column(nullable = false) var username: String = _ @@ -45,9 +48,10 @@ class Credentials { } object Credentials { - def apply(userId: Long, username: String, password: String): Credentials = { + def apply(userId: JLong, accountId: JLong, username: String, password: String): Credentials = { val credentials = new Credentials credentials.userId = userId + credentials.accountId = accountId credentials.username = username credentials.password = password credentials diff --git a/server/src/main/scala/com/lbs/server/repository/model/DoctorHistory.scala b/server/src/main/scala/com/lbs/server/repository/model/DoctorHistory.scala index 2b31fe8..64ac3fc 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/DoctorHistory.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/DoctorHistory.scala @@ -41,8 +41,8 @@ class DoctorHistory extends History with RecordId { var name: String = _ @BeanProperty - @Column(name = "user_id", nullable = false) - var userId: JLong = _ + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ @BeanProperty @Column(name = "city_id", nullable = false) @@ -62,9 +62,9 @@ class DoctorHistory extends History with RecordId { } object DoctorHistory { - def apply(userId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], serviceId: Long, time: ZonedDateTime): DoctorHistory = { + def apply(accountId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], serviceId: Long, time: ZonedDateTime): DoctorHistory = { val doctor = new DoctorHistory - doctor.userId = userId + doctor.accountId = accountId doctor.id = id doctor.name = name doctor.time = time diff --git a/server/src/main/scala/com/lbs/server/repository/model/Monitoring.scala b/server/src/main/scala/com/lbs/server/repository/model/Monitoring.scala index f18486c..53dfa47 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/Monitoring.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/Monitoring.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.repository.model import java.time.ZonedDateTime @@ -36,6 +36,9 @@ class Monitoring extends RecordId { @Column(name = "user_id", nullable = false) var userId: JLong = _ + @BeanProperty + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ @BeanProperty @Column(name = "chat_id", nullable = false) @@ -103,12 +106,13 @@ class Monitoring extends RecordId { } object Monitoring { - def apply(userId: Long, chatId: String, sourceSystemId: Long, cityId: Long, cityName: String, clinicId: Option[Long], clinicName: String, + def apply(userId: Long, accountId: Long, chatId: String, sourceSystemId: Long, cityId: Long, cityName: String, clinicId: Option[Long], clinicName: String, serviceId: Long, serviceName: String, doctorId: Option[Long], doctorName: String, dateFrom: ZonedDateTime, dateTo: ZonedDateTime, autobook: Boolean = false, created: ZonedDateTime = ZonedDateTime.now(), timeOfDay: Int, active: Boolean = true): Monitoring = { val monitoring = new Monitoring monitoring.userId = userId + monitoring.accountId = accountId monitoring.chatId = chatId monitoring.sourceSystemId = sourceSystemId monitoring.cityId = cityId diff --git a/server/src/main/scala/com/lbs/server/repository/model/ServiceHistory.scala b/server/src/main/scala/com/lbs/server/repository/model/ServiceHistory.scala index 17728fe..f009322 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/ServiceHistory.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/ServiceHistory.scala @@ -41,8 +41,8 @@ class ServiceHistory extends History with RecordId { var name: String = _ @BeanProperty - @Column(name = "userId_id", nullable = false) - var userId: JLong = _ + @Column(name = "account_id", nullable = false) + var accountId: JLong = _ @BeanProperty @Column(name = "city_id", nullable = false) @@ -58,9 +58,9 @@ class ServiceHistory extends History with RecordId { } object ServiceHistory { - def apply(userId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], time: ZonedDateTime): ServiceHistory = { + def apply(accountId: Long, id: Long, name: String, cityId: Long, clinicId: Option[Long], time: ZonedDateTime): ServiceHistory = { val service = new ServiceHistory - service.userId = userId + service.accountId = accountId service.id = id service.name = name service.time = time diff --git a/server/src/main/scala/com/lbs/server/repository/model/SystemUser.scala b/server/src/main/scala/com/lbs/server/repository/model/SystemUser.scala index fd2cce7..3517f53 100644 --- a/server/src/main/scala/com/lbs/server/repository/model/SystemUser.scala +++ b/server/src/main/scala/com/lbs/server/repository/model/SystemUser.scala @@ -1,34 +1,44 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.repository.model import javax.persistence._ +import scala.beans.BeanProperty + @Entity @Access(AccessType.FIELD) -class SystemUser extends RecordId - - - +class SystemUser extends RecordId { + @BeanProperty + @Column(name = "active_account_id", nullable = false) + var activeAccountId: JLong = _ +} +object SystemUser { + def apply(activeAccountId: Long): SystemUser = { + val user = new SystemUser + user.activeAccountId = activeAccountId + user + } +} \ No newline at end of file diff --git a/server/src/main/scala/com/lbs/server/service/ApiService.scala b/server/src/main/scala/com/lbs/server/service/ApiService.scala index 718b7ed..a6e4e31 100644 --- a/server/src/main/scala/com/lbs/server/service/ApiService.scala +++ b/server/src/main/scala/com/lbs/server/service/ApiService.scala @@ -40,53 +40,53 @@ class ApiService extends SessionSupport { @Autowired private var textEncryptor: TextEncryptor = _ - def getAllCities(userId: Long): Either[Throwable, List[IdName]] = - withSession(userId) { session => + def getAllCities(accountId: Long): Either[Throwable, List[IdName]] = + withSession(accountId) { session => LuxmedApi.reservationFilter(session.accessToken, session.tokenType).map(_.cities) } - def getAllClinics(userId: Long, cityId: Long): Either[Throwable, List[IdName]] = - withSession(userId) { session => + def getAllClinics(accountId: Long, cityId: Long): Either[Throwable, List[IdName]] = + withSession(accountId) { session => LuxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId)).map(_.clinics) } - def getAllServices(userId: Long, cityId: Long, clinicId: Option[Long]): Either[Throwable, List[IdName]] = - withSession(userId) { session => + def getAllServices(accountId: Long, cityId: Long, clinicId: Option[Long]): Either[Throwable, List[IdName]] = + withSession(accountId) { session => LuxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId).map(_.services) } - def getAllDoctors(userId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, List[IdName]] = - withSession(userId) { session => + def getAllDoctors(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, List[IdName]] = + withSession(accountId) { session => LuxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId, serviceId = Some(serviceId)).map(_.doctors) } - def getDefaultPayer(userId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, Option[IdName]] = - withSession(userId) { session => + def getDefaultPayer(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long): Either[Throwable, Option[IdName]] = + withSession(accountId) { session => LuxmedApi.reservationFilter(session.accessToken, session.tokenType, cityId = Some(cityId), clinicId = clinicId, serviceId = Some(serviceId)).map(_.defaultPayer) } - def getAvailableTerms(userId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long], + def getAvailableTerms(accountId: Long, cityId: Long, clinicId: Option[Long], serviceId: Long, doctorId: Option[Long], fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: Option[ZonedDateTime] = None, timeOfDay: Int = 0, languageId: Long = 10, findFirstFreeTerm: Boolean = false): Either[Throwable, List[AvailableVisitsTermPresentation]] = - getDefaultPayer(userId, cityId, clinicId, serviceId).flatMap { + getDefaultPayer(accountId, cityId, clinicId, serviceId).flatMap { case Some(payerId) => - withSession(userId) { session => + withSession(accountId) { session => LuxmedApi.availableTerms(session.accessToken, session.tokenType, payerId.id, cityId, clinicId, serviceId, doctorId, fromDate, toDate, timeOfDay, languageId, findFirstFreeTerm).map(_.availableVisitsTermPresentation) } - case None => sys.error(s"Can't determine payer id by user: $userId, city: $cityId, clinic: $clinicId, service: $serviceId") + case None => sys.error(s"Can't determine payer id by user: $accountId, city: $cityId, clinic: $clinicId, service: $serviceId") } - def temporaryReservation(userId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): Either[Throwable, (TemporaryReservationResponse, ValuationsResponse)] = - withSession(userId) { session => + def temporaryReservation(accountId: Long, temporaryReservationRequest: TemporaryReservationRequest, valuationsRequest: ValuationsRequest): Either[Throwable, (TemporaryReservationResponse, ValuationsResponse)] = + withSession(accountId) { session => LuxmedApi.temporaryReservation(session.accessToken, session.tokenType, temporaryReservationRequest) match { case Left(ex) => Left(ex) case Right(temporaryReservation) => @@ -97,30 +97,30 @@ class ApiService extends SessionSupport { } } - def deleteTemporaryReservation(userId: Long, temporaryReservationId: Long): Either[Throwable, HttpResponse[String]] = - withSession(userId) { session => + def deleteTemporaryReservation(accountId: Long, temporaryReservationId: Long): Either[Throwable, HttpResponse[String]] = + withSession(accountId) { session => LuxmedApi.deleteTemporaryReservation(session.accessToken, session.tokenType, temporaryReservationId) } - def reservation(userId: Long, reservationRequest: ReservationRequest): Either[Throwable, ReservationResponse] = - withSession(userId) { session => + def reservation(accountId: Long, reservationRequest: ReservationRequest): Either[Throwable, ReservationResponse] = + withSession(accountId) { session => LuxmedApi.reservation(session.accessToken, session.tokenType, reservationRequest) } - def visitsHistory(userId: Long, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1), + def visitsHistory(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now().minusYears(1), toDate: ZonedDateTime = ZonedDateTime.now(), page: Int = 1, pageSize: Int = 100): Either[Throwable, List[HistoricVisit]] = - withSession(userId) { session => + withSession(accountId) { session => LuxmedApi.visitsHistory(session.accessToken, session.tokenType, fromDate, toDate, page, pageSize).map(_.historicVisits) } - def reservedVisits(userId: Long, fromDate: ZonedDateTime = ZonedDateTime.now(), + def reservedVisits(accountId: Long, fromDate: ZonedDateTime = ZonedDateTime.now(), toDate: ZonedDateTime = ZonedDateTime.now().plusMonths(3)): Either[Throwable, List[ReservedVisit]] = - withSession(userId) { session => + withSession(accountId) { session => LuxmedApi.reservedVisits(session.accessToken, session.tokenType, fromDate, toDate).map(_.reservedVisits) } - def deleteReservation(userId: Long, reservationId: Long): Either[Throwable, HttpResponse[String]] = - withSession(userId) { session => + def deleteReservation(accountId: Long, reservationId: Long): Either[Throwable, HttpResponse[String]] = + withSession(accountId) { session => LuxmedApi.deleteReservation(session.accessToken, session.tokenType, reservationId) } diff --git a/server/src/main/scala/com/lbs/server/service/DataService.scala b/server/src/main/scala/com/lbs/server/service/DataService.scala index 0d45055..0425e24 100644 --- a/server/src/main/scala/com/lbs/server/service/DataService.scala +++ b/server/src/main/scala/com/lbs/server/service/DataService.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server.service import java.time.ZonedDateTime @@ -39,10 +39,10 @@ import org.springframework.stereotype.Service class DataService { @Autowired - private var dataRepository: DataRepository = _ + private[service] var dataRepository: DataRepository = _ - def getLatestCities(userId: Long): Seq[IdName] = { - dataRepository.getCityHistory(userId).mapTo[IdName] + def getLatestCities(accountId: Long): Seq[IdName] = { + dataRepository.getCityHistory(accountId).mapTo[IdName] } def getLatestClinicsByCityId(userId: Long, cityId: Long): Seq[IdName] = { @@ -57,8 +57,8 @@ class DataService { dataRepository.getDoctorHistory(userId, cityId, clinicId, serviceId).mapTo[IdName] } - def getCredentials(userId: Long): Option[Credentials] = { - dataRepository.findCredentials(userId) + def getCredentials(accountId: Long): Option[Credentials] = { + dataRepository.findCredentials(accountId) } @Transactional @@ -79,38 +79,56 @@ class DataService { dataRepository.getActiveMonitorings } - def getActiveMonitoringsCount(userId: Long): Long = { - dataRepository.getActiveMonitoringsCount(userId) + def getActiveMonitoringsCount(accountId: Long): Long = { + dataRepository.getActiveMonitoringsCount(accountId) } - def getActiveMonitorings(userId: Long): Seq[Monitoring] = { - dataRepository.getActiveMonitorings(userId) + def getActiveMonitorings(accountId: Long): Seq[Monitoring] = { + dataRepository.getActiveMonitorings(accountId) } - def findActiveMonitoring(userId: Long, cityId: Long, serviceId: Long): Option[Monitoring] = { - dataRepository.findActiveMonitoring(userId, cityId, serviceId) + def findActiveMonitoring(accountId: Long, cityId: Long, serviceId: Long): Option[Monitoring] = { + dataRepository.findActiveMonitoring(accountId, cityId, serviceId) } def getActiveMonitoringsSince(since: ZonedDateTime): Seq[Monitoring] = { dataRepository.getActiveMonitoringsSince(since) } - def findMonitoring(userId: Long, monitoringId: Long): Option[Monitoring] = { - dataRepository.findMonitoring(userId, monitoringId) + def findMonitoring(accountId: Long, monitoringId: Long): Option[Monitoring] = { + dataRepository.findMonitoring(accountId, monitoringId) } def findSettings(userId: Long): Option[Settings] = { dataRepository.findSettings(userId) } - def findUserIdBySource(source: MessageSource): Option[Long] = { - dataRepository.findUserId(source.chatId, source.sourceSystem.id).map(_.toLong) + def findUserAndAccountIdBySource(source: MessageSource): Option[(Long, Long)] = { + val userIdMaybe = dataRepository.findUserId(source.chatId, source.sourceSystem.id).map(_.toLong) + userIdMaybe.flatMap(userId => dataRepository.findAccountId(userId).map(_.toLong).map(accountId => userId -> accountId)) } def findCredentialsByUsername(username: String): Option[Credentials] = { dataRepository.findCredentialsByUsername(username) } + def getUserCredentials(userId: Long): Seq[Credentials] = { + dataRepository.getUserCredentials(userId) + } + + def findUserCredentialsByAccountId(userId: Long, accountId: Long): Option[Credentials] = { + dataRepository.findUserCredentialsByUserIdAndAccountId(userId, accountId) + } + + def findUser(userId: Long): Option[SystemUser] = { + dataRepository.findUser(userId) + } + + @Transactional + def saveUser(user: SystemUser): SystemUser = { + dataRepository.saveEntity(user) + } + @Transactional def saveSettings(settings: Settings): Settings = { dataRepository.saveEntity(settings) @@ -128,36 +146,63 @@ class DataService { val src = Source(source.chatId, source.sourceSystem.id, credentials.userId) dataRepository.saveEntity(src) } + val userMaybe = dataRepository.findUser(credentials.userId) + userMaybe match { + case Some(user) => + user.activeAccountId = credentials.accountId + dataRepository.saveEntity(user) + case None => sys.error(s"Strange, but user [#${credentials.userId}] not found") + } credentials.username = username credentials.password = password dataRepository.saveEntity(credentials) - case None => //new user - val user = dataRepository.saveEntity(new SystemUser) - val src = Source(source.chatId, source.sourceSystem.id, user.recordId) - dataRepository.saveEntity(src) - val credentials = Credentials(user.recordId, username, password) - dataRepository.saveEntity(credentials) + case None => //new user or new account? + val userMaybe = dataRepository.findUserIdBySource(source.chatId, source.sourceSystem.id).flatMap { + userId => dataRepository.findUser(userId) + } + userMaybe match { + case Some(user) => //user already exists, this is just the new credentials + val account = dataRepository.saveEntity(new Account) + user.activeAccountId = account.recordId + dataRepository.saveEntity(user) + val sourceMaybe = dataRepository.findSource(source.chatId, source.sourceSystem.id, user.recordId) + sourceMaybe match { + case Some(_) => //source already exists. Just save credentials + case None => //add new source + val src = Source(source.chatId, source.sourceSystem.id, user.recordId) + dataRepository.saveEntity(src) + } + val credentials = Credentials(user.recordId, account.recordId, username, password) + dataRepository.saveEntity(credentials) + case None => //everything is new + val account = dataRepository.saveEntity(new Account) + val user = dataRepository.saveEntity(SystemUser(account.recordId)) + val src = Source(source.chatId, source.sourceSystem.id, user.recordId) + dataRepository.saveEntity(src) + val credentials = Credentials(user.recordId, account.recordId, username, password) + dataRepository.saveEntity(credentials) + } } } @Transactional - def storeAppointment(userId: Long, bookingData: BookingData): Unit = { + def storeAppointment(accountId: Long, bookingData: BookingData): Unit = { val time = ZonedDateTime.now() val cityId = bookingData.cityId val clinicId = bookingData.clinicId val serviceId = bookingData.serviceId val doctorId = bookingData.doctorId - val city = CityHistory(userId, cityId.id, cityId.name, time) + val city = CityHistory(accountId, cityId.id, cityId.name, time) dataRepository.saveEntity(city) - val clinicMaybe = clinicId.optionalId.map(id => ClinicHistory(userId, id, clinicId.name, cityId.id, time)) + val clinicMaybe = clinicId.optionalId.map(id => ClinicHistory(accountId, id, clinicId.name, cityId.id, time)) clinicMaybe.foreach(dataRepository.saveEntity) - val service = ServiceHistory(userId, serviceId.id, serviceId.name, cityId.id, clinicId.optionalId, time) + val service = ServiceHistory(accountId, serviceId.id, serviceId.name, cityId.id, clinicId.optionalId, time) dataRepository.saveEntity(service) - val doctorMaybe = doctorId.optionalId.map(id => DoctorHistory(userId, id, doctorId.name, cityId.id, clinicId.optionalId, serviceId.id, time)) + val doctorMaybe = doctorId.optionalId.map(id => DoctorHistory(accountId, id, doctorId.name, cityId.id, clinicId.optionalId, serviceId.id, time)) doctorMaybe.foreach(dataRepository.saveEntity) } } diff --git a/server/src/main/scala/com/lbs/server/service/MonitoringService.scala b/server/src/main/scala/com/lbs/server/service/MonitoringService.scala index 28d1536..e393a12 100644 --- a/server/src/main/scala/com/lbs/server/service/MonitoringService.scala +++ b/server/src/main/scala/com/lbs/server/service/MonitoringService.scala @@ -90,7 +90,7 @@ class MonitoringService extends Logger { private def monitor(monitoring: Monitoring): Unit = { LOG.debug(s"Looking for available terms. Monitoring [#${monitoring.recordId}]") val dateFrom = optimizeDateFrom(monitoring.dateFrom) - val termsEither = apiService.getAvailableTerms(monitoring.userId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId, + val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId, monitoring.doctorId, dateFrom, Some(monitoring.dateTo)) termsEither match { case Right(terms) => @@ -108,7 +108,7 @@ class MonitoringService extends Logger { case Left(ex: InvalidLoginOrPasswordException) => LOG.error(s"User entered invalid name or password. Monitoring will be disabled", ex) bot.sendMessage(monitoring.source, lang(monitoring.userId).invalidLoginOrPassword) - val activeUserMonitorings = dataService.getActiveMonitorings(monitoring.userId) + val activeUserMonitorings = dataService.getActiveMonitorings(monitoring.accountId) activeUserMonitorings.foreach { m => deactivateMonitoring(m.recordId) } @@ -172,12 +172,12 @@ class MonitoringService extends Logger { val temporaryReservationRequest = term.mapTo[TemporaryReservationRequest] val valuationsRequest = term.mapTo[ValuationsRequest] val reservationMaybe = for { - okResponse <- apiService.temporaryReservation(monitoring.userId, temporaryReservationRequest, valuationsRequest) + okResponse <- apiService.temporaryReservation(monitoring.accountId, temporaryReservationRequest, valuationsRequest) (temporaryReservation, valuations) = okResponse temporaryReservationId = temporaryReservation.id visitTermVariant = valuations.visitTermVariants.head reservationRequest = (temporaryReservationId, visitTermVariant, term).mapTo[ReservationRequest] - reservation <- apiService.reservation(monitoring.userId, reservationRequest) + reservation <- apiService.reservation(monitoring.accountId, reservationRequest) } yield reservation reservationMaybe match { @@ -203,22 +203,22 @@ class MonitoringService extends Logger { } def createMonitoring(monitoring: Monitoring): Monitoring = { - val userMonitoringsCount = dataService.getActiveMonitoringsCount(monitoring.userId) + val userMonitoringsCount = dataService.getActiveMonitoringsCount(monitoring.accountId) require(userMonitoringsCount + 1 <= 5, lang(monitoring.userId).maximumMonitoringsLimitExceeded) - val activeMonitoring = dataService.findActiveMonitoring(monitoring.userId, monitoring.cityId, monitoring.serviceId) + val activeMonitoring = dataService.findActiveMonitoring(monitoring.accountId, monitoring.cityId, monitoring.serviceId) require(activeMonitoring.isEmpty, lang(monitoring.userId).monitoringOfTheSameTypeExists) dataService.saveMonitoring(monitoring) } - def getActiveMonitorings(userId: Long): Seq[Monitoring] = { - dataService.getActiveMonitorings(userId) + def getActiveMonitorings(accountId: Long): Seq[Monitoring] = { + dataService.getActiveMonitorings(accountId) } - def bookAppointmentByScheduleId(userId: Long, monitoringId: Long, scheduleId: Long, time: Long): Unit = { - val monitoringMaybe = dataService.findMonitoring(userId, monitoringId) + def bookAppointmentByScheduleId(accountId: Long, monitoringId: Long, scheduleId: Long, time: Long): Unit = { + val monitoringMaybe = dataService.findMonitoring(accountId, monitoringId) monitoringMaybe match { case Some(monitoring) => - val termsEither = apiService.getAvailableTerms(monitoring.userId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId, + val termsEither = apiService.getAvailableTerms(monitoring.accountId, monitoring.cityId, monitoring.clinicId, monitoring.serviceId, monitoring.doctorId, monitoring.dateFrom, Some(monitoring.dateTo)) termsEither match { case Right(terms) => @@ -254,6 +254,7 @@ class MonitoringService extends Logger { val monitorings = dataService.getActiveMonitorings LOG.debug(s"Active monitorings found: ${monitorings.length}") initializeMonitorings(monitorings) + disableOutdated() initializeDbChecker() } } diff --git a/server/src/main/scala/com/lbs/server/service/SessionSupport.scala b/server/src/main/scala/com/lbs/server/service/SessionSupport.scala index 9919350..554bde7 100644 --- a/server/src/main/scala/com/lbs/server/service/SessionSupport.scala +++ b/server/src/main/scala/com/lbs/server/service/SessionSupport.scala @@ -44,26 +44,26 @@ trait SessionSupport { private val lock = new ParametrizedLock[Long] - protected def withSession[T](userId: Long)(fn: Session => Either[Throwable, T]): Either[Throwable, T] = - lock.obtainLock(userId).synchronized { + protected def withSession[T](accountId: Long)(fn: Session => Either[Throwable, T]): Either[Throwable, T] = + lock.obtainLock(accountId).synchronized { def auth: Either[Throwable, Session] = { - val credentialsMaybe = dataService.getCredentials(userId) + val credentialsMaybe = dataService.getCredentials(accountId) credentialsMaybe match { case Some(credentials) => val loginResponse = login(credentials.username, credentials.password) loginResponse.map(r => Session(r.accessToken, r.tokenType)) - case None => Left(UserNotFoundException(userId)) + case None => Left(UserNotFoundException(accountId)) } } def session: Either[Throwable, Session] = { - sessions.get(userId) match { + sessions.get(accountId) match { case Some(sess) => Right(sess) case None => auth match { case Right(sess) => - sessions.put(userId, sess) + sessions.put(accountId, sess) Right(sess) case left => left } @@ -74,8 +74,8 @@ trait SessionSupport { case Right(s) => fn(s) match { case Left(ex) if ex.getMessage.contains("session has expired") => - Log.debug(s"The session for user [#$userId] has expired. Try to relogin") - sessions.remove(userId) + Log.debug(s"The session for account [#$accountId] has expired. Try to relogin") + sessions.remove(accountId) session.flatMap(fn) case another => Log.debug(s"Call to remote api function has completed with result:\n$another") @@ -85,8 +85,8 @@ trait SessionSupport { } } - def addSession(userId: Long, accessToken: String, tokenType: String): Unit = - lock.obtainLock(userId).synchronized { - sessions.put(userId, Session(accessToken, tokenType)) + def addSession(accountId: Long, accessToken: String, tokenType: String): Unit = + lock.obtainLock(accountId).synchronized { + sessions.put(accountId, Session(accessToken, tokenType)) } } diff --git a/server/src/main/scala/com/lbs/server/util/package.scala b/server/src/main/scala/com/lbs/server/util/package.scala index 186a7a9..c999a06 100644 --- a/server/src/main/scala/com/lbs/server/util/package.scala +++ b/server/src/main/scala/com/lbs/server/util/package.scala @@ -1,26 +1,26 @@ /** - * MIT License - * - * Copyright (c) 2018 Yevhen Zadyra - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ + * MIT License + * + * Copyright (c) 2018 Yevhen Zadyra + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ package com.lbs.server import java.time.format.DateTimeFormatter @@ -49,6 +49,7 @@ package object util { val (userId, bookingData) = data.asInstanceOf[(UserId, BookingData)] Monitoring( userId = userId.userId, + accountId = userId.accountId, chatId = userId.source.chatId, sourceSystemId = userId.source.sourceSystem.id, cityId = bookingData.cityId.id, diff --git a/server/src/test/scala/com/lbs/server/actor/AuthSpec.scala b/server/src/test/scala/com/lbs/server/actor/AuthSpec.scala index 1f3b2a8..acd754b 100644 --- a/server/src/test/scala/com/lbs/server/actor/AuthSpec.scala +++ b/server/src/test/scala/com/lbs/server/actor/AuthSpec.scala @@ -13,7 +13,7 @@ class AuthSpec extends AkkaTestKit { "An Auth actor " when { val source = MessageSource(TelegramMessageSourceSystem, "1") - val userId = UserId(1L, source) + val userId = UserId(1L, 1L, source) "user is unauthorized" must { val unauthorizedHelpActor = TestProbe() @@ -23,7 +23,7 @@ class AuthSpec extends AkkaTestKit { val loginActorFactory: ByMessageSourceWithOriginatorActorFactory = (_, _) => loginActor.ref val chatActorFactory: UserId => ActorRef = _ => chatActor.ref val dataService = mock(classOf[DataService]) - when(dataService.findUserIdBySource(source)).thenReturn(None) + when(dataService.findUserAndAccountIdBySource(source)).thenReturn(None) val auth = system.actorOf(Auth.props(source, dataService, unauthorizedHelpFactory, loginActorFactory, chatActorFactory)) @@ -57,7 +57,7 @@ class AuthSpec extends AkkaTestKit { "forward initial message to chat actor after the user has logged in" in { val cmd = Command(source, Message("1", Some("any"))) - val msg = LoggedIn(ForwardCommand(cmd), 1L) + val msg = LoggedIn(ForwardCommand(cmd), 1L, 1L) auth ! msg chatActor.expectMsg(cmd) } @@ -79,7 +79,7 @@ class AuthSpec extends AkkaTestKit { val loginActorFactory: ByMessageSourceWithOriginatorActorFactory = (_, _) => loginActor.ref val chatActorFactory: UserId => ActorRef = _ => chatActor.ref val dataService = mock(classOf[DataService]) - when(dataService.findUserIdBySource(source)).thenReturn(Some(userId.userId)) + when(dataService.findUserAndAccountIdBySource(source)).thenReturn(Some(userId.userId, userId.accountId)) val auth = system.actorOf(Auth.props(source, dataService, unauthorizedHelpFactory, loginActorFactory, chatActorFactory))