diff --git a/src/main/scala/ru/org/codingteam/horta/core/Core.scala b/src/main/scala/ru/org/codingteam/horta/core/Core.scala index c31ca46..87fffc9 100644 --- a/src/main/scala/ru/org/codingteam/horta/core/Core.scala +++ b/src/main/scala/ru/org/codingteam/horta/core/Core.scala @@ -4,7 +4,7 @@ import akka.actor._ import akka.pattern.ask import akka.util.Timeout import org.joda.time.DateTime -import ru.org.codingteam.horta.database.{DAO, PersistentStore} +import ru.org.codingteam.horta.database.{RepositoryFactory, PersistentStore} import ru.org.codingteam.horta.messages._ import ru.org.codingteam.horta.plugins.HelperPlugin.HelperPlugin import ru.org.codingteam.horta.plugins._ @@ -212,8 +212,7 @@ class Core extends Actor with ActorLogging { object Core { - private def getCommands(pluginDefinitions: List[(ActorRef, PluginDefinition)] - ): Map[String, List[(ActorRef, CommandDefinition)]] = { + private def getCommands(pluginDefinitions: List[(ActorRef, PluginDefinition)]): Map[String, List[(ActorRef, CommandDefinition)]] = { val commands = for ((actor, pluginDefinition) <- pluginDefinitions) yield { for (command <- pluginDefinition.commands) yield (command.name, actor, command) } @@ -228,8 +227,10 @@ object Core { private def getCommandsDescription(pluginDefinitions: List[(ActorRef, PluginDefinition)]) = pluginDefinitions.map(t => t._2.name -> t._2.commands.map(cd => cd.name -> cd.level)).toMap - private def getStorages(pluginDefinitions: List[(ActorRef, PluginDefinition)]): Map[String, DAO] = { - pluginDefinitions.map(_._2).filter(_.dao.isDefined).map(definition => (definition.name, definition.dao.get)).toMap + private def getStorages(pluginDefinitions: Seq[(ActorRef, PluginDefinition)]): Map[String, RepositoryFactory] = { + pluginDefinitions.toStream.flatMap { case (_, definition) => + definition.repositoryFactory.map(factory => (definition.name, factory)) + }.toMap } } diff --git a/src/main/scala/ru/org/codingteam/horta/database/PersistentStore.scala b/src/main/scala/ru/org/codingteam/horta/database/PersistentStore.scala index 45d1614..93f64dc 100644 --- a/src/main/scala/ru/org/codingteam/horta/database/PersistentStore.scala +++ b/src/main/scala/ru/org/codingteam/horta/database/PersistentStore.scala @@ -2,58 +2,20 @@ package ru.org.codingteam.horta.database import javax.sql.DataSource -import akka.actor.{Actor, ActorLogging} +import akka.actor.{Actor, ActorLogging, ActorSelection} +import akka.pattern.ask import akka.util.Timeout import com.googlecode.flyway.core.Flyway import org.h2.jdbcx.JdbcConnectionPool import ru.org.codingteam.horta.configuration.Configuration import scalikejdbc.{ConnectionPool, DB, DBSession, DataSourceConnectionPool} +import scala.concurrent.Future import scala.concurrent.duration._ import scala.language.postfixOps +import scala.reflect.ClassTag -case class StoreObject(plugin: String, id: Option[Any], obj: Any) - -case class ReadObject(plugin: String, id: Any) - -case class DeleteObject(plugin: String, id: Any) - -trait DAO { - - /** - * Schema name for current DAO. - * @return schema name. - */ - def schema: String - - /** - * Store an object in the database. - * @param session session to access the database. - * @param id object id (if null then it should be generated). - * @param obj stored object. - * @return stored object id (or None if object was not stored). - */ - def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] - - /** - * Read an object from the database. - * @param session session to access the database. - * @param id object id. - * @return stored object or None if object not found. - */ - def read(implicit session: DBSession, id: Any): Option[Any] - - /** - * Delete an object from the database. - * @param session session to access the database. - * @param id object id. - * @return true if object was successfully deleted, false otherwise. - */ - def delete(implicit session: DBSession, id: Any): Boolean - -} - -class PersistentStore(storages: Map[String, DAO]) extends Actor with ActorLogging { +class PersistentStore(repositories: Map[String, RepositoryFactory]) extends Actor with ActorLogging { val Url = Configuration.storageUrl val User = Configuration.storageUser @@ -70,45 +32,22 @@ class PersistentStore(storages: Map[String, DAO]) extends Actor with ActorLoggin } override def receive = { - case StoreObject(plugin, id, obj) => - storages.get(plugin) match { - case Some(dao) => - initializeDatabase(dao) - withTransaction { session => - sender ! dao.store(session, id, obj) - } - - case None => - log.info(s"Cannot store object $obj for plugin $plugin") - } - - case ReadObject(plugin, id) => - storages.get(plugin) match { - case Some(dao) => - initializeDatabase(dao) - withTransaction { session => - sender ! dao.read(session, id) - } - - case None => - log.info(s"Cannot read object $id for plugin $plugin") - } - - case DeleteObject(plugin, id) => - storages.get(plugin) match { - case Some(dao) => - initializeDatabase(dao) + case PersistentStore.Execute(plugin, action) => + repositories.get(plugin) match { + case Some(factory) => + initializeDatabase(factory) withTransaction { session => - sender ! dao.delete(session, id) + val repository = factory.create(session) + sender ! action(repository) } case None => - log.info(s"Cannot delete object $id for plugin $plugin") + log.info(s"Cannot execute action $action for plugin $plugin: repository not found") } } - private def initializeDatabase(dao: DAO) { - val schema = dao.schema + private def initializeDatabase(factory: RepositoryFactory) { + val schema = factory.schema if (!initializedSchemas.contains(schema)) { initializeScript(schema) initializedSchemas += schema @@ -133,4 +72,18 @@ class PersistentStore(storages: Map[String, DAO]) extends Actor with ActorLoggin flyway.repair() flyway.migrate() } + +} + +object PersistentStore { + + private case class Execute(plugin: String, action: (Any) => Any) + + def execute[Repository, T: ClassTag](plugin: String, store: ActorSelection) + (action: (Repository) => T) + (implicit timeout: Timeout): Future[T] = { + val message = Execute(plugin, (r) => action(r.asInstanceOf[Repository])) + (store ? message).mapTo[T] + } + } diff --git a/src/main/scala/ru/org/codingteam/horta/database/RepositoryFactory.scala b/src/main/scala/ru/org/codingteam/horta/database/RepositoryFactory.scala new file mode 100644 index 0000000..a2cfd1d --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/database/RepositoryFactory.scala @@ -0,0 +1,11 @@ +package ru.org.codingteam.horta.database + +import scalikejdbc.DBSession + +/** + * A repository factory. Will be used to create the repository. + * + * @param schema schema name for current repository. + * @param create a repository creation function. + */ +case class RepositoryFactory(schema: String, create: (DBSession => Any)) diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/BasePlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/BasePlugin.scala index e02794f..f0cfc3d 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/BasePlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/BasePlugin.scala @@ -1,18 +1,17 @@ package ru.org.codingteam.horta.plugins import akka.actor.{Actor, ActorLogging} -import ru.org.codingteam.horta.database.DAO /** - * CommandPlugin class used as base for all command plugins. + * Common plugin functionality. Every plugin should be inherited from this class. */ abstract class BasePlugin extends Actor with ActorLogging { + def receive = { case GetPluginDefinition => sender ! pluginDefinition } protected val core = context.actorSelection("/user/core") - protected val store = context.actorSelection("/user/core/store") /** * Plugin name. @@ -32,15 +31,10 @@ abstract class BasePlugin extends Actor with ActorLogging { */ protected def commands: List[CommandDefinition] = List() - /** - * Plugin data access object. May be None if not present. - * @return data access object. - */ - protected def dao: Option[DAO] = None - /** * A full plugin definition. * @return plugin definition. */ - private def pluginDefinition = PluginDefinition(name, notifications, commands, dao) + protected def pluginDefinition = PluginDefinition(name, notifications, commands, None) + } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/DataAccessingPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/DataAccessingPlugin.scala new file mode 100644 index 0000000..0dd5e49 --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/plugins/DataAccessingPlugin.scala @@ -0,0 +1,47 @@ +package ru.org.codingteam.horta.plugins + +import akka.util.Timeout +import ru.org.codingteam.horta.database.{RepositoryFactory, PersistentStore} +import scalikejdbc.DBSession + +import scala.concurrent.Future +import scala.reflect.ClassTag + +/** + * Trait for the plugins that can access the database. + */ +trait DataAccessingPlugin[Repository] extends BasePlugin { + + /** + * A full plugin definition. + * @return plugin definition. + */ + override protected def pluginDefinition: PluginDefinition = + super.pluginDefinition.copy(repositoryFactory = Some(RepositoryFactory(schema, createRepository))) + + /** + * Schema name for database storage. + */ + protected val schema: String + + /** + * A function creating the repository for database access. + */ + protected val createRepository: (DBSession) => Repository + + /** + * Execute repository action. Use with caution - do not mess with plugin data members within action; that is not + * context-safe. + * + * @param action an action. + * @tparam T type of action result. + * @return future that will be resolved after action execution. + */ + protected def withDatabase[T: ClassTag](action: (Repository) => T) + (implicit timeout: Timeout): Future[T] = { + PersistentStore.execute[Repository, T](name, store)(action) + } + + private val store = context.actorSelection("/user/core/store") + +} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/PluginDefinition.scala b/src/main/scala/ru/org/codingteam/horta/plugins/PluginDefinition.scala index 6b9cc07..1d69d52 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/PluginDefinition.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/PluginDefinition.scala @@ -1,6 +1,6 @@ package ru.org.codingteam.horta.plugins -import ru.org.codingteam.horta.database.DAO +import ru.org.codingteam.horta.database.RepositoryFactory /** * Description of events. @@ -17,9 +17,10 @@ case class Notifications(messages: Boolean, * @param name plugin name. * @param notifications description of events plugin want to be notified of. * @param commands a list of commands supported by the plugin. - * @param dao plugin data access object if present. + * @param repositoryFactory a factory of repositories used for data access. Data access is disabled if factory isn't + * defined. */ case class PluginDefinition(name: String, notifications: Notifications, commands: List[CommandDefinition], - dao: Option[DAO]) + repositoryFactory: Option[RepositoryFactory]) diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaDAO.scala b/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaDAO.scala deleted file mode 100644 index 31139c7..0000000 --- a/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaDAO.scala +++ /dev/null @@ -1,106 +0,0 @@ -package ru.org.codingteam.horta.plugins.karma - -import java.sql.Date - -import org.joda.time.{DateTime, Period} -import ru.org.codingteam.horta.core.Clock -import ru.org.codingteam.horta.database.DAO -import scalikejdbc._ - -case class SetKarma(room: String, user:String, member: String, karma: Int) -case class GetKarma(room: String, member: String) -case class GetTopKarma(room: String) -case class GetLastChangeTime(room: String, user:String) - -class KarmaDAO extends DAO { - - override def schema: String = "Karma" - - override def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] = { - id match { - case Some(SetKarma(room, user, member, karma)) => - querySetKarma(session, room, user, member, karma) - case _ => sys.error("Unknown argument for KarmaDAO.store") - } - } - - override def delete(implicit session: DBSession, id: Any): Boolean = ??? - - override def read(implicit session: DBSession, id: Any): Option[Any] = { - id match { - case GetKarma(room, member) => - queryKarma(session, room, member) - case GetTopKarma(room) => - queryTopKarma(session, room) - case GetLastChangeTime(room, user) => - queryLastChange(session, room, user) - case _ => sys.error("Unknown argument for KarmaDAO.read") - } - } - - private def queryLastChange(implicit session: DBSession, room: String, member: String): Option[DateTime] = { - sql"""select changetime - from KarmaChanges - where room = $room and member = $member - """.map(rs => rs.jodaDateTime("changetime")).single().apply() - } - - private def queryChangeIsPresentInDB(implicit session: DBSession, room: String, member: String): Boolean = { - sql"""select exists (select * - from KarmaChanges - where room = $room and member = $member) - """.map(rs => (rs.boolean(1))).single().apply().getOrElse(false) - } - - private def querySetLastChange(implicit session: DBSession, room: String, member: String): Unit = { - if (queryChangeIsPresentInDB(session, room, member)) { - sql"""update KarmaChanges - set changetime=${Clock.now} - where room = $room and member = $member - """.update().apply() - } else { - sql"""insert into KarmaChanges (room,member,changetime) - values ($room, $member, ${Clock.now}) - """.update().apply() - } - } - - private def queryKarma(implicit session: DBSession, room: String, member: String): Option[Int] = { - sql"""select karma - from Karma - where room = $room and member = $member - """.map(rs => rs.int("karma")).single().apply() - } - - private def queryTopKarma(implicit session: DBSession, room: String): Option[List[String]] = { - Option(sql"""select top 5 member, karma - from Karma - where room = $room - order by karma desc - """.map(rs => rs.string("member") + " " + rs.string("karma")).list().apply()) - } - - private def queryKarmaIsPresentInDB(implicit session: DBSession, room: String, member: String): Option[Long] = { - sql"""select id - from Karma - where room = $room and member = $member - """.map(rs => rs.long(1)).single().apply() - } - - private def querySetKarma(implicit session: DBSession, room: String, user: String, member: String, karma: Int): Option[Long] = { - querySetLastChange(session, room, user) - - queryKarmaIsPresentInDB(session, room, member) match { - case Some(key: Long) => - sql"""update Karma - set karma=karma + $karma - where room = $room and member = $member - """.update().apply() - Some(key) - case None => - Option(sql"""insert into Karma (room,member,karma) - values ($room, $member, $karma) - """.updateAndReturnGeneratedKey().apply()) - } - } -} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaPlugin.scala index 897b982..f9cfecf 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaPlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaPlugin.scala @@ -1,17 +1,14 @@ package ru.org.codingteam.horta.plugins.karma - import akka.util.Timeout -import akka.pattern._ -import org.joda.time.{Period, DateTime} +import org.joda.time.{DateTime, Period} import ru.org.codingteam.horta.core.Clock import ru.org.codingteam.horta.localization.Localization -import scala.concurrent.duration._ -import ru.org.codingteam.horta.database.{ReadObject, StoreObject, DAO} -import ru.org.codingteam.horta.plugins.{CommandDefinition, CommandProcessor, BasePlugin} +import ru.org.codingteam.horta.plugins.{BasePlugin, CommandDefinition, CommandProcessor, DataAccessingPlugin} import ru.org.codingteam.horta.protocol.Protocol import ru.org.codingteam.horta.security.{CommonAccess, Credential} +import scala.concurrent.duration._ private object KarmaCommand object KarmaAction extends Enumeration { @@ -22,9 +19,9 @@ object KarmaAction extends Enumeration { val KarmaUp = "+" val KarmaDown = "-" } -import KarmaAction._ +import ru.org.codingteam.horta.plugins.karma.KarmaAction._ -class KarmaPlugin extends BasePlugin with CommandProcessor { +class KarmaPlugin extends BasePlugin with CommandProcessor with DataAccessingPlugin[KarmaRepository] { val HELP_MESSAGE = s"karma $KarmaShow [username]\nkarma $KarmaTop\nkarma username $KarmaUp/$KarmaDown" @@ -32,7 +29,8 @@ class KarmaPlugin extends BasePlugin with CommandProcessor { override def name = "KarmaPlugin" - override protected def dao: Option[DAO] = Some(new KarmaDAO()) + override val schema = "Karma" + override val createRepository = KarmaRepository.apply _ implicit val timeout = Timeout(60.seconds) import context.dispatcher @@ -69,21 +67,18 @@ class KarmaPlugin extends BasePlugin with CommandProcessor { Protocol.sendResponse(credential.location, credential, message) private def showTopKarma(credential: Credential, room:String): Unit = { - ((store ? ReadObject(name, GetTopKarma(room))) map { - case Some(karma:List[String]) => - "\n" + karma.map(msg => msg).mkString("\n") - }).onSuccess({case msg => sendResponse(credential,msg)}) + withDatabase(_.getTopKarma(room)) map { karma => + val msg = "\n" + karma.mkString("\n") + sendResponse(credential, msg) + } } private def showKarma(credential: Credential, room: String, user: String): Unit = { val template = Localization.localize("%s's karma")(credential) val text = template.format(user) - ((store ? ReadObject(name, GetKarma(room, user))) map { - case Some(karma:Int) => - s"$text: $karma" - case _ => - s"$text: 0" - }).onSuccess({case msg => sendResponse(credential, msg)}) + withDatabase(_.getKarma(room, user)) map { karma => + sendResponse(credential, s"$text: $karma") + } } private def changeKarma(credential: Credential, room:String, user: String, value: Int): Unit = { @@ -91,20 +86,23 @@ class KarmaPlugin extends BasePlugin with CommandProcessor { if (credential.name == user) sendResponse(credential, Localization.localize("You cannot change your karma.")) else { - ((store ? ReadObject(name, GetLastChangeTime(room, credential.name))) map { - case Some(time:DateTime) => - new Period(time, Clock.now).toDurationFrom(DateTime.now).getStandardHours > PERIOD_BETWEEN_CHANGES - case None => - true - }).onSuccess({ - case canChangeCarma if canChangeCarma => { - store ? StoreObject(name, Some(SetKarma(credential.roomId.getOrElse("unknown"), credential.name, user, value)), None) - val template = Localization.localize("%s's karma changed") - sendResponse(credential, template.format(user)) + withDatabase(_.getLastChangeTime(room, credential.name)) map { timeOption => + val canChangeCarma = timeOption match { + case Some(time) => + new Period(time, Clock.now).toDurationFrom(DateTime.now).getStandardHours > PERIOD_BETWEEN_CHANGES + case None => + true } - case _ => + if (canChangeCarma) { + withDatabase(_.setKarma(credential.roomId.getOrElse("unknown"), credential.name, user, value)) map { _ => + val template = Localization.localize("%s's karma changed") + sendResponse(credential, template.format(user)) + } + } else { sendResponse(credential, Localization.localize("You cannot change karma too fast.")) - }) + } + } } } + } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaRepository.scala b/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaRepository.scala new file mode 100644 index 0000000..62f92c8 --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/plugins/karma/KarmaRepository.scala @@ -0,0 +1,77 @@ +package ru.org.codingteam.horta.plugins.karma + +import org.joda.time.DateTime +import ru.org.codingteam.horta.core.Clock +import scalikejdbc._ + +case class KarmaRepository(session: DBSession) { + + implicit val s = session + + def getTopKarma(room: String): List[String] = { + sql"""select top 5 member, karma + from Karma + where room = $room + order by karma desc + """.map(rs => rs.string("member") + " " + rs.string("karma")).list().apply() + } + + def getKarma(room: String, member: String): Option[Int] = { + sql"""select karma + from Karma + where room = $room and member = $member + """.map(rs => rs.int("karma")).single().apply() + } + + def getLastChangeTime(room: String, member: String): Option[DateTime] = { + sql"""select changetime + from KarmaChanges + where room = $room and member = $member + """.map(rs => rs.jodaDateTime("changetime")).single().apply() + } + + def setKarma(room: String, user: String, member: String, karma: Int): Option[Long] = { + setLastChangeTime(room, user) + + karmaIsPresentInDB(room, member) match { + case Some(key: Long) => + sql"""update Karma + set karma=karma + $karma + where room = $room and member = $member + """.update().apply() + Some(key) + case None => + Option(sql"""insert into Karma (room,member,karma) + values ($room, $member, $karma) + """.updateAndReturnGeneratedKey().apply()) + } + } + + private def karmaIsPresentInDB(room: String, member: String): Option[Long] = { + sql"""select id + from Karma + where room = $room and member = $member + """.map(rs => rs.long(1)).single().apply() + } + + private def setLastChangeTime(room: String, member: String): Unit = { + if (changeIsPresentInDB(room, member)) { + sql"""update KarmaChanges + set changetime=${Clock.now} + where room = $room and member = $member + """.update().apply() + } else { + sql"""insert into KarmaChanges (room,member,changetime) + values ($room, $member, ${Clock.now}) + """.update().apply() + } + } + + private def changeIsPresentInDB(room: String, member: String): Boolean = { + sql"""select exists (select * + from KarmaChanges + where room = $room and member = $member) + """.map(rs => (rs.boolean(1))).single().apply().getOrElse(false) + } + +} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/log/LogDAO.scala b/src/main/scala/ru/org/codingteam/horta/plugins/log/LogDAO.scala deleted file mode 100644 index e447dff..0000000 --- a/src/main/scala/ru/org/codingteam/horta/plugins/log/LogDAO.scala +++ /dev/null @@ -1,67 +0,0 @@ -package ru.org.codingteam.horta.plugins.log - -import ru.org.codingteam.horta.database.DAO -import scalikejdbc._ - -case class GetMessages(room: String, phrase: String) - -/** - * Data access object for room log storage. - */ -class LogDAO extends DAO { - - val MAX_MESSAGES_IN_RESULT = 5 - - override def schema: String = "log" - - /** - * Store an object in the database. - * @param session session to access the database. - * @param id object id (if null then it should be generated). - * @param obj stored object. - * @return stored object id (or None if object was not stored). - */ - override def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] = { - val LogMessage(_, time, room, sender, eventType, text) = obj - val id = sql"""insert into Log (time, room, sender, type, message) - values ($time, $room, $sender, ${eventType.name}, $text)""" - .updateAndReturnGeneratedKey().apply() - Some(id) - } - - /** - * Delete an object from the database. - * @param session session to access the database. - * @param id object id. - * @return true if object was successfully deleted, false otherwise. - */ - override def delete(implicit session: DBSession, id: Any): Boolean = ??? - - /** - * Read an object from the database. - * @param session session to access the database. - * @param id object id. - * @return stored object or None if object not found. - */ - override def read(implicit session: DBSession, id: Any): Option[Any] = { - id match { - case GetMessages(room, phrase) => - queryRoomMessages(session, room, phrase) - } - } - - private def queryRoomMessages(implicit session: DBSession, room: String, phrase: String): Option[Seq[LogMessage]] = { - val result = sql"""select top $MAX_MESSAGES_IN_RESULT id, time, sender, type, message - from Log - where room = $room and message like ${"%" + phrase + "%"} - """.map(rs => LogMessage( - Some(rs.int("id")), - rs.jodaDateTime("time"), - room, - rs.string("sender"), - EventType(rs.string("type")), - rs.string("message"))).list().apply() - Some(result) - } - -} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/log/LogPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/log/LogPlugin.scala index 855ecac..d197848 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/log/LogPlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/log/LogPlugin.scala @@ -1,11 +1,9 @@ package ru.org.codingteam.horta.plugins.log import akka.actor.ActorRef -import akka.pattern._ import akka.util.Timeout import org.jivesoftware.smack.util.StringUtils import org.joda.time.DateTime -import ru.org.codingteam.horta.database.{ReadObject, DAO, StoreObject} import ru.org.codingteam.horta.localization.Localization import ru.org.codingteam.horta.messages._ import ru.org.codingteam.horta.plugins._ @@ -17,7 +15,11 @@ import scala.concurrent.duration._ object SearchLogCommand -class LogPlugin extends BasePlugin with ParticipantProcessor with MessageProcessor with CommandProcessor { +class LogPlugin extends BasePlugin + with ParticipantProcessor + with MessageProcessor + with CommandProcessor + with DataAccessingPlugin[LogRepository] { implicit val timeout = Timeout(60.seconds) @@ -27,7 +29,8 @@ class LogPlugin extends BasePlugin with ParticipantProcessor with MessageProcess override protected def name: String = "log" - override protected def dao: Option[DAO] = Some(new LogDAO()) + override protected val schema: String = "log" + override protected val createRepository = LogRepository.apply _ override protected def commands: List[CommandDefinition] = List( CommandDefinition(CommonAccess, "search", SearchLogCommand)) @@ -78,7 +81,7 @@ class LogPlugin extends BasePlugin with ParticipantProcessor with MessageProcess eventType: EventType, text: String) { val message = LogMessage(None, time, roomJID, sender, eventType, text) - store ? StoreObject(name, None, message) + withDatabase(_.store(message)) } private def getReasonText(reason: LeaveReason) = { @@ -91,9 +94,8 @@ class LogPlugin extends BasePlugin with ParticipantProcessor with MessageProcess } private def getSearchResponse(room:String, phrase: String): Future[String] = { - (store ? ReadObject(name, GetMessages(room, phrase))) map { - case Some(messages: Seq[LogMessage]) => - messages.map(message => s"${message.time} ${message.sender} ${prepareMessageText(message.text)}").mkString("\n") + withDatabase(_.getMessages(room, phrase)) map { messages => + messages.map(message => s"${message.time} ${message.sender} ${prepareMessageText(message.text)}").mkString("\n") } } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/log/LogRepository.scala b/src/main/scala/ru/org/codingteam/horta/plugins/log/LogRepository.scala new file mode 100644 index 0000000..204986b --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/plugins/log/LogRepository.scala @@ -0,0 +1,37 @@ +package ru.org.codingteam.horta.plugins.log + +import scalikejdbc._ + +/** + * Repository for room log storage. + */ +case class LogRepository(session: DBSession) { + + val MAX_MESSAGES_IN_RESULT = 5 + + implicit val s = session + + def store(message: LogMessage): Unit = { + message match { + case LogMessage(_, time, room, sender, eventType, text) => + sql"""insert into Log (time, room, sender, type, message) + values ($time, $room, $sender, ${eventType.name}, $text)""" + .updateAndReturnGeneratedKey().apply() + } + } + + def getMessages(room: String, phrase: String): Seq[LogMessage] = { + val result = sql"""select top $MAX_MESSAGES_IN_RESULT id, time, sender, type, message + from Log + where room = $room and message like ${"%" + phrase + "%"} + """.map(rs => LogMessage( + Some(rs.int("id")), + rs.jodaDateTime("time"), + room, + rs.string("sender"), + EventType(rs.string("type")), + rs.string("message"))).list().apply() + result + } + +} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailDAO.scala b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailDAO.scala deleted file mode 100644 index 58c01a4..0000000 --- a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailDAO.scala +++ /dev/null @@ -1,49 +0,0 @@ -package ru.org.codingteam.horta.plugins.mail - -import ru.org.codingteam.horta.database.DAO -import scalikejdbc._ - -class MailDAO extends DAO { - - override def schema = "mail" - - /** - * Store new message in a database. - * @param session session to access the database. - * @param id object id (always None). - * @param obj a MailMessage instance. - * @return stored object id (or None if object was not stored). - */ - override def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] = { - val MailMessage(_, room, senderNick, receiverNick, text) = obj - val id = sql"insert into Mail (room, sender, receiver, message) values ($room, $senderNick, $receiverNick, $text)" - .updateAndReturnGeneratedKey().apply() - Some(id) - } - - /** - * Read the messages from the database. - * @param session session to access the database. - * @param id tuple (room, receiverNick). - * @return stored message sequence. - */ - override def read(implicit session: DBSession, id: Any): Option[Any] = { - val (room: String, receiverNick: String) = id - val result = sql"select id, sender, message from Mail where room = $room and receiver = $receiverNick".map( - rs => MailMessage(Some(rs.int("id")), room, rs.string("sender"), receiverNick, rs.string("message"))) - .list().apply() - Some(result) - } - - /** - * Delete the message from the database. - * @param session session to access the database. - * @param anyId object id. - * @return true if object was successfully deleted, false otherwise. - */ - override def delete(implicit session: DBSession, anyId: Any): Boolean = { - val id = anyId.asInstanceOf[Int] - sql"delete from mail where id = $id".update().apply() == 1 - } - -} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailMessage.scala b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailMessage.scala index 2751f8f..b65d8fa 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailMessage.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailMessage.scala @@ -1,3 +1,3 @@ package ru.org.codingteam.horta.plugins.mail -private case class MailMessage(id: Option[Int], room: String, senderNick: String, receiverNick: String, text: String) +case class MailMessage(id: Option[Int], room: String, senderNick: String, receiverNick: String, text: String) diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailPlugin.scala index 03542f1..7904c5a 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailPlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailPlugin.scala @@ -1,25 +1,27 @@ package ru.org.codingteam.horta.plugins.mail import akka.actor.ActorRef -import akka.pattern.ask import akka.util.Timeout import org.jivesoftware.smack.util.StringUtils import org.joda.time.DateTime -import ru.org.codingteam.horta.database.{DeleteObject, StoreObject, ReadObject} import ru.org.codingteam.horta.localization.Localization import ru.org.codingteam.horta.messages.LeaveReason -import ru.org.codingteam.horta.plugins.{CommandDefinition, CommandProcessor, ParticipantProcessor, BasePlugin} +import ru.org.codingteam.horta.plugins._ import ru.org.codingteam.horta.protocol.Protocol -import ru.org.codingteam.horta.security.{Credential, CommonAccess} +import ru.org.codingteam.horta.security.{CommonAccess, Credential} + import scala.concurrent.duration._ -import scala.concurrent.Future +import scala.concurrent.{Future, Promise} private object SendMailCommand /** * Plugin for delivering the mail. */ -class MailPlugin extends BasePlugin with CommandProcessor with ParticipantProcessor { +class MailPlugin extends BasePlugin + with CommandProcessor + with ParticipantProcessor + with DataAccessingPlugin[MailRepository] { import context.dispatcher @@ -31,7 +33,8 @@ class MailPlugin extends BasePlugin with CommandProcessor with ParticipantProces override def commands = List(CommandDefinition(CommonAccess, "send", SendMailCommand)) - override def dao = Some(new MailDAO()) + override protected val schema = "mail" + override protected val createRepository = MailRepository.apply _ override def processCommand(credential: Credential, token: Any, @@ -70,7 +73,7 @@ class MailPlugin extends BasePlugin with CommandProcessor with ParticipantProces private def sendMail(sender: Credential, receiverNick: String, message: String) { implicit val c = sender - import Localization._ + import ru.org.codingteam.horta.localization.Localization._ // First try to send the message right now: val location = sender.location @@ -107,20 +110,20 @@ class MailPlugin extends BasePlugin with CommandProcessor with ParticipantProces } private def saveMessage(room: String, senderNick: String, receiverNick: String, message: String): Future[Boolean] = { - (store ? StoreObject(name, None, MailMessage(None, room, senderNick, receiverNick, message))).map { - case Some(_) => true - case None => false + val promise = Promise[Boolean]() + withDatabase(_.store(MailMessage(None, room, senderNick, receiverNick, message))) onComplete { result => + promise.success(result.isSuccess) } + + promise.future } private def readMessages(room: String, receiverNick: String): Future[Seq[MailMessage]] = { - (store ? ReadObject(name, (room, receiverNick))) map { - case Some(messages: Seq[MailMessage]) => messages - } + withDatabase(_.getMessages(room, receiverNick)) } - private def deleteMessage(id: Int) { - store ? DeleteObject(name, id) + private def deleteMessage(id: Int): Unit = { + withDatabase(_.deleteMessage(id)) } } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailRepository.scala b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailRepository.scala new file mode 100644 index 0000000..651affc --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/plugins/mail/MailRepository.scala @@ -0,0 +1,27 @@ +package ru.org.codingteam.horta.plugins.mail + +import scalikejdbc._ + +case class MailRepository(session: DBSession) { + + implicit val s = session + + def store(message: MailMessage): Unit = { + message match { + case MailMessage(_, room, senderNick, receiverNick, text) => + sql"insert into Mail (room, sender, receiver, message) values ($room, $senderNick, $receiverNick, $text)" + .updateAndReturnGeneratedKey().apply() + } + } + + def getMessages(room: String, receiverNick: String): Seq[MailMessage] = { + sql"select id, sender, message from Mail where room = $room and receiver = $receiverNick".map( + rs => MailMessage(Some(rs.int("id")), room, rs.string("sender"), receiverNick, rs.string("message"))) + .list().apply() + } + + def deleteMessage(id: Int): Unit = { + sql"delete from mail where id = $id".update().apply() + } + +} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/pet/Pet.scala b/src/main/scala/ru/org/codingteam/horta/plugins/pet/Pet.scala index 68aff77..1732826 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/pet/Pet.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/pet/Pet.scala @@ -5,7 +5,7 @@ import akka.event.LoggingReceive import akka.pattern.ask import akka.util.Timeout import org.jivesoftware.smack.util.StringUtils -import ru.org.codingteam.horta.database.{ReadObject, StoreObject} +import ru.org.codingteam.horta.database.PersistentStore import ru.org.codingteam.horta.localization.Localization._ import ru.org.codingteam.horta.messages.GetParticipants import ru.org.codingteam.horta.plugins.pet.commands.AbstractCommand @@ -131,13 +131,13 @@ class Pet(roomId: String, location: ActorRef) extends Actor with ActorLogging { } private def setPetData(pet: PetData) { - val Some(_) = Await.result(store ? StoreObject("pet", Some(PetDataId(roomId)), pet), 5 minutes) + Await.result( + PersistentStore.execute[PetRepository, Unit](PetPlugin.name, store)(_.storePetData(roomId, pet)), 5 minutes) self ! SetPetDataInternal(pet) } private def readStoredData(): Future[Option[PetData]] = { - val request = store ? ReadObject("pet", PetDataId(roomId)) - request.mapTo[Option[PetData]] + PersistentStore.execute[PetRepository, Option[PetData]](PetPlugin.name, store)(_.readPetData(roomId)) } def sayToEveryone(text: String)(implicit credential: Credential) { diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetCoinStorage.scala b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetCoinStorage.scala index d7c6acb..5bf63c6 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetCoinStorage.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetCoinStorage.scala @@ -1,15 +1,12 @@ package ru.org.codingteam.horta.plugins.pet import akka.actor.{Actor, ActorLogging} -import akka.pattern.ask import akka.util.Timeout -import ru.org.codingteam.horta.database.{StoreObject, ReadObject} +import ru.org.codingteam.horta.database.PersistentStore import scala.concurrent.Await import scala.concurrent.duration._ -import scala.math._ - case class GetPTC() case class UpdateUserPTC(transactionName: String, user: String, delta: Int) case class UpdateUserPTCWithOverflow(transactionName: String, user: String, delta: Int) @@ -34,27 +31,22 @@ class PetCoinStorage(room: String) extends Actor with ActorLogging { } private def withCoins(transactionName: String, action: Map[String, Int] => (Option[Map[String, Int]], Int)): Int = { - val Some(oldCoins) = coins match { - case Some(c) => Some(c) + val oldCoins = coins match { + case Some(c) => c case None => Await.result( - (store ? ReadObject(PetPlugin.name, PetCoinsId(room))).mapTo[Option[Map[String, Int]]], + PersistentStore.execute[PetRepository, Map[String, Int]](PetPlugin.name, store)(_.readCoins(room)), waitFor) } action(oldCoins) match { - case (Some(rawNewCoins), result) => { + case (Some(rawNewCoins), result) => val newCoins = rawNewCoins.filter(_._2 > 0) Await.result( - store ? StoreObject( - PetPlugin.name, - Some(PetCoinsId(room)), - PetCoinTransaction(transactionName, oldCoins, newCoins)), - waitFor) match { - case Some(_) => coins = Some(newCoins) - case None => sys.error("Cannot process PTC transaction") - } + PersistentStore.execute[PetRepository, Unit](PetPlugin.name, store) + (_.storeTransaction(room, PetCoinTransaction(transactionName, oldCoins, newCoins))), + waitFor) + coins = Some(newCoins) result - } case (None, 0) => 0 case other => sys.error(s"Logic error: $other") } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetPlugin.scala index 0f6b0b5..b0082d4 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetPlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetPlugin.scala @@ -4,7 +4,6 @@ import akka.actor.{ActorRef, Props} import akka.pattern.ask import akka.util.Timeout import org.joda.time.DateTime -import ru.org.codingteam.horta.database.ReadObject import ru.org.codingteam.horta.localization.Localization import ru.org.codingteam.horta.plugins._ import ru.org.codingteam.horta.plugins.pet.commands._ @@ -18,7 +17,7 @@ import scala.language.postfixOps /** * Plugin for managing the so-called pet. Distinct pet belongs to every room. */ -class PetPlugin extends BasePlugin with CommandProcessor with RoomProcessor { +class PetPlugin extends BasePlugin with CommandProcessor with RoomProcessor with DataAccessingPlugin[PetRepository] { import context.dispatcher @@ -51,7 +50,8 @@ class PetPlugin extends BasePlugin with CommandProcessor with RoomProcessor { override def commands = List(CommandDefinition(CommonAccess, "pet", null)) - override def dao = Some(new PetDAO()) + override protected val schema = "pet" + override protected val createRepository = PetRepository.apply _ override def processCommand(credential: Credential, token: Any, arguments: Array[String]) { val location = credential.location @@ -65,10 +65,8 @@ class PetPlugin extends BasePlugin with CommandProcessor with RoomProcessor { case Array(PetCommandMatcher(command), args@_*) => (pet ? Pet.ExecuteCommand(command, credential, args.toArray)).mapTo[String].map(s => (false, s)) case Array("transactions") => - (store ? ReadObject(name, PetCoinTransactionsId(credential.roomId.get, credential.name))) - .mapTo[Option[Seq[PetCoinTransactionModel]]] - .map { case transactions => - (true, transactions.get.mkString("\n")) + withDatabase(_.readTransactions(credential.roomId.get, credential.name)) map { case transactions => + (true, transactions.mkString("\n")) } case _ => Future.successful((false, Localization.localize("Try $pet help.")(credential))) } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetDAO.scala b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetRepository.scala similarity index 53% rename from src/main/scala/ru/org/codingteam/horta/plugins/pet/PetDAO.scala rename to src/main/scala/ru/org/codingteam/horta/plugins/pet/PetRepository.scala index 2ca2502..53f7290 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetDAO.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/pet/PetRepository.scala @@ -2,54 +2,26 @@ package ru.org.codingteam.horta.plugins.pet import org.joda.time.DateTime import ru.org.codingteam.horta.core.Clock -import ru.org.codingteam.horta.database.DAO import scalikejdbc._ -case class PetDataId(room: String) -case class PetCoinsId(room: String) -case class PetCoinTransactionsId(room: String, nick: String) - case class PetCoinTransaction(name: String, state1: Map[String, Int], state2: Map[String, Int]) case class PetCoinTransactionModel(id: Int, room: String, nickname: String, time: DateTime, change: Int, reason: String) -class PetDAO extends DAO { +case class PetRepository(session: DBSession) { val transactionViewLimit = 10 - override def schema = "pet" + implicit val s = session - override def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] = { - (id, obj) match { - case (Some(PetDataId(room)), data: PetData) => storePetData(session, room, data) - case (Some(PetCoinsId(room)), transaction: PetCoinTransaction) => - storeTransaction(session, room, transaction) - Some(Unit) - case _ => sys.error(s"Invalid parameters for the PetDAO.store: $id, $obj") - } - } - - override def read(implicit session: DBSession, id: Any): Option[Any] = { - id match { - case PetDataId(roomName) => readPetData(session, roomName) - case PetCoinsId(roomName) => Some(readCoins(session, roomName)) - case PetCoinTransactionsId(roomName, nickname) => Some(readPetCoinTransactions(session, roomName, nickname)) - } - } - - override def delete(implicit session: DBSession, id: Any): Boolean = false - - private def storePetData(implicit session: DBSession, room: String, data: PetData) = { - val roomName = room.asInstanceOf[String] - val exist = sql"select * from Pet where room = $roomName".map(rs => true).single().apply() + def storePetData(room: String, data: PetData): Unit = { + val exist = sql"select * from Pet where room = $room".map(rs => true).single().apply() exist match { - case Some(_) => updatePetData(session, roomName, data) - case None => insertPetData(session, roomName, data) + case Some(_) => updatePetData(room, data) + case None => insertPetData(room, data) } - - Some(Unit) } - private def readPetData(implicit session: DBSession, roomName: String) = { + def readPetData(roomName: String) = { sql"select * from Pet where room = $roomName".map( rs => PetData( rs.string("nickname"), @@ -59,12 +31,19 @@ class PetDAO extends DAO { rs.jodaDateTime("birth"))).single().apply() } - private def readCoins(implicit session: DBSession, room: String): Map[String, Int] = { + def readCoins(room: String): Map[String, Int] = { sql"select nick, amount from PetCoins where room = $room".map( rs => rs.string("nick") -> rs.int("amount")).list().apply().toMap } - private def readPetCoinTransactions(implicit session: DBSession, room: String, nickname: String) = { + def storeTransaction(roomName: String, data: PetCoinTransaction): Unit = { + val PetCoinTransaction(transactionName, state1, state2) = data + storeTransactionHistory(roomName, Clock.now, transactionName, state1, state2) + deleteCoins(roomName) + insertCoins(roomName, state2) + } + + def readTransactions(room: String, nickname: String): Seq[PetCoinTransactionModel] = { sql""" select id, time, change, reason from PetTransaction @@ -81,15 +60,20 @@ class PetDAO extends DAO { rs.string("reason"))).list().apply() } - private def storeTransaction(session: DBSession, roomName: String, data: PetCoinTransaction): Unit = { - val PetCoinTransaction(transactionName, state1, state2) = data - storeTransactionHistory(session, roomName, Clock.now, transactionName, state1, state2) - deleteCoins(session, roomName) - insertCoins(session, roomName, state2) + private def updatePetData(room: String, obj: PetData) { + val PetData(nickname, alive, health, satiation, birth) = obj + sql"""update Pet + set nickname = $nickname, alive = $alive, health = $health, satiation = $satiation, birth = $birth + where room = $room""".update().apply() } - private def storeTransactionHistory(implicit session: DBSession, - roomName: String, + private def insertPetData(room: String, obj: PetData) { + val PetData(nickname, alive, health, satiation, birth) = obj + sql"""insert into Pet (room, nickname, alive, health, satiation, birth) + values ($room, $nickname, $alive, $health, $satiation, $birth)""".update().apply() + } + + private def storeTransactionHistory(roomName: String, time: DateTime, transactionName: String, state1: Map[String, Int], @@ -107,27 +91,14 @@ class PetDAO extends DAO { } } - private def insertPetData(implicit session: DBSession, room: String, obj: PetData) { - val PetData(nickname, alive, health, satiation, birth) = obj - sql"""insert into Pet (room, nickname, alive, health, satiation, birth) - values ($room, $nickname, $alive, $health, $satiation, $birth)""".update().apply() - } - - private def updatePetData(implicit session: DBSession, room: String, obj: PetData) { - val PetData(nickname, alive, health, satiation, birth) = obj - sql"""update Pet - set nickname = $nickname, alive = $alive, health = $health, satiation = $satiation, birth = $birth - where room = $room""".update().apply() - } - - private def insertCoins(implicit session: DBSession, room: String, coins: Map[String, Int]) { + private def insertCoins(room: String, coins: Map[String, Int]) { coins filter (_._2 > 0) foreach { case (nick, amount) => sql"insert into PetCoins(room, nick, amount) values ($room, $nick, $amount)".update().apply() } } - private def deleteCoins(implicit session: DBSession, room: String) { + private def deleteCoins(room: String) { sql"delete from PetCoins where room = $room".update().apply() } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfDAO.scala b/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfDAO.scala deleted file mode 100644 index 408fb52..0000000 --- a/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfDAO.scala +++ /dev/null @@ -1,29 +0,0 @@ -package ru.org.codingteam.horta.plugins.wtf - -import ru.org.codingteam.horta.database.DAO -import scalikejdbc._ - -class WtfDAO extends DAO { - - override def schema = "wtf" - - override def store(implicit session: DBSession, id: Option[Any], obj: Any): Option[Any] = { - val WtfDefinition(_, room, word, definition, author) = obj - val id = sql"insert into Wtf (room, word, definition, author) values ($room, $word, $definition, $author)" - .updateAndReturnGeneratedKey().apply() - Some(id) - } - - override def read(implicit session: DBSession, id: Any): Option[Any] = { - val (room: String, word: String) = id - sql"select id, definition, author from Wtf where room = $room and upper(word) = upper($word)".map( - rs => WtfDefinition(Some(rs.int("id")), room, word, rs.string("definition"), rs.string("author"))) - .single().apply() - } - - override def delete(implicit session: DBSession, anyId: Any): Boolean = { - val id = anyId.asInstanceOf[Int] - sql"delete from wtf where id = $id".update().apply() == 1 - } - -} diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfPlugin.scala b/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfPlugin.scala index 01796a5..86716f7 100644 --- a/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfPlugin.scala +++ b/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfPlugin.scala @@ -1,10 +1,8 @@ package ru.org.codingteam.horta.plugins.wtf -import akka.pattern.ask import akka.util.Timeout -import ru.org.codingteam.horta.database.{DeleteObject, ReadObject, StoreObject} import ru.org.codingteam.horta.localization.Localization._ -import ru.org.codingteam.horta.plugins.{BasePlugin, CommandDefinition, CommandProcessor} +import ru.org.codingteam.horta.plugins.{BasePlugin, CommandDefinition, CommandProcessor, DataAccessingPlugin} import ru.org.codingteam.horta.protocol.Protocol import ru.org.codingteam.horta.security.{CommonAccess, Credential} @@ -14,7 +12,7 @@ import scala.concurrent.duration._ private object WtfCommand private object WtfDeleteCommand -class WtfPlugin extends BasePlugin with CommandProcessor { +class WtfPlugin extends BasePlugin with CommandProcessor with DataAccessingPlugin[WtfRepository] { import context.dispatcher implicit val timeout = Timeout(60.seconds) @@ -26,7 +24,8 @@ class WtfPlugin extends BasePlugin with CommandProcessor { CommandDefinition(CommonAccess, "wtf-delete", WtfDeleteCommand) ) - override def dao = Some(new WtfDAO) + override protected val schema = "wtf" + override protected val createRepository = WtfRepository.apply _ protected def processCommand(credential: Credential, token: Any, @@ -51,7 +50,7 @@ class WtfPlugin extends BasePlugin with CommandProcessor { } private def showDefinition(credential: Credential, room: String, word: String): Unit = { - store ? ReadObject(name, (room, word)) map { + withDatabase(_.read(room, word)) map { case Some(wtfDefinition: WtfDefinition) => sendResponse(credential, s"> ${wtfDefinition.definition} © ${wtfDefinition.author}") case None => @@ -65,17 +64,15 @@ class WtfPlugin extends BasePlugin with CommandProcessor { (word.trim, definition.trim) match { case ("", _) => sendResponse(credential, localize("You cannot define an empty string.")) case (word, "") => deleteDefinition(credential, room, word) - case (word, definition) => store ? ReadObject(name, (room, word)) map { - case Some(wtfDefinition: WtfDefinition) => store ? DeleteObject(name, wtfDefinition.id.get) map { - case true => store ? StoreObject(name, None, WtfDefinition(None, room, word, definition, credential.name)) map { - case Some(_) => sendResponse(credential, localize("Definition updated.")) - case None => sendResponse(credential, localize("Cannot update a definition.")) + case (word, definition) => withDatabase(_.read(room, word)) map { + case Some(wtfDefinition: WtfDefinition) => withDatabase(_.delete(wtfDefinition.id.get)) map { + case true => withDatabase(_.store(WtfDefinition(None, room, word, definition, credential.name))) map { _ => + sendResponse(credential, localize("Definition updated.")) } case false => sendResponse(credential, localize("Cannot update a definition.")) } - case None => store ? StoreObject(name, None, WtfDefinition(None, room, word, definition, credential.name)) map { - case Some(_) => sendResponse(credential, localize("Definition added.")) - case None => sendResponse(credential, localize("Cannot add a definition.")) + case None => withDatabase(_.store(WtfDefinition(None, room, word, definition, credential.name))) map { _ => + sendResponse(credential, localize("Definition added.")) } } } @@ -84,8 +81,8 @@ class WtfPlugin extends BasePlugin with CommandProcessor { private def deleteDefinition(credential: Credential, room: String, word: String): Unit = { implicit val c = credential - store ? ReadObject(name, (room, word)) map { - case Some(wtfDefinition: WtfDefinition) => store ? DeleteObject(name, wtfDefinition.id.get) map { + withDatabase(_.read(room, word)) map { + case Some(wtfDefinition: WtfDefinition) => withDatabase(_.delete(wtfDefinition.id.get)) map { case true => sendResponse(credential, localize("Definition deleted.")) case false => sendResponse(credential, localize("Cannot delete a definition.")) } diff --git a/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfRepository.scala b/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfRepository.scala new file mode 100644 index 0000000..79d7c58 --- /dev/null +++ b/src/main/scala/ru/org/codingteam/horta/plugins/wtf/WtfRepository.scala @@ -0,0 +1,25 @@ +package ru.org.codingteam.horta.plugins.wtf + +import scalikejdbc._ + +case class WtfRepository(session: DBSession) { + + implicit val s = session + + def store(obj: WtfDefinition): Unit = { + val WtfDefinition(_, room, word, definition, author) = obj + sql"insert into Wtf (room, word, definition, author) values ($room, $word, $definition, $author)" + .update().apply() + } + + def read(room: String, word: String) = { + sql"select id, definition, author from Wtf where room = $room and upper(word) = upper($word)".map( + rs => WtfDefinition(Some(rs.int("id")), room, word, rs.string("definition"), rs.string("author"))) + .single().apply() + } + + def delete(id: Int): Boolean = { + sql"delete from wtf where id = $id".update().apply() == 1 + } + +}