Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Епик Александр - Phase 1 #1

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
10 changes: 10 additions & 0 deletions app/ DTOs/HolderDTO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package DTOs

import play.api.libs.json.{Json, Reads}

case class HolderDTO(pages: Int,
items: Option[Seq[VacancyDTO]])

object HolderDTO {
implicit val holderDtoReader: Reads[HolderDTO] = Json.reads[HolderDTO]
}
11 changes: 11 additions & 0 deletions app/ DTOs/RegionDTO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package DTOs

import play.api.libs.json.{Json, Reads}

case class RegionDTO(id: String,
name: String,
areas: Seq[RegionDTO])

object RegionDTO {
implicit val regionDtoReader: Reads[RegionDTO] = Json.reads[RegionDTO]
}
12 changes: 12 additions & 0 deletions app/ DTOs/SalaryDTO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package DTOs

import play.api.libs.json.{Json, Reads}

case class SalaryDTO(currency: Option[String],
from: Option[Int],
gross: Option[Boolean],
to: Option[Int])

object SalaryDTO {
implicit val salaryDtoReader: Reads[SalaryDTO] = Json.reads[SalaryDTO]
}
10 changes: 10 additions & 0 deletions app/ DTOs/SnippetDTO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package DTOs

import play.api.libs.json.{Json, Reads}

case class SnippetDTO(requirement: Option[String],
responsibility: Option[String])

object SnippetDTO {
implicit val snippetDtoReader: Reads[SnippetDTO] = Json.reads[SnippetDTO]
}
13 changes: 13 additions & 0 deletions app/ DTOs/VacancyDTO.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package DTOs

import play.api.libs.json.{Json, Reads}

case class VacancyDTO(id: String,
name: Option[String],
alternate_url: Option[String],
snippet: Option[SnippetDTO],
salary: Option[SalaryDTO])

object VacancyDTO {
implicit val vacancyDtoReader: Reads[VacancyDTO] = Json.reads[VacancyDTO]
}
4 changes: 2 additions & 2 deletions app/controllers/JobAggregatorController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class JobAggregatorController @Inject()(val controllerComponents: ControllerComp

def index(text: String, area: Int) = Action.async { implicit request: Request[AnyContent] =>

jobAggregatorService.parse(text, area).map(_ => Ok(""))
.recover(x => InternalServerError("Some exception has occurred"))
jobAggregatorService.aggregateData(text, area).map(_ => Ok(""))
.recover(exception => InternalServerError("Following exception has occurred: " + exception.getMessage))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

вместо конкатенации можно использовать интерполяцию:

 InternalServerError(s"Following exception has occurred: ${exception.getMessage}")

}
}
14 changes: 6 additions & 8 deletions app/model/Job.scala
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
package model

import java.util.UUID

case class Job(id: Int,
title: Option[String],
requirement: Option[String],
responsibility: Option[String],
alternate_url: Option[String],
salary_from: Option[Int],
salary_to: Option[Int],
salary_currency: Option[String],
salary_gross: Option[Boolean],
alternateUrl: Option[String],
salaryFrom: Option[Int],
salaryTo: Option[Int],
salaryCurrency: Option[String],
salaryGross: Option[Boolean],
city: Option[String],
key_word: Option[String])
keyWord: Option[String])
17 changes: 8 additions & 9 deletions app/model/db/JobTable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package model.db

import model.Job
import slick.jdbc.PostgresProfile.api._
import java.util.UUID

class JobTable(tag: Tag) extends Table[Job](tag, "job") {
def id = column[Int]("id", O.PrimaryKey)
Expand All @@ -13,23 +12,23 @@ class JobTable(tag: Tag) extends Table[Job](tag, "job") {

def responsibility = column[Option[String]]("responsibility")

def alternate_url = column[Option[String]]("alternate_url")
def alternateUrl = column[Option[String]]("alternate_url")

def salary_from = column[Option[Int]]("salary_from")
def salaryFrom = column[Option[Int]]("salary_from")

def salary_to = column[Option[Int]]("salary_to")
def salaryTo = column[Option[Int]]("salary_to")

def salary_currency = column[Option[String]]("salary_currency")
def salaryCurrency = column[Option[String]]("salary_currency")

def salary_gross = column[Option[Boolean]]("salary_gross")
def salaryGross = column[Option[Boolean]]("salary_gross")

def city = column[Option[String]]("city")

def key_word = column[Option[String]]("key_word")
def keyWord = column[Option[String]]("key_word")


def * =
(id, title, requirement, responsibility, alternate_url, salary_from, salary_to, salary_currency, salary_gross,
city, key_word) <> (Job.tupled, Job.unapply)
(id, title, requirement, responsibility, alternateUrl, salaryFrom, salaryTo, salaryCurrency, salaryGross,
city, keyWord) <> (Job.tupled, Job.unapply)
}

35 changes: 25 additions & 10 deletions app/scheduler/Task.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,37 @@ import akka.actor.ActorSystem

import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import play.api.Configuration
import play.api.{Configuration, Logger, Mode}
import service.JobAggregatorService

import scala.util.Try

class Task @Inject()(actorSystem: ActorSystem,
configuration: Configuration,
jobAggregatorService: JobAggregatorService)(implicit executionContext: ExecutionContext) {

val initialDelay: String = configuration.get[String]("initialDelay")
val interval: String = configuration.get[String]("interval")
val cities: Seq[String] = configuration.get[Seq[String]]("cities")
val keyWords: Seq[String] = configuration.get[Seq[String]]("keyWords")
if (!configuration.has("initialDelay") || !configuration.has("interval")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мы проверяем сразу все поля и, если какого-то нет, падаем с ошибкой общего содержания. Обычно так делать не следует, потому что пользователю (техподдержке) будет не понятно где именно произошла ошибка и придется идти проверять всё. Лучше перенести эти сообщения непосредственно в попытки чтения соответствующего поля и, если его нет, выводить конкретное сообщение

|| !configuration.has("cities") || !configuration.has("keyWords"))
throw new NoSuchFieldException("Configuration doesn't have some of these paths:" +

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

В общем случае кидать исключения не ФП стиль, т.к. исключения это сайд эффект. Альтернатива - конструирование объекта Constants (`Configuration',...), который может быть в одном из двух состояний - построился, и значит с параметрами всё в порядке, или не построился и хранит описание ошибок.

Но конкретно тут без конфигурации не получится запустить приложение в принципе (не будет параметров для запуска), так что не сильно критично. Но лучше переместить это в соответствующие места.

" initialDelay, interval, cities, keyWords")

val initialDelay: Option[String] = configuration.getOptional[String]("initialDelay")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Зачем тип Option, если без поля initialDelayмы выкинем исключение (код выше)
  2. Когда строковая константа встречается больше 1 раза, его выносят в именованную константу

Тут можно сделать одно из двух действий (зависит от задуманной логики):

  1. при отсутствии значения в конфиге подставить значение по умолчанию:
val initialDelay: String = configuration.getOptional[String]("initialDelay").getOrElse("SomeDefaultValue")

2.при отсутствии значения в конфиге выкинуть исключение:

val initialDelay: String = configuration.getOptional[String]("initialDelay").getOrElse(sys.error("Error message"))

val interval: Option[String] = configuration.getOptional[String]("interval")
val cities: Option[Seq[String]] = configuration.getOptional[Seq[String]]("cities")
val keyWords: Option[Seq[String]] = configuration.getOptional[Seq[String]]("keyWords")

if (initialDelay.isEmpty || interval.isEmpty || cities.isEmpty || keyWords.isEmpty)
throw new ClassCastException("Some of these paths have wrong type: initialDelay, interval, cities, keyWords")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Снова общее сообщение об ошибке. Мало того, что не ясно, какой именно параметр имеет не верный формат, не ясно ещё и какой формат должен быть. Видимо, раз идет проверка на пустоту, должен быть Some. Но тогда следует сразу и читать параметр не как Option'ы


val initDelay: Try[FiniteDuration] = Try(Duration(initialDelay.get).asInstanceOf[FiniteDuration])
val interv: Try[FiniteDuration] = Try(Duration(interval.get).asInstanceOf[FiniteDuration])

if(initDelay.isFailure || interv.isFailure)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

лучше проверки проводить сразу в момент чтения

throw new ClassCastException("Initial delay or interval have wrong format")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

аналогично общее сообщение без подробностей
и кстати, зачем выше идет заворачивание в Try, если здесь разворачивание и выброс исключения


actorSystem.scheduler.scheduleAtFixedRate(initialDelay = Duration(initialDelay).asInstanceOf[FiniteDuration],
interval = Duration(interval).asInstanceOf[FiniteDuration]) { () =>
jobAggregatorService.parse(keyWords, cities)
println("Scheduled task executed")
//actorSystem.log.info("Executing something...")
actorSystem.scheduler.scheduleAtFixedRate(initialDelay = initDelay.get,
interval = interv.get) { () =>
jobAggregatorService.aggregateData(keyWords.get, cities.get)
Logger("play").info("Scheduled task executed")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Действительно ли это логгирование относится к play'ю? Обычно делают так, что каждый модуль имеет свой собственный логгер и через него логгирует сообщения. Так делают, чтобы в логах можно было быстро найти сообщения от соответствующего компонента.

Т.е. тут предлагаю сделать 2 действия:

  1. Дать логгеру название соответствующее модулю
  2. Вынести логгер в отдельную переменную:
val logger = Logger("RightName")

}
}
Loading