1 /*
2  *  Make.org Core API
3  *  Copyright (C) 2018 Make.org
4  *
5  * This program is free software: you can redistribute it and/or modify
6  *  it under the terms of the GNU Affero General Public License as
7  *  published by the Free Software Foundation, either version 3 of the
8  *  License, or (at your option) any later version.
9  *
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU Affero General Public License for more details.
14  *
15  *  You should have received a copy of the GNU Affero General Public License
16  *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17  *
18  */
19 
20 package org.make.api.user
21 
22 import cats.data.NonEmptyList
23 import grizzled.slf4j.Logging
24 import org.make.api.extensions.MakeDBExecutionContextComponent
25 import org.make.api.technical.DatabaseTransactions._
26 import org.make.api.technical.{PersistentCompanion, ShortenedNames}
27 import org.make.api.technical.ScalikeSupport._
28 import org.make.core.question.QuestionId
29 import org.make.core.user._
30 import scalikejdbc._
31 import cats.implicits._
32 
33 import java.util.UUID
34 import scala.concurrent.Future
35 
36 trait DefaultPersistentUserProposalsServiceComponent
37     extends PersistentUserProposalsServiceComponent
38     with ShortenedNames
39     with Logging {
40 
41   this: MakeDBExecutionContextComponent =>
42 
43   override lazy val persistentUserProposalsService: PersistentUserProposalsService =
44     new DefaultPersistentUserProposalsService
45 
46   class DefaultPersistentUserProposalsService extends PersistentUserProposalsService {
47 
48     import DefaultPersistentUserProposalsServiceComponent.UserProposalsCount
49 
50     private val userProposalsAlias = UserProposalsCount.alias
51     private val column = UserProposalsCount.column
52 
53     private def makePredicates(userId: UserId, questionId: QuestionId) =
54       sqls
55         .eq(column.questionId, questionId.value)
56         .and(sqls.eq(column.userId, userId.value))
57 
58     private def getCounterOpt(userId: UserId, questionId: QuestionId)(implicit ctx: EC): Future[Option[Int]] =
59       Future(NamedDB("READ").retryableTx { implicit session =>
60         withSQL {
61           select(userProposalsAlias.count)
62             .from(UserProposalsCount.as(userProposalsAlias))
63             .where(makePredicates(userId, questionId))
64         }.map(_.int(1)).single()
65       })
66 
67     override def getCounter(userId: UserId, questionId: QuestionId): Future[Int] = {
68       implicit val ctx: EC = readExecutionContext
69       getCounterOpt(userId, questionId).map(_.getOrElse(0))
70     }
71 
72     private def createUserProposalsRow(userId: UserId, questionId: QuestionId)(implicit ctx: EC): Future[Unit] =
73       Future(NamedDB("WRITE").retryableTx { implicit session =>
74         withSQL {
75           insert
76             .into(UserProposalsCount)
77             .namedValues(
78               column.id -> UUID.randomUUID().toString,
79               column.userId -> userId.value,
80               column.questionId -> questionId.value,
81               column.count -> 1.toString
82             )
83         }.execute()
84       })
85 
86     override def incrementCounter(userId: UserId, questionId: QuestionId): Future[Int] = {
87       implicit val ctx: EC = writeExecutionContext
88       getCounterOpt(userId, questionId).flatMap({
89         case Some(count) =>
90           Future(NamedDB("WRITE").retryableTx { implicit session =>
91             withSQL {
92               update(UserProposalsCount)
93                 .set(column.count -> (count + 1).toString)
94                 .where(makePredicates(userId, questionId))
95             }.executeUpdate()
96           }).as(count + 1)
97         case None => createUserProposalsRow(userId, questionId).as(1)
98       })
99     }
100   }
101 }
102 
103 object DefaultPersistentUserProposalsServiceComponent {
104 
105   final case class UserProposalsCount(id: String, userId: String, questionId: String, count: Int)
106 
107   object UserProposalsCount
108       extends SQLSyntaxSupport[UserProposalsCount]
109       with PersistentCompanion[UserProposalsCount, User]
110       with ShortenedNames
111       with Logging {
112 
113     override val columnNames: Seq[String] = Seq("id", "user_id", "question_id", "count")
114 
115     override val tableName: String = "votes_per_user_per_question"
116 
117     override lazy val alias: SyntaxProvider[UserProposalsCount] = syntax("counts")
118 
119     override lazy val defaultSortColumns: NonEmptyList[SQLSyntax] = NonEmptyList.of(alias.questionId)
120 
121     def apply(
122       userPropositionsResultName: ResultName[UserProposalsCount]
123     )(resultSet: WrappedResultSet): UserProposalsCount = {
124       UserProposalsCount(
125         id = resultSet.string(userPropositionsResultName.id),
126         userId = resultSet.string(userPropositionsResultName.userId),
127         questionId = resultSet.string(userPropositionsResultName.questionId),
128         count = resultSet.int(userPropositionsResultName.count)
129       )
130     }
131   }
132 }
Line Stmt Id Pos Tree Symbol Tests Code
51 20061 1810 - 1835 Select scalikejdbc.SQLSyntaxSupportFeature.SQLSyntaxSupport.column DefaultPersistentUserProposalsServiceComponent.UserProposalsCount.column
55 21146 1932 - 1932 Select scalikejdbc.ParameterBinderFactory.stringParameterBinderFactory scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory
55 21668 1952 - 1968 Select org.make.core.question.QuestionId.value questionId.value
56 19911 1916 - 2020 Apply scalikejdbc.interpolation.SQLSyntax.and scalikejdbc.`package`.sqls.eq[String]((DefaultPersistentUserProposalsService.this.column.field("questionId"): scalikejdbc.interpolation.SQLSyntax), questionId.value)(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory).and(scalikejdbc.`package`.sqls.eq[String]((DefaultPersistentUserProposalsService.this.column.field("userId"): scalikejdbc.interpolation.SQLSyntax), userId.value)(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory))
56 20224 2006 - 2018 Select org.make.core.user.UserId.value userId.value
56 21758 1990 - 1990 Select scalikejdbc.ParameterBinderFactory.stringParameterBinderFactory scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory
56 20872 1983 - 2019 ApplyToImplicitArgs scalikejdbc.interpolation.SQLSyntax.eq scalikejdbc.`package`.sqls.eq[String]((DefaultPersistentUserProposalsService.this.column.field("userId"): scalikejdbc.interpolation.SQLSyntax), userId.value)(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory)
59 21936 2139 - 2414 ApplyToImplicitArgs scala.concurrent.Future.apply scala.concurrent.Future.apply[Option[Int]]({ <artifact> val qual$1: org.make.api.technical.DatabaseTransactions.RichDatabase = org.make.api.technical.DatabaseTransactions.RichDatabase({ <artifact> val x$1: String("READ") = "READ"; <artifact> val x$2: scalikejdbc.SettingsProvider = scalikejdbc.NamedDB.apply$default$2; <artifact> val x$3: scalikejdbc.ConnectionPoolContext = scalikejdbc.NamedDB.apply$default$3("READ", x$2); scalikejdbc.NamedDB.apply("READ", x$2)(x$3) }); <artifact> val x$7: scalikejdbc.DBSession => Option[Int] @scala.reflect.internal.annotations.uncheckedBounds = ((implicit session: scalikejdbc.DBSession) => { <synthetic> <stable> <artifact> val stabilizer$1: scalikejdbc.SQLToOption[Int,scalikejdbc.HasExtractor] @scala.reflect.internal.annotations.uncheckedBounds = scalikejdbc.`package`.withSQL.apply[Nothing](scalikejdbc.`package`.select.apply[Nothing]((DefaultPersistentUserProposalsService.this.userProposalsAlias.field("count"): scalikejdbc.interpolation.SQLSyntax)).from(DefaultPersistentUserProposalsServiceComponent.UserProposalsCount.as(DefaultPersistentUserProposalsService.this.userProposalsAlias)).where(DefaultPersistentUserProposalsService.this.makePredicates(userId, questionId))).map[Int](((x$1: scalikejdbc.WrappedResultSet) => x$1.int(1))).single; { <artifact> val x$4: scalikejdbc.DBSession = session; <artifact> val x$5: scalikejdbc.SQL[Int,scalikejdbc.HasExtractor] =:= scalikejdbc.SQL[Int,scalikejdbc.HasExtractor] @scala.reflect.internal.annotations.uncheckedBounds = GeneralizedTypeConstraintsForWithExtractor.this.=:=.tpEquals[scalikejdbc.SQL[Int,scalikejdbc.HasExtractor]]; <artifact> val x$6: scalikejdbc.ConnectionPoolContext = stabilizer$1.apply$default$2(); stabilizer$1.apply()(x$4, x$6, x$5) } }); <artifact> val x$8: scalikejdbc.TxBoundary[Option[Int]] @scala.reflect.internal.annotations.uncheckedBounds = qual$1.retryableTx$default$2[Option[Int]](x$7); qual$1.retryableTx[Option[Int]](x$7)(x$8) })(ctx)
68 20908 2530 - 2550 Select org.make.api.extensions.MakeDBExecutionContextComponent.readExecutionContext DefaultPersistentUserProposalsServiceComponent.this.readExecutionContext
69 20017 2557 - 2610 ApplyToImplicitArgs scala.concurrent.Future.map DefaultPersistentUserProposalsService.this.getCounterOpt(userId, questionId)(ctx).map[Int](((x$2: Option[Int]) => x$2.getOrElse[Int](0)))(ctx)
73 21671 2737 - 3130 ApplyToImplicitArgs scala.concurrent.Future.apply scala.concurrent.Future.apply[Unit]({ { <artifact> val qual$1: org.make.api.technical.DatabaseTransactions.RichDatabase = org.make.api.technical.DatabaseTransactions.RichDatabase({ <artifact> val x$1: String("WRITE") = "WRITE"; <artifact> val x$2: scalikejdbc.SettingsProvider = scalikejdbc.NamedDB.apply$default$2; <artifact> val x$3: scalikejdbc.ConnectionPoolContext = scalikejdbc.NamedDB.apply$default$3("WRITE", x$2); scalikejdbc.NamedDB.apply("WRITE", x$2)(x$3) }); <artifact> val x$4: scalikejdbc.DBSession => Boolean @scala.reflect.internal.annotations.uncheckedBounds = ((implicit session: scalikejdbc.DBSession) => scalikejdbc.`package`.withSQL.apply[scalikejdbc.UpdateOperation](scalikejdbc.`package`.insert.into(DefaultPersistentUserProposalsServiceComponent.UserProposalsCount).namedValues((DefaultPersistentUserProposalsService.this.column.field("id"): scalikejdbc.interpolation.SQLSyntax).->[String](java.util.UUID.randomUUID().toString())(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory), (DefaultPersistentUserProposalsService.this.column.field("userId"): scalikejdbc.interpolation.SQLSyntax).->[String](userId.value)(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory), (DefaultPersistentUserProposalsService.this.column.field("questionId"): scalikejdbc.interpolation.SQLSyntax).->[String](questionId.value)(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory), (DefaultPersistentUserProposalsService.this.column.field("count"): scalikejdbc.interpolation.SQLSyntax).->[String](1.toString())(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory))).execute.apply()(session)); <artifact> val x$5: scalikejdbc.TxBoundary[Boolean] @scala.reflect.internal.annotations.uncheckedBounds = qual$1.retryableTx$default$2[Boolean](x$4); qual$1.retryableTx[Boolean](x$4)(x$5) }; () })(ctx)
87 21094 3252 - 3273 Select org.make.api.extensions.MakeDBExecutionContextComponent.writeExecutionContext DefaultPersistentUserProposalsServiceComponent.this.writeExecutionContext
88 20181 3280 - 3736 ApplyToImplicitArgs scala.concurrent.Future.flatMap DefaultPersistentUserProposalsService.this.getCounterOpt(userId, questionId)(ctx).flatMap[Int](((x0$1: Option[Int]) => x0$1 match { case (value: Int): Some[Int]((count @ _)) => cats.implicits.toFunctorOps[scala.concurrent.Future, Int](scala.concurrent.Future.apply[Int]({ <artifact> val qual$1: org.make.api.technical.DatabaseTransactions.RichDatabase = org.make.api.technical.DatabaseTransactions.RichDatabase({ <artifact> val x$1: String("WRITE") = "WRITE"; <artifact> val x$2: scalikejdbc.SettingsProvider = scalikejdbc.NamedDB.apply$default$2; <artifact> val x$3: scalikejdbc.ConnectionPoolContext = scalikejdbc.NamedDB.apply$default$3("WRITE", x$2); scalikejdbc.NamedDB.apply("WRITE", x$2)(x$3) }); <artifact> val x$4: scalikejdbc.DBSession => Int @scala.reflect.internal.annotations.uncheckedBounds = ((implicit session: scalikejdbc.DBSession) => scalikejdbc.`package`.withSQL.apply[scalikejdbc.UpdateOperation](scalikejdbc.`package`.update.apply(DefaultPersistentUserProposalsServiceComponent.UserProposalsCount).set((DefaultPersistentUserProposalsService.this.column.field("count"): scalikejdbc.interpolation.SQLSyntax).->[String](count.+(1).toString())(scalikejdbc.this.ParameterBinderFactory.stringParameterBinderFactory)).where(DefaultPersistentUserProposalsService.this.makePredicates(userId, questionId))).executeUpdate.apply()(session)); <artifact> val x$5: scalikejdbc.TxBoundary[Int] @scala.reflect.internal.annotations.uncheckedBounds = qual$1.retryableTx$default$2[Int](x$4); qual$1.retryableTx[Int](x$4)(x$5) })(ctx))(cats.implicits.catsStdInstancesForFuture(ctx)).as[Int](count.+(1)) case scala.None => cats.implicits.toFunctorOps[scala.concurrent.Future, Unit](DefaultPersistentUserProposalsService.this.createUserProposalsRow(userId, questionId)(ctx))(cats.implicits.catsStdInstancesForFuture(ctx)).as[Int](1) }))(ctx)
113 21761 4134 - 4178 Apply scala.collection.SeqFactory.Delegate.apply scala.`package`.Seq.apply[String]("id", "user_id", "question_id", "count")
115 20832 4217 - 4246 Literal <nosymbol> "votes_per_user_per_question"
124 20837 4580 - 4881 Apply org.make.api.user.DefaultPersistentUserProposalsServiceComponent.UserProposalsCount.apply DefaultPersistentUserProposalsServiceComponent.this.UserProposalsCount.apply(resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("id"): scalikejdbc.interpolation.SQLSyntax))), resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("userId"): scalikejdbc.interpolation.SQLSyntax))), resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("questionId"): scalikejdbc.interpolation.SQLSyntax))), resultSet.int(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("count"): scalikejdbc.interpolation.SQLSyntax))))
125 19858 4630 - 4659 ApplyImplicitView scalikejdbc.interpolation.Implicits.scalikejdbcSQLSyntaxToStringImplicitDef scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("id"): scalikejdbc.interpolation.SQLSyntax))
125 21942 4613 - 4660 Apply scalikejdbc.WrappedResultSet.string resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("id"): scalikejdbc.interpolation.SQLSyntax)))
126 20021 4679 - 4730 Apply scalikejdbc.WrappedResultSet.string resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("userId"): scalikejdbc.interpolation.SQLSyntax)))
126 21051 4696 - 4729 ApplyImplicitView scalikejdbc.interpolation.Implicits.scalikejdbcSQLSyntaxToStringImplicitDef scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("userId"): scalikejdbc.interpolation.SQLSyntax))
127 20658 4753 - 4808 Apply scalikejdbc.WrappedResultSet.string resultSet.string(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("questionId"): scalikejdbc.interpolation.SQLSyntax)))
127 21645 4770 - 4807 ApplyImplicitView scalikejdbc.interpolation.Implicits.scalikejdbcSQLSyntaxToStringImplicitDef scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("questionId"): scalikejdbc.interpolation.SQLSyntax))
128 21841 4826 - 4873 Apply scalikejdbc.WrappedResultSet.int resultSet.int(scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("count"): scalikejdbc.interpolation.SQLSyntax)))
128 20187 4840 - 4872 ApplyImplicitView scalikejdbc.interpolation.Implicits.scalikejdbcSQLSyntaxToStringImplicitDef scalikejdbc.`package`.scalikejdbcSQLSyntaxToStringImplicitDef((userPropositionsResultName.field("count"): scalikejdbc.interpolation.SQLSyntax))