Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# production
/build

# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
61 changes: 59 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,59 @@
# disaster-management
Lynk Hackathon 2019 - Disaster Management
# Catamaran

Every year, the primary way of communication between the people who need help and the people on the ground is through whatsapp forward chains. It is everywhere, cheap, decentralized and very effective. But, the volunteeers are not very happy about this setup. There's no way to stop the circulation of these messages even the victim has received the much needed help. Some volunteers might get the message 3 days after the fact and will rush to the spot thinking that it's new only to realize that it is a dead issue.
This is a huge waste of resources, if you think about it.

Catamaran aims to solve this!


### Technologies used
- React
- Akka-http
- Scala
- Slick
- Postgres

It also uses `Twilio` to send messages through whatsapp.

### Architecture
```
+---------------------------------------------------------------------------+
| API |
| +--------------------------------------------------------------------+ |
| | Service layer | |
| | +-----------------------------------------------------------+ | |
| | | DAO | | |
| | | +-------------------------------------------------+ | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | Domain Model | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | | | | | |
| | | +-------------------------------------------------+ | | |
| | | | | |
| | | | | |
| | | | | |
| | +-----------------------------------------------------------+ | |
| | | |
| | | |
| +--------------------------------------------------------------------+ |
| |
| |
+---------------------------------------------------------------------------+
```
We use onion architecture in which the layers talk only to the layer immediately below them.

### Domain models:
- Tickets
- Volunteers



3 changes: 3 additions & 0 deletions api/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
target/
*.iml
44 changes: 44 additions & 0 deletions api/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@


scalaVersion := "2.12.8"
version := "0.1.0-SNAPSHOT"

resolvers ++= Seq(
"Millhouse Bintray" at "http://dl.bintray.com/themillhousegroup/maven"
)

val akkaHttpVersion = "10.1.7"
val akkaVersion = "2.5.19"
val akkaStack = Seq(
"com.typesafe.akka" %% "akka-http" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-http-spray-json" % akkaHttpVersion,
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-stream" % akkaVersion
)


val scalaj = "org.scalaj" %% "scalaj-http" % "2.4.2"

val scaldingArgs = "com.twitter" %% "scalding-args" % "0.17.4"

val logbackVersion = "1.1.7"
val logbackCore = "ch.qos.logback" % "logback-core" % logbackVersion
val logbackClassic = "ch.qos.logback" % "logback-classic" % logbackVersion
val scalaLogging = "com.typesafe.scala-logging" %% "scala-logging" % "3.9.2"
val logging = Seq(logbackCore, logbackClassic, scalaLogging)

val jodaTime = "joda-time" % "joda-time" % "2.9.9"
val jodaConvert = "org.joda" % "joda-convert" % "1.8.1"
val jodaDependencies = Seq(jodaTime, jodaConvert)

val slickVersion = "3.3.0"
val slick = "com.typesafe.slick" %% "slick" % slickVersion
val slickHikari = "com.typesafe.slick" %% "slick-hikaricp" % slickVersion
val postgres = "org.postgresql" % "postgresql" % "9.4.1212"
val flyway = "org.flywaydb" % "flyway-core" % "4.1.1"
val slickStack = Seq(slick, postgres, slickHikari, flyway)


val rootDependencies = Seq(scaldingArgs, scalaj) ++ akkaStack ++ slickStack ++ logging ++ jodaDependencies

libraryDependencies ++= rootDependencies
12 changes: 12 additions & 0 deletions api/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
version: "3.7"

services:
catamaran_db:
image: postgres:11.2
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=pass
- POSTGRES_PORT=5433
- POSTGRES_DB=catamaran_db
ports:
- 5432:5432
1 change: 1 addition & 0 deletions api/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 1.3.2
1 change: 1 addition & 0 deletions api/project/plugins.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.8")
27 changes: 27 additions & 0 deletions api/src/main/resources/application.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
catamaran {
db {
postgres {
dataSourceClass = "org.postgresql.ds.PGSimpleDataSource"
connectionTimeout = 10000
properties = {
serverName = "localhost"
serverName = ${?CATAMARAN_DB_SERVER}
portNumber = 5432
portNumber = ${?CATAMARAN_DB_PORT}
databaseName = "catamaran_db"
databaseName = ${?CATAMARAN_DB_NAME}
user = "postgres"
user = ${?CATAMARAN_DB_USER}
password = "pass"
password = ${?CATAMARAN_DB_PASSWORD}
}
}
}
twilio {
authToken = "3a9e41ce24b30c177cfad9780ce8f113"
sid = "ACa7c6316c3c162efdd614e685969ca6ab"
from = "whatsapp:+14155238886"
}
}
}
11 changes: 11 additions & 0 deletions api/src/main/resources/db/migration/V1__create_tickets.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
CREATE TABLE tickets (
id uuid primary key,
issue_type varchar not null,
message varchar not null,
status varchar not null,
address varchar not null,
phone_number varchar not null,
created_timestamp date,
dispatched_timestamp date,
resolved_timestamp date
);
8 changes: 8 additions & 0 deletions api/src/main/resources/db/migration/V2__create_volunteers.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE TABLE volunteers (
id uuid primary key,
first_name varchar not null,
last_name varchar not null,
gender varchar not null,
email varchar not null,
phone_number varchar not null
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE TABLE assigned_tickets (
ticket_id uuid REFERENCES tickets(id),
volunteer_id uuid REFERENCES volunteers(id)
);
4 changes: 4 additions & 0 deletions api/src/main/resources/db/migration/V4__date_to_timestamp.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ALTER TABLE tickets
ALTER COLUMN created_timestamp TYPE timestamp,
ALTER COLUMN dispatched_timestamp TYPE timestamp,
ALTER COLUMN resolved_timestamp TYPE timestamp;
5 changes: 5 additions & 0 deletions api/src/main/resources/db/migration/V5__user_details.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
CREATE TABLE users (
volunteer_id uuid primary key references volunteers(id),
email varchar not null,
password varchar not null
);
12 changes: 12 additions & 0 deletions api/src/main/scala/dao/AssignedTicketDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package dao

import models.{AssignedTicket, AssignedTicketSchema, User, UserSchema}
import sql.SqlDatabase

case class AssignedTicketDao(database: SqlDatabase) extends AssignedTicketSchema {
import database.driver.api._

def assignUser(assignedTicket: AssignedTicket) = {
database.db.run(assignedTickets += assignedTicket)
}
}
33 changes: 33 additions & 0 deletions api/src/main/scala/dao/TicketDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package dao

import java.util.UUID

import models.{Ticket, TicketSchema}
import sql.SqlDatabase

import scala.concurrent.{ExecutionContext, Future}

class TicketDao(protected val database: SqlDatabase)(implicit val ec: ExecutionContext) extends TicketSchema {

import database._
import database.driver.api._

def addTicket(ticket: Ticket): Future[Ticket] = {
db.run(tickets += ticket).map(_ => ticket)
}

def listTickets(statusOpt: Option[String], queryOpt: Option[String]): Future[Seq[Ticket]] = {
val query = tickets.filterOpt(statusOpt)((table, status) => table.status === status).filterOpt(queryOpt)((table, query) => table.message === query)
db.run(query.result)
}

def getTicketById(id: UUID): Future[Option[Ticket]] = {
db.run(tickets.filter(_.id === id).result).map(_.headOption)
}

def updateTicket(id: UUID, status: String) = {
db.run(tickets.filter(_.id === id).map(_.status).update(status))
}


}
22 changes: 22 additions & 0 deletions api/src/main/scala/dao/UserDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dao

import java.util.UUID

import models.{User, UserSchema}
import service.UserInfo
import sql.SqlDatabase

import scala.concurrent.{ExecutionContext, Future}

case class UserDao(database: SqlDatabase)(implicit ec: ExecutionContext) extends UserSchema {
import database.driver.api._

def addUser(user: User) = {
database.db.run(users += user)
}

def fetchUser(userInfo: UserInfo): Future[Option[UUID]] = {
val query = users.filter(_.email === userInfo.email).filter(_.password === userInfo.password)
database.db.run(query.result).map(_.headOption.map(_.volunteerId))
}
}
34 changes: 34 additions & 0 deletions api/src/main/scala/dao/VolunteerDao.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dao

import java.util.UUID

import models.{Volunteer, VolunteerSchema}
import sql.SqlDatabase
import scala.concurrent.{ExecutionContext, Future}

case class VolunteerDao(database: SqlDatabase)(implicit val ec: ExecutionContext) extends VolunteerSchema {

import database._
import database.driver.api._

def addVolunteer(volunteer: Volunteer) = {
db.run(volunteers += volunteer)
}

def findVolunteerBy(firstName: String, lastName: String, email: String, gender: String, phoneNo: String) = {
db.run(volunteers.filter {
v =>
v.firstName === firstName &&
v.lastName === lastName &&
v.email === email &&
v.gender === gender &&
v.phone === phoneNo
}.result).map(_.headOption)
}

def findVolunteer(id: UUID): Future[Option[Volunteer]] = {
val query = volunteers.filter(_.id === id)
database.db.run(query.result).map(_.headOption)
}
}

25 changes: 25 additions & 0 deletions api/src/main/scala/models/AssignedTicket.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package models

import java.util.UUID

import slick.lifted
import sql.SqlDatabase

case class AssignedTicket(ticketId: UUID, volunteerId: UUID)

trait AssignedTicketSchema {
protected val database: SqlDatabase

import database.driver.api._

val assignedTickets = lifted.TableQuery[AssignedTickets]

protected class AssignedTickets(tag: Tag) extends Table[AssignedTicket](tag, "assigned_tickets") {
def ticketId = column[UUID]("ticket_id")

def volunteerId = column[UUID]("volunteer_id")

def * = (ticketId, volunteerId) <>
((AssignedTicket.apply _).tupled, AssignedTicket.unapply)
}
}
Loading