Skip to content

Commit

Permalink
Version 2.2
Browse files Browse the repository at this point in the history
  • Loading branch information
makiftutuncu committed Oct 18, 2017
1 parent 61d4c33 commit f3ced70
Show file tree
Hide file tree
Showing 16 changed files with 220 additions and 224 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ API Reference
"errors": [
{
"name": "requestFailed",
"reason": "Diyanet returned content type.",
"data": "321"
"reason": "Diyanet returned invalid content type.",
"data": "applocation/xml"
}
]
}
Expand Down Expand Up @@ -131,6 +131,7 @@ It returns prayer times for a month belonging to given `countryId`, `cityId` and

* Every key in ``prayerTimes`` object is the date for the prayer times in the value object. Dates are formatted as: `yyyy-MM-dd`
* `fajr`, `shuruq`, `dhuhr`, `asr`, `maghrib`, `isha` and `qibla` are times formatted as `HH:mm`, representing the named times respectively.
* `qibla` key might not always exist.

***

Expand All @@ -141,7 +142,7 @@ Muezzin API is licensed under the terms of the MIT License.
```
MIT License
Copyright (c) 2016 Mehmet Akif Tütüncü
Copyright (c) 2017 Mehmet Akif Tütüncü
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.mehmetakiftutuncu.muezzinapi.controllers

import java.time.Duration
import javax.inject.{Inject, Singleton}

import com.github.mehmetakiftutuncu.errors.{CommonError, Errors}
import com.google.firebase.database.DatabaseReference.CompletionListener
import com.google.firebase.database.{DatabaseError, DatabaseReference}
import com.mehmetakiftutuncu.muezzinapi.data.FirebaseRealtimeDatabase._
import com.mehmetakiftutuncu.muezzinapi.data.AbstractFirebaseRealtimeDatabase
import com.mehmetakiftutuncu.muezzinapi.utilities.{AbstractConf, ControllerBase, Log, Timer}
import play.api.libs.json.Json
import play.api.mvc.{Action, AnyContent}

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Promise

@Singleton
class NukeController @Inject()(Conf: AbstractConf,
FirebaseRealtimeDatabase: AbstractFirebaseRealtimeDatabase) extends ControllerBase {
private val whatCanBeNuked: Set[String] = Set("countries", "prayerTimes")

def nuke(target: String, code: String): Action[AnyContent] = Action.async {
if (!whatCanBeNuked.contains(target)) {
futureFailWithErrors("You don't know what you're nuking!", Errors(CommonError.invalidData.reason("Invalid nuke target!").data(target)))
} else if (!Conf.getString("muezzinApi.nuke.code").contains(code)) {
futureFailWithErrors("You don't know nuke codes!", Errors(CommonError.authorization.reason("Invalid nuke code!")))
} else {
Timer.start(s"nuke.$target")

val reference: DatabaseReference = FirebaseRealtimeDatabase.root / target

val promise: Promise[Errors] = Promise[Errors]()

reference.removeValue(new CompletionListener {
override def onComplete(databaseError: DatabaseError, databaseReference: DatabaseReference): Unit = {
val errors: Errors = if (databaseError != null) {
Errors(CommonError.database.reason(databaseError.toException.getMessage))
} else {
Errors.empty
}

promise.success(errors)
}
})

promise.future.map { errors: Errors =>
val duration: Duration = Timer.stop(s"nuke.$target")

if (errors.hasErrors) {
failWithErrors(s"""Failed to nuke "$target"!""", errors)
} else {
Log.debug(s"""Successfully nuked "$target" in ${duration.toMillis} ms.""")

success(Json.obj("target" -> "destroyed"))
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ class PrayerTimesController @Inject()(CountryService: AbstractCountryService,
if (countryAsOpt.isEmpty) {
futureFailWithErrors(log, Errors(CommonError.notFound.data(countryId.toString)))
} else {
val country: Country = countryAsOpt.get

CityService.getCities(countryId).flatMap {
case Left(cityErrors: Errors) =>
futureFailWithErrors(log, cityErrors)
Expand All @@ -41,8 +39,6 @@ class PrayerTimesController @Inject()(CountryService: AbstractCountryService,
if (cityAsOpt.isEmpty) {
futureFailWithErrors(log, Errors(CommonError.notFound.data(cityId.toString)))
} else {
val city: City = cityAsOpt.get

DistrictService.getDistricts(countryId, cityId).flatMap {
case Left(districtErrors: Errors) =>
futureFailWithErrors(log, districtErrors)
Expand All @@ -55,7 +51,7 @@ class PrayerTimesController @Inject()(CountryService: AbstractCountryService,
} else if (districtId.isEmpty && districts.nonEmpty) {
futureFailWithErrors(log, Errors(CommonError.invalidRequest.reason(s"""District id is not provided but city "$cityId" has districts available!""")))
} else {
val place: Place = Place(countryId, cityId, districtId)
val place: Place = Place(countryId, Some(cityId), districtId)

PrayerTimesService.getPrayerTimes(place).map {
case Left(prayerTimesErrors: Errors) =>
Expand Down
18 changes: 10 additions & 8 deletions app/com/mehmetakiftutuncu/muezzinapi/models/Place.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
package com.mehmetakiftutuncu.muezzinapi.models

case class Place(countryId: Int, cityId: Int, districtId: Option[Int]) {
case class Place(countryId: Int, cityId: Option[Int], districtId: Option[Int]) {
def toForm: Map[String, Seq[String]] = {
Map(
"Country" -> Seq(countryId.toString),
"State" -> Seq(cityId.toString),
"period" -> Seq("Aylik")
) ++ districtId.map(id => Map("City" -> Seq(id.toString))).getOrElse(Map.empty[String, Seq[String]])
val countryMap: Map[String, Seq[String]] = Map("ulkeId" -> Seq(countryId.toString))
val cityMap: Map[String, Seq[String]] = cityId.map(id => Map("ilId" -> Seq(id.toString))).getOrElse(Map.empty)
val districtMap: Map[String, Seq[String]] = districtId.map(id => Map("ilceId" -> Seq(id.toString))).getOrElse(Map.empty)

countryMap ++ cityMap ++ districtMap
}

def toLog: String = s"""country "$countryId", city "$cityId" and district "${districtId.map(_.toString).getOrElse("")}""""
def toLog: String = s"""country "$countryId", city "${cityId.map(_.toString).getOrElse("")}" and district "${districtId.map(_.toString).getOrElse("")}""""

def toPath: String = s"country/$countryId${cityId.map("/city/" + _).getOrElse("")}${districtId.map("/district/" + _).getOrElse("")}"

def toPath: String = s"country/$countryId/city/$cityId${districtId.map("/district/" + _.toString).getOrElse("")}"
def toKey: String = s"$countryId${cityId.map("." + _).getOrElse("")}${districtId.map("." + _).getOrElse("")}"
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ case class PrayerTimesOfDay(date: LocalDate,
asr: PrayerTime,
maghrib: PrayerTime,
isha: PrayerTime,
qibla: PrayerTime) {
qibla: Option[PrayerTime]) {
def toJson: JsObject = Json.obj(
date.format(PrayerTimesOfDay.dateFormatter) -> {
fajr.toJson ++
Expand All @@ -22,7 +22,7 @@ case class PrayerTimesOfDay(date: LocalDate,
asr.toJson ++
maghrib.toJson ++
isha.toJson ++
qibla.toJson
qibla.map(_.toJson).getOrElse(Json.obj())
}
)

Expand All @@ -35,7 +35,8 @@ case class PrayerTimesOfDay(date: LocalDate,
map.put("asr", asr.time.format(PrayerTime.timeFormatter))
map.put("maghrib", maghrib.time.format(PrayerTime.timeFormatter))
map.put("isha", isha.time.format(PrayerTime.timeFormatter))
map.put("qibla", qibla.time.format(PrayerTime.timeFormatter))

qibla.foreach(q => map.put("qibla", q.time.format(PrayerTime.timeFormatter)))

map
}
Expand All @@ -52,7 +53,7 @@ object PrayerTimesOfDay {
asr: String,
maghrib: String,
isha: String,
qibla: String): PrayerTimesOfDay = {
qibla: Option[String]): PrayerTimesOfDay = {
PrayerTimesOfDay(
LocalDate.parse(date, dateFormatter),
fajr,
Expand All @@ -72,7 +73,7 @@ object PrayerTimesOfDay {
asr: String,
maghrib: String,
isha: String,
qibla: String): PrayerTimesOfDay = {
qibla: Option[String]): PrayerTimesOfDay = {
PrayerTimesOfDay(
date,
PrayerTime(fajr, PrayerTimeTypes.Fajr),
Expand All @@ -81,7 +82,7 @@ object PrayerTimesOfDay {
PrayerTime(asr, PrayerTimeTypes.Asr),
PrayerTime(maghrib, PrayerTimeTypes.Maghrib),
PrayerTime(isha, PrayerTimeTypes.Isha),
PrayerTime(qibla, PrayerTimeTypes.Qibla)
qibla.map(PrayerTime(_, PrayerTimeTypes.Qibla))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class DistrictService @Inject()(Cache: AbstractCache,

Future.successful(Maybe(districtsFromFirebase))
} else {
DistrictFetcherService.getDistricts(cityId).flatMap {
DistrictFetcherService.getDistricts(countryId, cityId).flatMap {
maybeDistricts: Maybe[List[District]] =>
if (maybeDistricts.hasErrors) {
Future.successful(Maybe(maybeDistricts.errors))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
override def getPrayerTimes(place: Place): Future[Maybe[List[PrayerTimesOfDay]]] = {
val log: String = s"Failed to get prayer times for ${place.toLog}!"

val cacheKey: String = (FirebaseRealtimeDatabase.root / "prayerTimes" / place.toPath).cacheKey
val cacheKey: String = (prayerTimesReference / place.toPath).cacheKey

val prayerTimesFromCacheAsOpt: Option[List[PrayerTimesOfDay]] = Cache.get[List[PrayerTimesOfDay]](cacheKey)

Expand Down Expand Up @@ -82,14 +82,14 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
}

override def setPrayerTimesToFirebase(place: Place, prayerTimes: List[PrayerTimesOfDay]): Future[Errors] = {
Timer.start(s"setPrayerTimesToFirebase.${place.toPath}")
Timer.start(s"setPrayerTimesToFirebase.${place.toKey}")

val prayerTimesReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / place.toPath
val prayerTimesReferenceForPlace: DatabaseReference = prayerTimesReference / place.toPath

val setErrorFutures: List[Future[Errors]] = prayerTimes.map {
prayerTimesOfDay: PrayerTimesOfDay =>
val key: String = prayerTimesOfDay.date.format(PrayerTimesOfDay.dateFormatter)
val prayerTimesOfDayReference: DatabaseReference = prayerTimesReference / key
val prayerTimesOfDayReference: DatabaseReference = prayerTimesReferenceForPlace / key

val promise: Promise[Errors] = Promise[Errors]()

Expand All @@ -114,7 +114,7 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
setErrors: List[Errors] =>
val errors: Errors = setErrors.foldLeft(Errors.empty)(_ ++ _)

val duration: Duration = Timer.stop(s"setPrayerTimesToFirebase.${place.toPath}")
val duration: Duration = Timer.stop(s"setPrayerTimesToFirebase.${place.toKey}")

Log.debug(s"""Set prayer times to Firebase for "${place.toLog}" in ${duration.toMillis} ms.""")

Expand All @@ -125,9 +125,9 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
private def getPrayerTimesFromFirebase(place: Place): Future[Maybe[List[PrayerTimesOfDay]]] = {
val log: String = s"Failed to get prayer times for ${place.toLog} from Firebase Realtime Database!"

Timer.start(s"getPrayerTimesFromFirebase.${place.toPath}")
Timer.start(s"getPrayerTimesFromFirebase.${place.toKey}")

val prayerTimesReference: DatabaseReference = FirebaseRealtimeDatabase.root / "prayerTimes" / place.toPath
val prayerTimesReferenceForPlace: DatabaseReference = prayerTimesReference / place.toPath

val promise: Promise[Maybe[List[PrayerTimesOfDay]]] = Promise[Maybe[List[PrayerTimesOfDay]]]()

Expand Down Expand Up @@ -156,7 +156,8 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
val asr: String = (currentDataSnapshot / "asr").getValue(classOf[String])
val maghrib: String = (currentDataSnapshot / "maghrib").getValue(classOf[String])
val isha: String = (currentDataSnapshot / "isha").getValue(classOf[String])
val qibla: String = (currentDataSnapshot / "qibla").getValue(classOf[String])

val qibla: Option[String] = Option((currentDataSnapshot / "qibla").getValue(classOf[String]))

PrayerTimesOfDay(date, fajr, shuruq, dhuhr, asr, maghrib, isha, qibla)
}
Expand All @@ -172,22 +173,22 @@ class PrayerTimesService @Inject()(Cache: AbstractCache,
}
}

prayerTimesReference.addValueEventListener(valueEventListener)
prayerTimesReferenceForPlace.addValueEventListener(valueEventListener)

val futureResult: Future[Maybe[List[PrayerTimesOfDay]]] = promise.future

futureResult.map {
result: Maybe[List[PrayerTimesOfDay]] =>
prayerTimesReference.removeEventListener(valueEventListener)
prayerTimesReferenceForPlace.removeEventListener(valueEventListener)

val duration: Duration = Timer.stop(s"getPrayerTimesFromFirebase.${place.toPath}")
val duration: Duration = Timer.stop(s"getPrayerTimesFromFirebase.${place.toKey}")

Log.debug(s"""Got prayer times from Firebase for "${place.toLog}" in ${duration.toMillis} ms.""")

result
}.recoverWith {
case _ =>
prayerTimesReference.removeEventListener(valueEventListener)
prayerTimesReferenceForPlace.removeEventListener(valueEventListener)
futureResult
}
}
Expand Down
Loading

0 comments on commit f3ced70

Please sign in to comment.