blob: 2bbe4095cf5e7e92a9111b5e080d95df12ac3941 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.nlpcraft.server.rest
import akka.http.scaladsl.coding.Coders
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
import akka.http.scaladsl.model.HttpMethods._
import akka.http.scaladsl.model._
import akka.http.scaladsl.model.headers._
import akka.http.scaladsl.server.Directives.{entity, _}
import akka.http.scaladsl.server._
import com.fasterxml.jackson.core.JsonProcessingException
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.scala.DefaultScalaModule
import com.typesafe.scalalogging.LazyLogging
import io.opencensus.stats.Measure
import io.opencensus.trace.{Span, Status}
import org.apache.commons.validator.routines.UrlValidator
import org.apache.nlpcraft.common.opencensus.NCOpenCensusTrace
import org.apache.nlpcraft.common.pool.NCThreadPoolManager
import org.apache.nlpcraft.common.util.NCUtils.{jsonToJavaMap, uncompress}
import org.apache.nlpcraft.common.{JavaMeta, NCE, U}
import org.apache.nlpcraft.model.NCModelView
import org.apache.nlpcraft.server.apicodes.NCApiStatusCode.{API_OK, _}
import org.apache.nlpcraft.server.company.NCCompanyManager
import org.apache.nlpcraft.server.feedback.NCFeedbackManager
import org.apache.nlpcraft.server.mdo.{NCQueryStateMdo, NCUserMdo}
import org.apache.nlpcraft.server.opencensus.NCOpenCensusServerStats
import org.apache.nlpcraft.server.probe.NCProbeManager
import org.apache.nlpcraft.server.query.NCQueryManager
import org.apache.nlpcraft.server.sugsyn.NCSuggestSynonymManager
import org.apache.nlpcraft.server.user.NCUserManager
import spray.json.DefaultJsonProtocol._
import spray.json.{JsObject, JsValue, RootJsonFormat}
import scala.concurrent.{ExecutionContext, Future}
import scala.jdk.CollectionConverters._
/**
* REST API default implementation.
*/
class NCBasicRestApi extends NCRestApi with LazyLogging with NCOpenCensusTrace with NCOpenCensusServerStats {
protected final val URL_VALIDATOR = new UrlValidator(Array("http", "https"), UrlValidator.ALLOW_LOCAL_URLS)
final val API_VER = 1
final val API = "api" / s"v$API_VER"
/** */
private final val CORS_HDRS = List(
`Access-Control-Allow-Origin`.*,
`Access-Control-Allow-Credentials`(true),
`Access-Control-Allow-Headers`("Authorization", "Content-Type", "X-Requested-With")
)
private final val JS_MAPPER = new ObjectMapper()
JS_MAPPER.registerModule(DefaultScalaModule)
/*
* General control exception.
* Note that these classes must be public because scala 2.11 internal errors (compilations problems).
*/
case class AccessTokenFailure(acsTkn: String) extends NCE(s"Unknown or expired access token: $acsTkn")
case class SignInFailure(email: String) extends NCE(s"Invalid or unknown user email: $email")
case class AdminRequired(email: String) extends NCE(s"Admin privileges required: $email")
case class InvalidOperation(email: String) extends NCE(s"Invalid operation.")
case class NotImplemented() extends NCE("Not implemented.")
class InvalidArguments(msg: String) extends NCE(msg)
case class OutOfRangeField(fn: String, from: Number, to: Number) extends InvalidArguments(s"Parameter ($fn) value is out of range ($from, $to).")
case class TooLargeField(fn: String, max: Int) extends InvalidArguments(s"Parameter ($fn) value exceeded max length of $max.")
case class InvalidField(fn: String) extends InvalidArguments(s"Parameter invalid: $fn")
case class EmptyField(fn: String) extends InvalidArguments(s"Parameter cannot be empty: $fn")
case class InvalidExternalUserId(usrExtId: String) extends InvalidArguments(s"External user ID is invalid or unknown: $usrExtId")
case class InvalidUserId(id: Long) extends InvalidArguments(s"User ID is invalid or unknown: $id")
case class InvalidModelId(id: String) extends InvalidArguments(s"Unknown model ID: $id")
case class InvalidModelOrElementId(mdlId: String, elmId: String) extends InvalidArguments(s"Unknown model element ID: $elmId")
case class AskReqHolder(
usrId: Long,
txt: String,
mdlId: String,
usrAgent: Option[String],
rmtAddr: Option[String],
data: Option[String],
enableLog: Boolean,
parent: Span
)
/*
* Maximum length specification for frequently used standard REST parameters,
*/
private final val STD_FIELD_LENGTHS = Map[String, Int](
"srvReqId" -> 32,
"mdlId" -> NCModelView.MODEL_ID_MAXLEN,
"elmId" -> NCModelView.MODEL_ELEMENT_ID_MAXLEN,
"country" -> 32,
"postalCode" -> 32,
"usrExtId" -> 64,
"name" -> 64,
"firstName" -> 64,
"lastName" -> 64,
"passwd" -> 64,
"newPasswd" -> 64,
"email" -> 64,
"adminEmail" -> 64,
"adminPasswd" -> 64,
"adminFirstName" -> 64,
"adminLastName" -> 64,
"website" -> 256,
"acsTok" -> 256,
"city" -> 512,
"region" -> 512,
"address" -> 512,
"txt" -> 1024,
"comment" -> 1024,
"avatarUrl" -> 512000,
"adminAvatarUrl" -> 512000
)
/**
*
* @param o
* @return
*/
@throws[NCE]
private def toJs(o: AnyRef): String =
try
JS_MAPPER.writeValueAsString(o)
catch {
case e: JsonProcessingException => throw new NCE(s"JSON serialization error.", e)
}
/**
*
* @param acsTkn Access token to check.
* @param shouldBeAdmin Admin flag.
* @return
*/
@throws[NCE]
private def authenticate0(acsTkn: String, shouldBeAdmin: Boolean): NCUserMdo =
startScopedSpan("authenticate0", "acsTkn" -> acsTkn, "shouldBeAdmin" -> shouldBeAdmin) { span =>
NCUserManager.getUserForAccessToken(acsTkn, span) match {
case None => throw AccessTokenFailure(acsTkn)
case Some(usr) =>
require(usr.email.isDefined)
if (shouldBeAdmin && !usr.isAdmin)
throw AdminRequired(usr.email.get)
usr
}
}
/**
*
* @param rmtAddr
* @return
*/
private def getAddress(rmtAddr: RemoteAddress): Option[String] =
rmtAddr.toOption match {
// 127.0.0.1 used to avoid local addresses like 0:0:0:0:0:0:0:1 (IPv6)
case Some(a) => Some(if (a.getHostName == "localhost") "127.0.0.1" else a.getHostAddress)
case None => None
}
/**
*
* @param acsUsr
* @param srvReqIdsOpt
* @param usrIdOpt
* @param usrExtIdOpt
* @param span
*/
@throws[AdminRequired]
private def getRequests(
acsUsr: NCUserMdo,
srvReqIdsOpt: Option[Set[String]],
usrIdOpt: Option[Long],
usrExtIdOpt: Option[String],
span: Span
): Set[NCQueryStateMdo] = {
require(acsUsr.email.isDefined)
val userId = getUserId(acsUsr, usrIdOpt, usrExtIdOpt)
val states = srvReqIdsOpt match {
case Some(srvReqIds) =>
val states = NCQueryManager.getForServerRequestIds(srvReqIds, span)
if (usrIdOpt.isDefined || usrExtIdOpt.isDefined) states.filter(_.userId == userId) else states
case None => NCQueryManager.getForUserId(userId, span)
}
if (states.exists(_.companyId != acsUsr.companyId) || !acsUsr.isAdmin && states.exists(_.userId != acsUsr.id))
throw AdminRequired(acsUsr.email.get)
states
}
/**
*
* @param s Query state MDO to convert to map.
* @return
*/
private def queryStateToMap(s: NCQueryStateMdo): java.util.Map[String, Any] =
Map(
"srvReqId" -> s.srvReqId,
"txt" -> s.text,
"usrId" -> s.userId,
"mdlId" -> s.modelId,
"probeId" -> s.probeId.orNull,
"status" -> s.status,
"resType" -> s.resultType.orNull,
"resBody" -> (
if (s.resultBody.isDefined &&
s.resultType.isDefined &&
s.resultType.get == "json"
)
U.jsonToObject(s.resultBody.get)
else
s.resultBody.orNull
),
"resMeta" -> s.resultMeta.orNull,
"error" -> s.error.orNull,
"errorCode" -> s.errorCode.map(Integer.valueOf).orNull,
"logHolder" -> (if (s.logJson.isDefined) U.jsonToObject(s.logJson.get) else null),
"intentId" -> s.intentId.orNull
).filter(_._2 != null).asJava
/**
* Extracts and checks JSON.
*
* @param jsOpt JSON value. Optional.
* @param name Property name.
*/
@throws[TooLargeField]
@throws[InvalidField]
private def extractJson(jsOpt: Option[spray.json.JsValue], name: String): Option[String] =
jsOpt match {
case Some(js) =>
val s = js.compactPrint
checkLength(name, s, 512000)
// Validates.
try
U.jsonToJavaMap(s)
catch {
case _: NCE => throw InvalidField(name)
}
Some(s)
case None => None
}
/**
*
* @param r Route to CORS enable.
*/
protected def corsHandler(r: Route): Route = respondWithHeaders(CORS_HDRS) {
options {
complete(HttpResponse(StatusCodes.OK).
withHeaders(`Access-Control-Allow-Methods`(OPTIONS, POST, PUT, GET, DELETE)))
} ~ r
}
/**
* Checks operation permissions and gets user ID.
*
* @param acsUsr Currently signed-in user.
* @param usrIdOpt User ID. Optional.
* @param usrExtIdOpt User 'on-behalf-of' external ID. Optional.
*/
@throws[AdminRequired]
@throws[InvalidUserId]
@throws[InvalidExternalUserId]
protected def getUserId(
acsUsr: NCUserMdo,
usrIdOpt: Option[Long],
usrExtIdOpt: Option[String]
): Long = {
require(acsUsr.email.isDefined)
val id1Opt = usrIdOpt match {
case Some(userId) =>
if (!acsUsr.isAdmin && userId != acsUsr.id)
throw AdminRequired(acsUsr.email.get)
val usr = NCUserManager.getUserById(userId).getOrElse(throw InvalidUserId(userId))
if (usr.companyId != acsUsr.companyId)
throw InvalidUserId(userId)
Some(userId)
case None => None
}
val id2Opt = usrExtIdOpt match {
case Some(usrExtId) =>
if (!acsUsr.isAdmin)
throw AdminRequired(acsUsr.email.get)
Some(NCUserManager.getOrInsertExternalUserId(acsUsr.companyId, usrExtId))
case None => None
}
if (id1Opt.isDefined && id2Opt.isDefined && id1Opt.get != id2Opt.get)
throw new InvalidArguments("User ID and external user ID are inconsistent.")
id1Opt.getOrElse(id2Opt.getOrElse(acsUsr.id))
}
/**
*
* @param acsTkn Access token to check.
*/
@throws[NCE]
protected def authenticate(acsTkn: String): NCUserMdo = authenticate0(acsTkn, shouldBeAdmin = false)
/**
*
* @param acsTkn Access token to check.
*/
@throws[NCE]
protected def authenticateAsAdmin(acsTkn: String): NCUserMdo = authenticate0(acsTkn, shouldBeAdmin = true)
/**
*
* @param mdlId
* @param compId
*/
@throws[InvalidField]
protected def checkModelId(mdlId: String, compId: Long): Unit =
if (!NCProbeManager.existsForModel(compId, mdlId))
throw InvalidModelId(mdlId)
/**
* Checks length of field value.
*
* @param name Field name.
* @param v Field value.
* @param maxLen Optional maximum length. If not specified - the standard range for 'name' will be used.
*/
@throws[TooLargeField]
@throws[EmptyField]
protected def checkLength(name: String, v: String, maxLen: Int = -1): Unit = {
val max: Int =
if (maxLen == -1)
STD_FIELD_LENGTHS.
getOrElse(name, throw new AssertionError(s"Unknown standard REST field: $name"))
else
maxLen
if (v.length > max)
throw TooLargeField(name, max)
else if (v.length < 1)
throw EmptyField(name)
}
/**
* Checks length of field value.
*
* @param name Field name.
* @param v Field value.
* @param maxLen Maximum length.
*/
@throws[TooLargeField]
@throws[EmptyField]
def checkLengthOpt(name: String, v: Option[_], maxLen: Int = -1): Unit =
if (v.isDefined)
checkLength(name, v.get.toString, maxLen)
/**
*
* @param pairs
*/
@throws[TooLargeField]
@throws[EmptyField]
protected def checkLength(pairs: (String, Any)*): Unit =
pairs.foreach {
case (name, value: Option[_]) => checkLengthOpt(name, value)
case (name, value: String) => checkLength(name, value)
case _ => assert(false)
}
/**
* Checks numeric range of field value.
*
* @param name Field name.
* @param v Field value.
* @param from Minimum from.
* @param to Maximum to.
*/
@throws[OutOfRangeField]
protected def checkRange[T](name: String, v: T, from: T, to: T)(implicit e: T => Number): Unit = {
val d = v.doubleValue()
if (d < from.doubleValue() || d > to.doubleValue())
throw OutOfRangeField(name, from, to)
}
/**
* Checks numeric range of field value.
*
* @param name Field name.
* @param vOpt Field value. Optional.
* @param from Minimum from.
* @param to Maximum to.
*/
@throws[OutOfRangeField]
protected def checkRangeOpt[T](name: String, vOpt: Option[T], from: T, to: T)(implicit e: T => Number): Unit =
if (vOpt.isDefined)
checkRange(name, vOpt.get, from, to)
/**
*
* @param js
* @param fn
*/
@throws[InvalidField]
protected def convert[T](js: JsObject, fn: String, extractor: JsValue => T): T =
try
extractor(js.fields(fn))
catch {
case _: Exception => throw InvalidField(fn)
}
/**
*
* @param js
* @param fn
* @param extractor
* @tparam T
* @return
*/
@throws[InvalidField]
protected def convertOpt[T](js: JsObject, fn: String, extractor: JsValue => T): Option[T] =
try
js.fields.get(fn) match {
case Some(v) => Some(extractor(v))
case None => None
}
catch {
case _: Exception => throw InvalidField(fn)
}
/**
*
* @return
*/
protected def $signin(): Route = {
case class Req$Signin$(
email: String,
passwd: String
)
case class Res$Signin$(
status: String,
acsTok: String
)
implicit val reqFmt: RootJsonFormat[Req$Signin$] = jsonFormat2(Req$Signin$)
implicit val resFmt: RootJsonFormat[Res$Signin$] = jsonFormat2(Res$Signin$)
// NOTE: no authentication requires on signin.
entity(as[Req$Signin$]) { req =>
startScopedSpan("signin$", "email" -> req.email) { span =>
checkLength("email" -> req.email, "passwd" -> req.passwd)
NCUserManager.signin(
req.email,
req.passwd,
span
) match {
case None => throw SignInFailure(req.email) // Email is unknown (user hasn't signed up).
case Some(acsTkn) => complete {
Res$Signin$(API_OK, acsTkn)
}
}
}
}
}
/**
*
* @return
*/
protected def $health(): Route = {
case class Res$Health$(status: String)
implicit val resFmt: RootJsonFormat[Res$Health$] = jsonFormat1(Res$Health$)
complete {
Res$Health$(API_OK)
}
}
/**
*
* @return
*/
protected def $signout(): Route = {
case class Req$Signout$(
acsTok: String
)
case class Res$Signout$(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Signout$] = jsonFormat1(Req$Signout$)
implicit val resFmt: RootJsonFormat[Res$Signout$] = jsonFormat1(Res$Signout$)
entity(as[Req$Signout$]) { req =>
startScopedSpan("signout$", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val usr = authenticate(req.acsTok)
for (mdlId <- NCProbeManager.getAllProbes(usr.companyId, span).flatMap(_.models.map(_.id))) {
NCProbeManager.clearConversation(usr.id, mdlId, span)
NCProbeManager.clearDialog(usr.id, mdlId, span)
}
NCUserManager.signout(req.acsTok, span)
complete {
Res$Signout$(API_OK)
}
}
}
}
/**
*
* @param fut
*/
private def successWithJs(fut: Future[String]): Route =
onSuccess(fut) {
js => complete(HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, js)))
}
/**
*
* @param o
*/
private def completeJs(o: Object): Route =
complete(HttpResponse(entity = HttpEntity(ContentTypes.`application/json`, toJs(o))))
/**
*
* @param gzipOpt
*/
@throws[NCE]
private def unzipProperties(gzipOpt: Option[String]): Option[JavaMeta] =
gzipOpt match {
case Some(gzip) => Some(jsonToJavaMap(uncompress(gzip)))
case None => None
}
/**
*
* @return
*/
private def ask0(process: AskReqHolder => Route): Route = {
case class Req$Ask$(
acsTok: String,
usrId: Option[Long],
usrExtId: Option[String],
txt: String,
mdlId: String,
data: Option[spray.json.JsValue],
enableLog: Option[Boolean]
)
implicit val reqFmt: RootJsonFormat[Req$Ask$] = jsonFormat7(Req$Ask$)
entity(as[Req$Ask$]) { req =>
startScopedSpan(
"ask$",
"usrId" -> req.usrId.getOrElse(-1),
"usrExtId" -> req.usrExtId.orNull,
"acsTok" -> req.acsTok,
"txt" -> req.txt,
"mdlId" -> req.mdlId) { span =>
checkLength(
"acsTok" -> req.acsTok, "usrExtId" -> req.usrExtId, "mdlId" -> req.mdlId, "txt" -> req.txt
)
val dataJs = extractJson(req.data, "data")
val acsUsr = authenticate(req.acsTok)
checkModelId(req.mdlId, acsUsr.companyId)
optionalHeaderValueByName("User-Agent") { usrAgent =>
extractClientIP { rmtAddr =>
process(AskReqHolder(
usrId = getUserId(acsUsr, req.usrId, req.usrExtId),
txt = req.txt,
mdlId = req.mdlId,
usrAgent = usrAgent,
rmtAddr = getAddress(rmtAddr),
data = dataJs,
req.enableLog.getOrElse(false),
parent = span
))
}
}
}
}
}
/**
*
* @return
*/
protected def $ask(): Route =
ask0(
(h: AskReqHolder) => {
val newSrvReqId = NCQueryManager.asyncAsk(
h.usrId,
h.txt,
h.mdlId,
h.usrAgent,
h.rmtAddr,
h.data,
h.enableLog,
h.parent
)
case class Res$Ask$(
status: String,
srvReqId: String
)
implicit val resFmt: RootJsonFormat[Res$Ask$] = jsonFormat2(Res$Ask$)
complete {
Res$Ask$(API_OK, newSrvReqId)
}
}
)
/**
*
* @return
*/
protected def $ask$Sync(): Route =
ask0(
(h: AskReqHolder) => {
val fut = NCQueryManager.futureAsk(
h.usrId,
h.txt,
h.mdlId,
h.usrAgent,
h.rmtAddr,
h.data,
h.enableLog,
h.parent
)
successWithJs(
fut.collect {
// We have to use Jackson (not spray) here to serialize 'resBody' field.
case res => toJs(
Map(
"status" -> API_OK.toString,
"state" -> queryStateToMap(res)
).asJava
)
}
)
}
)
/**
*
* @return
*/
protected def $cancel(): Route = {
case class Req$Cancel$(
acsTok: String,
usrId: Option[Long],
usrExtId: Option[String],
srvReqIds: Option[Set[String]]
)
case class Res$Cancel$(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Cancel$] = jsonFormat4(Req$Cancel$)
implicit val resFmt: RootJsonFormat[Res$Cancel$] = jsonFormat1(Res$Cancel$)
entity(as[Req$Cancel$]) { req =>
startScopedSpan("cancel$",
"acsTok" -> req.acsTok,
"usrId" -> req.usrId.getOrElse(-1),
"usrExtId" -> req.usrExtId.orNull,
"srvReqIds" -> req.srvReqIds.getOrElse(Nil).mkString(",")) { span =>
checkLength("acsTok" -> req.acsTok, "usrExtId" -> req.usrExtId)
val acsUsr = authenticate(req.acsTok)
val srvReqs = getRequests(acsUsr, req.srvReqIds, req.usrId, req.usrExtId, span)
NCQueryManager.cancelForServerRequestIds(srvReqs.map(_.srvReqId), span)
complete {
Res$Cancel$(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $check(): Route = {
case class Req$Check$(
acsTok: String,
usrId: Option[Long],
usrExtId: Option[String],
srvReqIds: Option[Set[String]],
maxRows: Option[Int]
)
implicit val reqFmt: RootJsonFormat[Req$Check$] = jsonFormat5(Req$Check$)
entity(as[Req$Check$]) { req =>
startScopedSpan(
"check$",
"usrId" -> req.usrId.getOrElse(-1),
"usrExtId" -> req.usrExtId.orNull,
"acsTok" -> req.acsTok,
"srvReqIds" -> req.srvReqIds.getOrElse(Nil).mkString(",")
) { span =>
checkLength("acsTok" -> req.acsTok, "usrExtId" -> req.usrExtId)
checkRangeOpt("maxRows", req.maxRows, 1, java.lang.Integer.MAX_VALUE)
val acsUsr = authenticate(req.acsTok)
val states =
getRequests(acsUsr, req.srvReqIds, req.usrId, req.usrExtId, span).
toSeq.sortBy(-_.createTstamp.getTime).
take(req.maxRows.getOrElse(Integer.MAX_VALUE))
// We have to use Jackson (not spray) here to serialize 'resBody' field.
val js = toJs(
Map(
"status" -> API_OK.toString,
"states" -> states.map(queryStateToMap).asJava
)
.asJava
)
complete(
HttpResponse(
entity = HttpEntity(ContentTypes.`application/json`, js)
)
)
}
}
}
/**
*
* @return
*/
protected def $model$sugsyn(): Route = {
case class Req$Model$Sugsyn(
acsTok: String,
mdlId: String,
minScore: Option[Double]
)
implicit val reqFmt: RootJsonFormat[Req$Model$Sugsyn] = jsonFormat3(Req$Model$Sugsyn)
entity(as[Req$Model$Sugsyn]) { req =>
startScopedSpan(
"model$sugsyn",
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId,
"minScore" -> req.minScore.getOrElse(-1)) { span =>
checkLength("acsTok" -> req.acsTok, "mdlId" -> req.mdlId)
checkRangeOpt("minScore", req.minScore, 0.0, 1.0)
val admUsr = authenticateAsAdmin(req.acsTok)
checkModelId(req.mdlId, admUsr.companyId)
val fut = NCSuggestSynonymManager.suggest(req.mdlId, req.minScore, span)
successWithJs(
fut.collect {
// We have to use Jackson (not spray) here to serialize 'result' field.
case res => toJs(Map("status" -> API_OK.toString, "result" -> res).asJava)
}
)
}
}
}
/**
*
* @return
*/
protected def $model$syns(): Route = {
case class Req$Model$Syns(
acsTok: String,
mdlId: String,
elmId: String
)
implicit val reqFmt: RootJsonFormat[Req$Model$Syns] = jsonFormat3(Req$Model$Syns)
entity(as[Req$Model$Syns]) { req =>
startScopedSpan(
"model$syns",
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId,
"elmId" -> req.elmId) { span =>
checkLength("acsTok" -> req.acsTok, "mdlId" -> req.mdlId, "elmId" -> req.elmId)
val admUsr = authenticateAsAdmin(req.acsTok)
val compId = admUsr.companyId
if (!NCProbeManager.existsForModel(compId, req.mdlId))
throw InvalidModelId(req.mdlId)
if (!NCProbeManager.existsForModelElement(compId, req.mdlId, req.elmId))
throw InvalidModelOrElementId(req.mdlId, req.elmId)
val fut = NCProbeManager.getModelElementInfo(req.mdlId, req.elmId, span)
successWithJs(
fut.collect {
// We have to use Jackson (not spray) here to serialize 'result' field.
case res =>
require(
res.containsKey("synonyms") &&
res.containsKey("synonymsExp") &&
res.containsKey("synonymsExpRatePct") &&
res.containsKey("synonymsExpCnt") &&
res.containsKey("synonymsCnt") &&
res.containsKey("values") &&
res.containsKey("valuesExp")
)
toJs(
Map(
"status" -> API_OK.toString,
"synonymsCnt" -> res.get("synonymsCnt"),
"synonymsExpCnt" -> res.get("synonymsExpCnt"),
"synonymsExpRatePct" -> res.get("synonymsExpRatePct"),
"synonyms" -> res.get("synonyms"),
"synonymsExp" -> res.get("synonymsExp"),
"values" -> res.get("values"),
"valuesExp" -> res.get("valuesExp")
)
)
}
)
}
}
}
/**
*
* @return
*/
protected def $model$info(): Route = {
case class Req$Model$Info(
acsTok: String,
mdlId: String
)
implicit val reqFmt: RootJsonFormat[Req$Model$Info] = jsonFormat2(Req$Model$Info)
entity(as[Req$Model$Info]) { req =>
startScopedSpan(
"model$syns",
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId
) { span =>
checkLength("acsTok" -> req.acsTok, "mdlId" -> req.mdlId)
val admUsr = authenticateAsAdmin(req.acsTok)
val compId = admUsr.companyId
if (!NCProbeManager.existsForModel(compId, req.mdlId))
throw InvalidModelId(req.mdlId)
val fut = NCProbeManager.getModelInfo(req.mdlId, span)
successWithJs(
fut.collect {
// We have to use Jackson (not spray) here to serialize 'result' field.
case res =>
toJs(
Map(
"status" -> API_OK.toString,
"model" -> res
)
)
}
)
}
}
}
/**
*
* @return
*/
protected def $clear$Conversation(): Route = {
case class Req$Clear$Conversation(
acsTok: String,
mdlId: String,
usrId: Option[Long],
usrExtId: Option[String]
)
case class Res$Clear$Conversation(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Clear$Conversation] = jsonFormat4(Req$Clear$Conversation)
implicit val resFmt: RootJsonFormat[Res$Clear$Conversation] = jsonFormat1(Res$Clear$Conversation)
entity(as[Req$Clear$Conversation]) { req =>
startScopedSpan(
"clear$Conversation",
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId,
"usrExtId" -> req.usrExtId.orNull,
"usrId" -> req.usrId.getOrElse(-1)) { span =>
//noinspection DuplicatedCode
checkLength(
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId,
"usrExtId" -> req.usrExtId
)
val acsUsr = authenticate(req.acsTok)
checkModelId(req.mdlId, acsUsr.companyId)
NCProbeManager.clearConversation(getUserId(acsUsr, req.usrId, req.usrExtId), req.mdlId, span)
complete {
Res$Clear$Conversation(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $clear$Dialog(): Route = {
case class Req$Clear$Dialog(
acsTok: String,
mdlId: String,
usrId: Option[Long],
usrExtId: Option[String]
)
case class Res$Clear$Dialog(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Clear$Dialog] = jsonFormat4(Req$Clear$Dialog)
implicit val resFmt: RootJsonFormat[Res$Clear$Dialog] = jsonFormat1(Res$Clear$Dialog)
entity(as[Req$Clear$Dialog]) { req =>
startScopedSpan(
"clear$Dialog",
"acsTok" -> req.acsTok,
"usrExtId" -> req.usrExtId.orNull,
"mdlId" -> req.mdlId,
"usrId" -> req.usrId.getOrElse(-1)) { span =>
//noinspection DuplicatedCode
checkLength(
"acsTok" -> req.acsTok,
"mdlId" -> req.mdlId,
"usrExtId" -> req.usrExtId
)
val acsUsr = authenticate(req.acsTok)
checkModelId(req.mdlId, acsUsr.companyId)
NCProbeManager.clearDialog(getUserId(acsUsr, req.usrId, req.usrExtId), req.mdlId, span)
complete {
Res$Clear$Dialog(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $company$Add(): Route = {
case class Req$Company$Add(
acsTok: String,
// New company.
name: String,
website: Option[String],
country: Option[String],
region: Option[String],
city: Option[String],
address: Option[String],
postalCode: Option[String],
// New company admin.
adminEmail: String,
adminPasswd: String,
adminFirstName: String,
adminLastName: String,
adminAvatarUrl: Option[String],
properties: Option[spray.json.JsValue]
)
case class Res$Company$Add(
status: String,
token: String,
adminId: Long
)
implicit val reqFmt: RootJsonFormat[Req$Company$Add] = jsonFormat14(Req$Company$Add)
implicit val resFmt: RootJsonFormat[Res$Company$Add] = jsonFormat3(Res$Company$Add)
//noinspection DuplicatedCod
entity(as[Req$Company$Add]) { req =>
startScopedSpan("company$Add", "name" -> req.name) { span =>
checkLength(
"acsTok" -> req.acsTok,
"name" -> req.name,
"website" -> req.website,
"country" -> req.country,
"region" -> req.region,
"city" -> req.city,
"address" -> req.address,
"postalCode" -> req.postalCode,
"adminEmail" -> req.adminEmail,
"adminPasswd" -> req.adminPasswd,
"adminFirstName" -> req.adminFirstName,
"adminLastName" -> req.adminLastName,
"adminAvatarUrl" -> req.adminAvatarUrl
)
val propsJs = extractJson(req.properties, "properties")
// Via REST only administrators of already created companies can create new companies.
authenticateAsAdmin(req.acsTok)
val res = NCCompanyManager.addCompany(
req.name,
req.website,
req.country,
req.region,
req.city,
req.address,
req.postalCode,
req.adminEmail,
req.adminPasswd,
req.adminFirstName,
req.adminLastName,
req.adminAvatarUrl,
propsJs,
span
)
complete {
Res$Company$Add(API_OK, res.token, res.adminId)
}
}
}
}
/**
*
* @return
*/
protected def $company$Get(): Route = {
case class Req$Company$Get(
acsTok: String
)
case class Res$Company$Get(
status: String,
id: Long,
name: String,
website: Option[String],
country: Option[String],
region: Option[String],
city: Option[String],
address: Option[String],
postalCode: Option[String],
properties: Option[JavaMeta]
)
implicit val reqFmt: RootJsonFormat[Req$Company$Get] = jsonFormat1(Req$Company$Get)
entity(as[Req$Company$Get]) { req =>
startScopedSpan("company$get", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val acsUsr = authenticate(req.acsTok)
val company = NCCompanyManager.
getCompany(acsUsr.companyId, span).
getOrElse(throw new NCE(s"Company not found: ${acsUsr.companyId}"))
completeJs {
Res$Company$Get(API_OK,
company.id,
company.name,
company.website,
company.country,
company.region,
company.city,
company.address,
company.postalCode,
unzipProperties(company.propertiesGzip)
)
}
}
}
}
/**
*
* @return
*/
protected def $company$Update(): Route = {
case class Req$Company$Update(
// Caller.
acsTok: String,
// Updated company.
name: String,
website: Option[String],
country: Option[String],
region: Option[String],
city: Option[String],
address: Option[String],
postalCode: Option[String],
properties: Option[spray.json.JsValue]
)
case class Res$Company$Update(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Company$Update] = jsonFormat9(Req$Company$Update)
implicit val resFmt: RootJsonFormat[Res$Company$Update] = jsonFormat1(Res$Company$Update)
entity(as[Req$Company$Update]) { req =>
startScopedSpan("company$Update", "acsTok" -> req.acsTok, "name" -> req.name) { span =>
checkLength("acsTok" -> req.acsTok,
"name" -> req.name,
"website" -> req.website,
"country" -> req.country,
"region" -> req.region,
"city" -> req.city,
"address" -> req.address,
"postalCode" -> req.postalCode
)
val propsJs = extractJson(req.properties, "properties")
val admUsr = authenticateAsAdmin(req.acsTok)
NCCompanyManager.updateCompany(
admUsr.companyId,
req.name,
req.website,
req.country,
req.region,
req.city,
req.address,
req.postalCode,
propsJs,
span
)
complete {
Res$Company$Update(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $feedback$Add(): Route = {
case class Req$Feedback$Add(
acsTok: String,
usrId : Option[Long],
usrExtId: Option[String],
srvReqId: String,
score: Double,
comment: Option[String]
)
case class Res$Feedback$Add(
status: String,
id: Long
)
implicit val reqFmt: RootJsonFormat[Req$Feedback$Add] = jsonFormat6(Req$Feedback$Add)
implicit val resFmt: RootJsonFormat[Res$Feedback$Add] = jsonFormat2(Res$Feedback$Add)
entity(as[Req$Feedback$Add]) { req =>
startScopedSpan(
"feedback$Add",
"usrId" -> req.usrId.getOrElse(-1),
"usrExtId" -> req.usrExtId.orNull,
"srvReqId" -> req.srvReqId) { span =>
checkLength(
"acsTok" -> req.acsTok,
"usrExtId" -> req.usrExtId,
"srvReqId" -> req.srvReqId,
"comment" -> req.comment
)
checkRange("score", req.score, 0, 1)
// Via REST only administrators of already created companies can create new companies.
val acsUsr = authenticate(req.acsTok)
val id = NCFeedbackManager.addFeedback(
req.srvReqId,
getUserId(acsUsr, req.usrId, req.usrExtId),
req.score,
req.comment,
span
)
complete {
Res$Feedback$Add(API_OK, id)
}
}
}
}
/**
*
* @return
*/
protected def $feedback$Delete(): Route = {
case class Req$Feedback$Delete(
acsTok: String,
// Feedback IDs to delete (optional).
id: Option[Long]
)
case class Res$Feedback$Delete(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Feedback$Delete] = jsonFormat2(Req$Feedback$Delete)
implicit val resFmt: RootJsonFormat[Res$Feedback$Delete] = jsonFormat1(Res$Feedback$Delete)
entity(as[Req$Feedback$Delete]) { req =>
startScopedSpan("feedback$Delete") { span =>
checkLength("acsTok" -> req.acsTok)
// Via REST only administrators of already created companies can create new companies.
val acsUsr = authenticate(req.acsTok)
require(acsUsr.email.isDefined)
req.id match {
case Some(id) =>
NCFeedbackManager.getFeedback(id, span) match {
case Some(f) =>
val companyId =
NCUserManager.
getUserById(f.userId, span).
getOrElse(throw new NCE(s"Company not found for user: ${f.userId}")).
companyId
if (companyId != acsUsr.companyId || (f.userId != acsUsr.id && !acsUsr.isAdmin))
throw AdminRequired(acsUsr.email.get)
NCFeedbackManager.deleteFeedback(f.id, span)
case None => // No-op.
}
case None =>
if (!acsUsr.isAdmin)
throw AdminRequired(acsUsr.email.get)
NCFeedbackManager.deleteAllFeedback(acsUsr.companyId, span)
}
complete {
Res$Feedback$Delete(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $feedback$All(): Route = {
case class Req$Feedback$All(
acsTok: String,
usrId: Option[Long],
usrExtId: Option[String],
srvReqId: Option[String]
)
case class Feedback_Feedback$All(
id: Long,
srvReqId: String,
usrId: Long,
score: Double,
comment: Option[String],
createTstamp: Long
)
case class Res$Feedback$All(
status: String,
feedback: Seq[Feedback_Feedback$All]
)
implicit val reqFmt: RootJsonFormat[Req$Feedback$All] = jsonFormat4(Req$Feedback$All)
implicit val fbFmt: RootJsonFormat[Feedback_Feedback$All] = jsonFormat6(Feedback_Feedback$All)
implicit val resFmt: RootJsonFormat[Res$Feedback$All] = jsonFormat2(Res$Feedback$All)
entity(as[Req$Feedback$All]) { req =>
startScopedSpan(
"feedback$All",
"usrId" -> req.usrId.getOrElse(-1),
"usrExtId" -> req.usrExtId.orNull
) { span =>
checkLength("acsTok" -> req.acsTok, "srvReqId" -> req.srvReqId, "usrExtId" -> req.usrExtId)
val acsUsr = authenticate(req.acsTok)
require(acsUsr.email.isDefined)
val feedback =
NCFeedbackManager.getFeedback(
acsUsr.companyId,
req.srvReqId,
if (req.usrId.isDefined || req.usrExtId.isDefined)
Some(getUserId(acsUsr, req.usrId, req.usrExtId))
else
None,
span
).map(f =>
Feedback_Feedback$All(
f.id,
f.srvReqId,
f.userId,
f.score,
f.feedbackComment,
f.createdOn.getTime
)
)
if (!acsUsr.isAdmin && feedback.exists(_.usrId != acsUsr.id))
throw AdminRequired(acsUsr.email.get)
complete {
Res$Feedback$All(API_OK, feedback)
}
}
}
}
/**
*
* @return
*/
protected def $company$Token$Reset(): Route = {
case class Req$Company$Token$Reset(
// Caller.
acsTok: String
)
case class Res$Company$Token$Reset(
status: String,
token: String
)
implicit val reqFmt: RootJsonFormat[Req$Company$Token$Reset] = jsonFormat1(Req$Company$Token$Reset)
implicit val resFmt: RootJsonFormat[Res$Company$Token$Reset] = jsonFormat2(Res$Company$Token$Reset)
entity(as[Req$Company$Token$Reset]) { req =>
startScopedSpan("company$Token$Reset", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val admUsr = authenticateAsAdmin(req.acsTok)
val tkn = NCCompanyManager.resetToken(admUsr.companyId, span)
complete {
Res$Company$Token$Reset(API_OK, tkn)
}
}
}
}
/**
*
* @return
*/
protected def $company$Delete(): Route = {
case class Req$Company$Delete(
// Caller.
acsTok: String
)
case class Res$Company$Delete(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$Company$Delete] = jsonFormat1(Req$Company$Delete)
implicit val resFmt: RootJsonFormat[Res$Company$Delete] = jsonFormat1(Res$Company$Delete)
entity(as[Req$Company$Delete]) { req =>
startScopedSpan("company$Delete", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val admUSr = authenticateAsAdmin(req.acsTok)
NCCompanyManager.deleteCompany(admUSr.companyId, span)
complete {
Res$Company$Delete(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $user$Add(): Route = {
case class Req$User$Add(
// Caller.
acsTok: String,
// New user.
email: String,
passwd: String,
firstName: String,
lastName: String,
avatarUrl: Option[String],
isAdmin: Boolean,
properties: Option[spray.json.JsValue],
usrExtId: Option[String]
)
case class Res$User$Add(
status: String,
id: Long
)
implicit val reqFmt: RootJsonFormat[Req$User$Add] = jsonFormat9(Req$User$Add)
implicit val resFmt: RootJsonFormat[Res$User$Add] = jsonFormat2(Res$User$Add)
entity(as[Req$User$Add]) { req =>
startScopedSpan("user$Add", "acsTok" -> req.acsTok, "email" -> req.email) { span =>
checkLength(
"acsTok" -> req.acsTok,
"email" -> req.email,
"passwd" -> req.passwd,
"firstName" -> req.firstName,
"lastName" -> req.lastName,
"avatarUrl" -> req.avatarUrl,
"usrExtId" -> req.usrExtId
)
val propsJs = extractJson(req.properties, "properties")
val admUsr = authenticateAsAdmin(req.acsTok)
val id = NCUserManager.addUser(
admUsr.companyId,
req.email,
req.passwd,
req.firstName,
req.lastName,
req.avatarUrl,
req.isAdmin,
propsJs,
req.usrExtId,
span
)
complete {
Res$User$Add(API_OK, id)
}
}
}
}
/**
*
* @return
*/
protected def $user$Update(): Route = {
case class Req$User$Update(
// Caller.
acsTok: String,
// Update user.
id: Option[Long],
firstName: String,
lastName: String,
avatarUrl: Option[String],
properties: Option[spray.json.JsValue]
)
case class Res$User$Update(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$User$Update] = jsonFormat6(Req$User$Update)
implicit val resFmt: RootJsonFormat[Res$User$Update] = jsonFormat1(Res$User$Update)
entity(as[Req$User$Update]) { req =>
startScopedSpan("user$Update", "acsTok" -> req.acsTok, "usrId" -> req.id.getOrElse(-1)) { span =>
checkLength(
"acsTok" -> req.acsTok,
"firstName" -> req.firstName,
"lastName" -> req.lastName,
"avatarUrl" -> req.avatarUrl
)
val propsJs = extractJson(req.properties, "properties")
val acsUsr = authenticate(req.acsTok)
NCUserManager.updateUser(
getUserId(acsUsr, req.id, None),
req.firstName,
req.lastName,
req.avatarUrl,
propsJs,
span
)
complete {
Res$User$Update(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $user$Delete(): Route = {
case class Req$User$Delete(
acsTok: String,
id: Option[Long],
usrExtId: Option[String]
)
case class Res$User$Delete(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$User$Delete] = jsonFormat3(Req$User$Delete)
implicit val resFmt: RootJsonFormat[Res$User$Delete] = jsonFormat1(Res$User$Delete)
entity(as[Req$User$Delete]) { req =>
startScopedSpan(
"user$Delete", "acsTok" -> req.acsTok, "usrId" -> req.id.getOrElse(-1)
) { span =>
checkLength("acsTok" -> req.acsTok, "usrExtId" -> req.usrExtId)
val acsUsr = authenticate(req.acsTok)
require(acsUsr.email.isDefined)
def delete(id: Long): Unit = {
NCUserManager.signoutAllSessions(id, span)
NCUserManager.deleteUser(id, span)
logger.info(s"User deleted: $id")
}
// Deletes all users from company except initiator.
if (req.id.isEmpty && req.usrExtId.isEmpty) {
if (!acsUsr.isAdmin)
throw AdminRequired(acsUsr.email.get)
NCUserManager.
getAllUsers(acsUsr.companyId, span).
filter(_.id != acsUsr.id).
map(_.id).
foreach(delete)
}
else {
val delUsrId = getUserId(acsUsr, req.id, req.usrExtId)
// Tries to delete own account.
if (delUsrId == acsUsr.id &&
acsUsr.isAdmin &&
!NCUserManager.isOtherAdminsExist(acsUsr.id)
)
throw InvalidOperation(s"Last admin user cannot be deleted: ${acsUsr.email.get}")
delete(delUsrId)
}
complete {
Res$User$Delete(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $user$Admin(): Route = {
case class Req$User$Admin(
acsTok: String,
id: Option[Long],
admin: Boolean
)
case class Res$User$Admin(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$User$Admin] = jsonFormat3(Req$User$Admin)
implicit val resFmt: RootJsonFormat[Res$User$Admin] = jsonFormat1(Res$User$Admin)
entity(as[Req$User$Admin]) { req =>
startScopedSpan(
"user$Admin", "acsTok" -> req.acsTok, "usrId" -> req.id.getOrElse(-1), "admin" -> req.admin
) { span =>
checkLength("acsTok" -> req.acsTok)
val initUsr = authenticateAsAdmin(req.acsTok)
val usrId = req.id.getOrElse(initUsr.id)
// Self update.
if (
usrId == initUsr.id &&
!req.admin &&
!NCUserManager.isOtherAdminsExist(initUsr.id, span)
)
throw InvalidOperation(s"Last admin user cannot lose admin privileges: ${initUsr.email}")
NCUserManager.updateUserPermissions(usrId, req.admin, span)
complete {
Res$User$Admin(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $user$Passwd$Reset(): Route = {
case class Req$User$Password$Reset(
// Caller.
acsTok: String,
id: Option[Long],
newPasswd: String
)
case class Res$User$Password$Reset(
status: String
)
implicit val reqFmt: RootJsonFormat[Req$User$Password$Reset] = jsonFormat3(Req$User$Password$Reset)
implicit val resFmt: RootJsonFormat[Res$User$Password$Reset] = jsonFormat1(Res$User$Password$Reset)
entity(as[Req$User$Password$Reset]) { req =>
startScopedSpan(
"user$Password$Reset",
"acsTok" -> req.acsTok, "usrId" -> req.id.getOrElse(-1)) { span =>
checkLength("acsTok" -> req.acsTok, "newPasswd" -> req.newPasswd)
val acsUsr = authenticate(req.acsTok)
NCUserManager.resetPassword(getUserId(acsUsr, req.id, None), req.newPasswd, span)
complete {
Res$User$Password$Reset(API_OK)
}
}
}
}
/**
*
* @return
*/
protected def $user$All(): Route = {
case class Req$User$All(
// Caller.
acsTok: String
)
case class ResUser_User$All(
id: Long,
email: Option[String],
usrExtId: Option[String],
firstName: Option[String],
lastName: Option[String],
avatarUrl: Option[String],
isAdmin: Boolean,
companyId: Long,
properties: Option[JavaMeta]
)
case class Res$User$All(
status: String,
users: Seq[ResUser_User$All]
)
implicit val reqFmt: RootJsonFormat[Req$User$All] = jsonFormat1(Req$User$All)
entity(as[Req$User$All]) { req =>
startScopedSpan("user$All", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val admUSr = authenticateAsAdmin(req.acsTok)
completeJs {
Res$User$All(
API_OK,
NCUserManager.getAllUsers(admUSr.companyId, span).map(u =>
ResUser_User$All(
u.id,
u.email,
u.extId,
u.firstName,
u.lastName,
u.avatarUrl,
u.isAdmin,
u.companyId,
unzipProperties(u.propertiesGzip)
)
)
)
}
}
}
}
/**
*
* @return
*/
protected def $user$Get(): Route = {
case class Req$User$Get(
// Caller.
acsTok: String,
id: Option[Long],
usrExtId: Option[String]
)
case class Res$User$Get(
status: String,
id: Long,
email: Option[String],
usrExtId: Option[String],
firstName: Option[String],
lastName: Option[String],
avatarUrl: Option[String],
isAdmin: Boolean,
properties: Option[JavaMeta]
)
implicit val reqFmt: RootJsonFormat[Req$User$Get] = jsonFormat3(Req$User$Get)
entity(as[Req$User$Get]) { req =>
startScopedSpan(
"user$Get", "acsTok" -> req.acsTok, "id" -> req.id.orElse(null), "usrExtId" -> req.usrExtId.orNull
) { span =>
checkLength("acsTok" -> req.acsTok, "usrExtId" -> req.usrExtId)
val acsUsr = authenticate(req.acsTok)
val usrId = getUserId(acsUsr, req.id, req.usrExtId)
if (acsUsr.id != usrId && !acsUsr.isAdmin)
throw AdminRequired(acsUsr.email.get)
val usr = NCUserManager.getUserById(usrId, span).getOrElse(throw new NCE(s"User not found: $usrId"))
completeJs {
Res$User$Get(API_OK,
usr.id,
usr.email,
usr.extId,
usr.firstName,
usr.lastName,
usr.avatarUrl,
usr.isAdmin,
unzipProperties(usr.propertiesGzip)
)
}
}
}
}
/**
*
* @return
*/
protected def $probe$All(): Route = {
case class Req$Probe$All(
acsTok: String
)
case class Model_Probe$All(
id: String,
name: String,
version: String,
elementIds: Set[String],
enabledBuiltInTokens: Set[String]
)
case class Probe_Probe$All(
probeToken: String,
probeId: String,
probeGuid: String,
probeApiVersion: String,
probeApiDate: String,
osVersion: String,
osName: String,
osArch: String,
startTstamp: Long,
tmzId: String,
tmzAbbr: String,
tmzName: String,
userName: String,
javaVersion: String,
javaVendor: String,
hostName: String,
hostAddr: String,
macAddr: String,
models: Set[Model_Probe$All]
)
case class Res$Probe$All(
status: String,
probes: Seq[Probe_Probe$All]
)
implicit val reqFmt: RootJsonFormat[Req$Probe$All] = jsonFormat1(Req$Probe$All)
implicit val mdlFmt: RootJsonFormat[Model_Probe$All] = jsonFormat5(Model_Probe$All)
implicit val probFmt: RootJsonFormat[Probe_Probe$All] = jsonFormat19(Probe_Probe$All)
implicit val resFmt: RootJsonFormat[Res$Probe$All] = jsonFormat2(Res$Probe$All)
entity(as[Req$Probe$All]) { req =>
startScopedSpan("probe$All", "acsTok" -> req.acsTok) { span =>
checkLength("acsTok" -> req.acsTok)
val admUsr = authenticateAsAdmin(req.acsTok)
val probeLst = NCProbeManager.getAllProbes(admUsr.companyId, span).map(mdo => Probe_Probe$All(
mdo.probeToken,
mdo.probeId,
mdo.probeGuid,
mdo.probeApiVersion,
mdo.probeApiDate.toString,
mdo.osVersion,
mdo.osName,
mdo.osArch,
mdo.startTstamp.getTime,
mdo.tmzId,
mdo.tmzAbbr,
mdo.tmzName,
mdo.userName,
mdo.javaVersion,
mdo.javaVendor,
mdo.hostName,
mdo.hostAddr,
mdo.macAddr,
mdo.models.map(m => Model_Probe$All(
m.id,
m.name,
m.version,
m.elementIds,
m.enabledBuiltInTokens
))
))
complete {
Res$Probe$All(API_OK, probeLst)
}
}
}
}
/**
*
* @param statusCode
* @param errCode
* @param errMsg
*/
protected def completeError(statusCode: StatusCode, errCode: String, errMsg: String): Route = {
currentSpan().setStatus(Status.INTERNAL.withDescription(s"code: $errCode, message: $errMsg"))
corsHandler(
complete(
HttpResponse(
status = statusCode,
entity = HttpEntity(
ContentTypes.`application/json`,
toJs(Map("code" -> errCode, "msg" -> errMsg).asJava)
)
)
)
)
}
/**
*
* @return
*/
def getExceptionHandler: ExceptionHandler = ExceptionHandler {
case e: AccessTokenFailure =>
val errMsg = e.getLocalizedMessage
val code = "NC_INVALID_ACCESS_TOKEN"
completeError(StatusCodes.Unauthorized, code, errMsg)
case e: SignInFailure =>
val errMsg = e.getLocalizedMessage
val code = "NC_SIGNIN_FAILURE"
completeError(StatusCodes.Unauthorized, code, errMsg)
case e: NotImplemented =>
val errMsg = e.getLocalizedMessage
val code = "NC_NOT_IMPLEMENTED"
completeError(StatusCodes.NotImplemented, code, errMsg)
case e: InvalidArguments =>
val errMsg = e.getLocalizedMessage
val code = "NC_INVALID_FIELD"
completeError(StatusCodes.BadRequest, code, errMsg)
case e: AdminRequired =>
val errMsg = e.getLocalizedMessage
val code = "NC_ADMIN_REQUIRED"
completeError(StatusCodes.Forbidden, code, errMsg)
case e: InvalidOperation =>
val errMsg = e.getLocalizedMessage
val code = "NC_INVALID_OPERATION"
completeError(StatusCodes.Forbidden, code, errMsg)
// General exception.
case e: NCE =>
val errMsg = "Unexpected system error."
val code = "NC_ERROR"
// We have to log error reason because even general exceptions are not expected here.
U.prettyError(logger,s"Unexpected top level REST API error: $errMsg", e)
completeError(StatusCodes.BadRequest, code, errMsg)
// Unexpected errors.
case e: Throwable =>
val errMsg = "Unexpected system error."
val code = "NC_ERROR"
U.prettyError(logger,s"Unexpected system error: $errMsg", e)
completeError(StatusCodes.InternalServerError, code, errMsg)
}
/**
*
* @return
*/
def getRejectionHandler: RejectionHandler = {
def complete(err: String): Route = completeError(StatusCodes.BadRequest, "NC_ERROR", err)
RejectionHandler.newBuilder().
handle {
case _: UnsupportedRequestContentTypeRejection => complete("Unsupported content type.")
case _: SchemeRejection => complete("Unsupported scheme.")
case _: UnsatisfiableRangeRejection => complete("Invalid range.")
case _: TransformationRejection => complete("Transformation error.")
case _: MalformedRequestContentRejection => complete("Malformed or missing content.")
case _: AuthenticationFailedRejection => complete("Authentication error.")
case _: TooManyRangesRejection => complete("Too many ranges.")
case err: MissingFormFieldRejection => complete(s"Missing form field: ${err.fieldName}")
case _: UnacceptedResponseContentTypeRejection => complete(s"Unsupported response content type.")
case _: MethodRejection => complete(s"Invalid method.")
case _: UnacceptedResponseEncodingRejection => complete(s"Unsupported response encoding.")
case _: UnsupportedRequestEncodingRejection => complete(s"Unsupported request encoding.")
case err: MissingCookieRejection => complete(s"Missing cookie: ${err.cookieName}'")
case err: InvalidRequiredValueForQueryParamRejection => complete(s"Invalid parameter value: ${err.parameterName}")
case _: UnsupportedWebSocketSubprotocolRejection => complete(s"Unsupported web socket sub-protocol.")
case _: CircuitBreakerOpenRejection => complete(s"Circuit breaker error.")
case _: MissingHeaderRejection => complete(s"Missing header.")
case err: MissingQueryParamRejection => complete(s"Missing parameter: ${err.parameterName}")
case _: InvalidOriginRejection => complete(s"Invalid origin.")
// Common case.
case _: Rejection => complete(s"Bad request.")
}.result()
}
/**
*
* @param m
* @param f
* @return
*/
private def withMetric(m: Measure, f: () => Route): Route = {
val start = U.now()
try
f()
finally {
recordStats(m -> (U.now() - start))
}
}
/**
* @return
*/
private implicit def getContext: ExecutionContext = NCThreadPoolManager.getContext("probes.communication")
/**
*
* @return
*/
override def getRoute: Route = {
val timeoutResp = HttpResponse(
StatusCodes.EnhanceYourCalm,
entity = "Unable to serve response within time limit."
)
handleExceptions(getExceptionHandler) {
handleRejections(getRejectionHandler) {
corsHandler (
get {
withRequestTimeoutResponse(_ => timeoutResp) {
path(API / "health") { $health() } // Also duplicated for POST.
}
} ~
post {
encodeResponseWith(Coders.NoCoding, Coders.Gzip) {
withRequestTimeoutResponse(_ => timeoutResp) {
path(API / "health") { $health() } // Duplicate for POST.
path(API / "signin") { withMetric(M_SIGNIN_LATENCY_MS, $signin) } ~
path(API / "signout") { withMetric(M_SIGNOUT_LATENCY_MS, $signout) } ~
path(API / "cancel") { withMetric(M_CANCEL_LATENCY_MS, $cancel) } ~
path(API / "check") { withMetric(M_CHECK_LATENCY_MS, $check) } ~
path(API / "clear"/ "conversation") { withMetric(M_CLEAR_CONV_LATENCY_MS, $clear$Conversation) } ~
path(API / "clear"/ "dialog") { withMetric(M_CLEAR_DIALOG_LATENCY_MS, $clear$Dialog) } ~
path(API / "company"/ "add") { withMetric(M_COMPANY_ADD_LATENCY_MS, $company$Add) } ~
path(API / "company"/ "get") { withMetric(M_COMPANY_GET_LATENCY_MS, $company$Get) } ~
path(API / "company" / "update") { withMetric(M_COMPANY_UPDATE_LATENCY_MS, $company$Update) } ~
path(API / "company" / "token" / "reset") { withMetric(M_COMPANY_TOKEN_LATENCY_MS, $company$Token$Reset) } ~
path(API / "company" / "delete") { withMetric(M_COMPANY_DELETE_LATENCY_MS, $company$Delete) } ~
path(API / "user" / "get") { withMetric(M_USER_GET_LATENCY_MS, $user$Get) } ~
path(API / "user" / "add") { withMetric(M_USER_ADD_LATENCY_MS, $user$Add) } ~
path(API / "user" / "update") { withMetric(M_USER_UPDATE_LATENCY_MS, $user$Update) } ~
path(API / "user" / "delete") { withMetric(M_USER_DELETE_LATENCY_MS, $user$Delete) } ~
path(API / "user" / "admin") { withMetric(M_USER_ADMIN_LATENCY_MS, $user$Admin) } ~
path(API / "user" / "passwd" / "reset") { withMetric(M_USER_PASSWD_RESET_LATENCY_MS, $user$Passwd$Reset) } ~
path(API / "user" / "all") { withMetric(M_USER_ALL_LATENCY_MS, $user$All) } ~
path(API / "feedback"/ "add") { withMetric(M_FEEDBACK_ADD_LATENCY_MS, $feedback$Add) } ~
path(API / "feedback"/ "all") { withMetric(M_FEEDBACK_GET_LATENCY_MS, $feedback$All) } ~
path(API / "feedback" / "delete") { withMetric(M_FEEDBACK_DELETE_LATENCY_MS, $feedback$Delete) } ~
path(API / "probe" / "all") { withMetric(M_PROBE_ALL_LATENCY_MS, $probe$All) } ~
path(API / "model" / "sugsyn") { withMetric(M_MODEL_SUGSYN_LATENCY_MS, $model$sugsyn) } ~
path(API / "model" / "syns") { withMetric(M_MODEL_SYNS_LATENCY_MS, $model$syns) } ~
path(API / "model" / "info") { withMetric(M_MODEL_SYNS_LATENCY_MS, $model$info) } ~
path(API / "ask") { withMetric(M_ASK_LATENCY_MS, $ask) } ~
path(API / "ask" / "sync") { withMetric(M_ASK_SYNC_LATENCY_MS, $ask$Sync) }
}
}
}
)
}
}
}
}