Multiple account support

This commit is contained in:
Eugene Zadyra
2018-06-07 12:40:49 +02:00
parent 550d52881a
commit f935f71d0d
28 changed files with 665 additions and 368 deletions

View File

@@ -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) =>

View File

@@ -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)
}

View File

@@ -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 = {

View File

@@ -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()

View File

@@ -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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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 <b>$username</b>"
override def pleaseChooseAccount: String = "<b>➡</b> Please choose an <b>action</b> or select <b>account</b>"
}

View File

@@ -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
}

View File

@@ -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"✅ Аккаунт було переключено на <b>$username</b>"
override def pleaseChooseAccount: String = "<b>➡</b> Будь ласка, оберіть <b>дію</b> або виберіть <b>акаунт</b>"
}

View File

@@ -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)
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
}

View File

@@ -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)
}

View File

@@ -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)
}
}

View File

@@ -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()
}
}

View File

@@ -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))
}
}

View File

@@ -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,

View File

@@ -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))