diff --git a/build.sbt b/build.sbt index 8f1a7ab..d767e4e 100644 --- a/build.sbt +++ b/build.sbt @@ -1,10 +1,13 @@ name := "socialnetwork" -version := "0.1" +version := "0.2" -scalaVersion := "2.13.1" +scalaVersion := "2.12.10" libraryDependencies ++= Seq( - "org.postgresql" % "postgresql" % "42.2.8", - "io.getquill" %% "quill-jdbc" % "3.5.0" + "com.github.finagle" %% "finch-core" % "0.31.0", + "com.github.finagle" %% "finch-circe" % "0.31.0", + "io.circe" %% "circe-generic" % "0.13.0", + "io.getquill" %% "quill-jdbc" % "3.5.0", + "org.postgresql" % "postgresql" % "42.2.10", ) diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf new file mode 100644 index 0000000..fa0b704 --- /dev/null +++ b/src/main/resources/application.conf @@ -0,0 +1,7 @@ +jdbc { + dataSourceClassName = "org.postgresql.ds.PGSimpleDataSource" + dataSource.user = "tom_from_myspace" + dataSource.databaseName = "socialnetwork" + dataSource.portNumber = 5432 + dataSource.serverName = "localhost" +} diff --git a/src/main/scala/com/github/sylvansson/socialnetwork/Endpoints.scala b/src/main/scala/com/github/sylvansson/socialnetwork/Endpoints.scala new file mode 100644 index 0000000..fff42e0 --- /dev/null +++ b/src/main/scala/com/github/sylvansson/socialnetwork/Endpoints.scala @@ -0,0 +1,40 @@ +package com.github.sylvansson.socialnetwork +import java.util.UUID + +import com.github.sylvansson.socialnetwork.Responses._ +import com.twitter.util.Future +import io.circe.generic.auto._ +import io.finch.circe._ +import io.finch.syntax.{get, post} +import io.finch.{Endpoint, Ok, param, _} + +object Endpoints { + def service = friendships.toService + + private def friendships = { + val listAcceptedFriendships: Endpoint[Success[Seq[Friendship]]] = + get("friendships.accepted" :: param[UUID]("user")) { userId: UUID => + Future(Friendship.findAccepted(userId)) + .map(fs => Ok(Success("acceptedFriendships", fs))) + } + + val listPendingFriendships: Endpoint[Success[Seq[Friendship]]] = + get("friendships.pending" :: param[UUID]("user")) { userId: UUID => + Future(Friendship.findPending(userId)) + .map(fs => Ok(Success("pendingFriendships", fs))) + } + + // TODO: Ensure that only the requestee can accept a friendship, + // once authentication has been implemented. + val acceptFriendship: Endpoint[EmptySuccess] = + post("friendships.accept" :: param[UUID]("requester") :: param[UUID]("requestee")) { + (requesterId: UUID, requesteeId: UUID) => + Future(Friendship.accept(requesterId, requesteeId)) + .map(_ => Ok(EmptySuccess())) + } + + listAcceptedFriendships :+: + listPendingFriendships :+: + acceptFriendship + } +} diff --git a/src/main/scala/com/github/sylvansson/socialnetwork/Responses.scala b/src/main/scala/com/github/sylvansson/socialnetwork/Responses.scala new file mode 100644 index 0000000..ed3bef3 --- /dev/null +++ b/src/main/scala/com/github/sylvansson/socialnetwork/Responses.scala @@ -0,0 +1,25 @@ +package com.github.sylvansson.socialnetwork + +import io.circe.{Encoder, Json} +import io.circe.syntax._ + +/** + * Case classes for responses. The response format is based on Slack's + * Web API: https://api.slack.com/web + */ +object Responses { + case class EmptySuccess() + object EmptySuccess { + implicit def encode: Encoder[EmptySuccess] = (_: EmptySuccess) => + Json.obj("ok" -> Json.fromBoolean(true)) + } + + case class Success[T](property: String, data: T) + object Success { + implicit def encode[T](implicit encodeT: Encoder[T]): Encoder[Success[T]] = (s: Success[T]) => + Json.obj( + "ok" -> Json.fromBoolean(true), + s.property -> s.data.asJson + ) + } +} diff --git a/src/main/scala/com/github/sylvansson/socialnetwork/Service.scala b/src/main/scala/com/github/sylvansson/socialnetwork/Service.scala new file mode 100644 index 0000000..5d55dca --- /dev/null +++ b/src/main/scala/com/github/sylvansson/socialnetwork/Service.scala @@ -0,0 +1,11 @@ +package com.github.sylvansson.socialnetwork + +import com.github.sylvansson.socialnetwork.Endpoints._ +import com.twitter.finagle.Http +import com.twitter.util.Await + +object Service extends App { + Await.ready( + Http.serve(":8080", service) + ) +} diff --git a/src/main/scala/com/github/sylvansson/socialnetwork/package.scala b/src/main/scala/com/github/sylvansson/socialnetwork/package.scala index 96e7aff..ba0ed17 100644 --- a/src/main/scala/com/github/sylvansson/socialnetwork/package.scala +++ b/src/main/scala/com/github/sylvansson/socialnetwork/package.scala @@ -6,7 +6,7 @@ import java.util.UUID import io.getquill._ package object socialnetwork { - val ctx = new PostgresJdbcContext(NamingStrategy(SnakeCase, PluralizedTableNames), "ctx") + val ctx = new PostgresJdbcContext(NamingStrategy(SnakeCase, PluralizedTableNames), "jdbc") import ctx._ case class User(id: UUID) { @@ -53,5 +53,14 @@ package object socialnetwork { def findPending(userId: UUID) = find(userId, Types.Pending) def findAccepted(userId: UUID) = find(userId, Types.Accepted) def findAll(userId: UUID) = find(userId, Types.All) + + def accept(requesterId: UUID, requesteeId: UUID): Long = + ctx.run( + query + .filter(_.requesterId == lift(requesterId)) + .filter(_.requesteeId == lift(requesteeId)) + .filter(_.since.isEmpty) + .update(_.since -> lift(Option(LocalDateTime.now))) + ) } }