mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-06-21 18:08:25 +00:00
Apply scalafmt to all files
This commit is contained in:
@ -7,27 +7,37 @@ import docspell.backend.{Config => BackendConfig}
|
||||
import docspell.common._
|
||||
import scodec.bits.ByteVector
|
||||
|
||||
case class Config(appName: String
|
||||
, appId: Ident
|
||||
, baseUrl: LenientUri
|
||||
, bind: Config.Bind
|
||||
, backend: BackendConfig
|
||||
, auth: Login.Config
|
||||
case class Config(
|
||||
appName: String,
|
||||
appId: Ident,
|
||||
baseUrl: LenientUri,
|
||||
bind: Config.Bind,
|
||||
backend: BackendConfig,
|
||||
auth: Login.Config
|
||||
)
|
||||
|
||||
object Config {
|
||||
val postgres = JdbcConfig(LenientUri.unsafe("jdbc:postgresql://localhost:5432/docspelldev"), "dev", "dev")
|
||||
val h2 = JdbcConfig(LenientUri.unsafe("jdbc:h2:./target/docspelldev.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"), "sa", "")
|
||||
val postgres =
|
||||
JdbcConfig(LenientUri.unsafe("jdbc:postgresql://localhost:5432/docspelldev"), "dev", "dev")
|
||||
val h2 = JdbcConfig(
|
||||
LenientUri.unsafe("jdbc:h2:./target/docspelldev.db;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"),
|
||||
"sa",
|
||||
""
|
||||
)
|
||||
|
||||
val default: Config =
|
||||
Config("Docspell"
|
||||
, Ident.unsafe("restserver1")
|
||||
, LenientUri.unsafe("http://localhost:7880")
|
||||
, Config.Bind("localhost", 7880)
|
||||
, BackendConfig(postgres
|
||||
, SignupConfig(SignupConfig.invite, Password("testpass"), Duration.hours(5 * 24))
|
||||
, BackendConfig.Files(512 * 1024, List(MimeType.pdf)))
|
||||
, Login.Config(ByteVector.fromValidHex("caffee"), Duration.minutes(2)))
|
||||
Config(
|
||||
"Docspell",
|
||||
Ident.unsafe("restserver1"),
|
||||
LenientUri.unsafe("http://localhost:7880"),
|
||||
Config.Bind("localhost", 7880),
|
||||
BackendConfig(
|
||||
postgres,
|
||||
SignupConfig(SignupConfig.invite, Password("testpass"), Duration.hours(5 * 24)),
|
||||
BackendConfig.Files(512 * 1024, List(MimeType.pdf))
|
||||
),
|
||||
Login.Config(ByteVector.fromValidHex("caffee"), Duration.minutes(2))
|
||||
)
|
||||
|
||||
case class Bind(address: String, port: Int)
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ object ConfigFile {
|
||||
ConfigSource.default.at("docspell.server").loadOrThrow[Config]
|
||||
|
||||
object Implicits {
|
||||
implicit val signupModeReader: ConfigReader[SignupConfig.Mode] =
|
||||
implicit val signupModeReader: ConfigReader[SignupConfig.Mode] =
|
||||
ConfigReader[String].emap(reason(SignupConfig.Mode.fromString))
|
||||
}
|
||||
}
|
||||
|
@ -13,12 +13,14 @@ import org.log4s._
|
||||
object Main extends IOApp {
|
||||
private[this] val logger = getLogger
|
||||
|
||||
val blockingEc: ExecutionContext = ExecutionContext.fromExecutor(Executors.newCachedThreadPool(
|
||||
ThreadFactories.ofName("docspell-restserver-blocking")))
|
||||
val blockingEc: ExecutionContext = ExecutionContext.fromExecutor(
|
||||
Executors.newCachedThreadPool(ThreadFactories.ofName("docspell-restserver-blocking"))
|
||||
)
|
||||
val blocker = Blocker.liftExecutionContext(blockingEc)
|
||||
|
||||
val connectEC: ExecutionContext = ExecutionContext.fromExecutorService(Executors.newFixedThreadPool(5,
|
||||
ThreadFactories.ofName("docspell-dbconnect")))
|
||||
val connectEC: ExecutionContext = ExecutionContext.fromExecutorService(
|
||||
Executors.newFixedThreadPool(5, ThreadFactories.ofName("docspell-dbconnect"))
|
||||
)
|
||||
|
||||
def run(args: List[String]) = {
|
||||
args match {
|
||||
@ -41,12 +43,15 @@ object Main extends IOApp {
|
||||
}
|
||||
|
||||
val cfg = ConfigFile.loadConfig
|
||||
val banner = Banner("REST Server"
|
||||
, BuildInfo.version
|
||||
, BuildInfo.gitHeadCommit
|
||||
, cfg.backend.jdbc.url
|
||||
, Option(System.getProperty("config.file"))
|
||||
, cfg.appId, cfg.baseUrl)
|
||||
val banner = Banner(
|
||||
"REST Server",
|
||||
BuildInfo.version,
|
||||
BuildInfo.gitHeadCommit,
|
||||
cfg.backend.jdbc.url,
|
||||
Option(System.getProperty("config.file")),
|
||||
cfg.appId,
|
||||
cfg.baseUrl
|
||||
)
|
||||
logger.info(s"\n${banner.render("***>")}")
|
||||
RestServer.stream[IO](cfg, connectEC, blockingEc, blocker).compile.drain.as(ExitCode.Success)
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ import docspell.common.NodeType
|
||||
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
final class RestAppImpl[F[_]: Sync](val config: Config, val backend: BackendApp[F]) extends RestApp[F] {
|
||||
final class RestAppImpl[F[_]: Sync](val config: Config, val backend: BackendApp[F])
|
||||
extends RestApp[F] {
|
||||
|
||||
def init: F[Unit] =
|
||||
backend.node.register(config.appId, NodeType.Restserver, config.baseUrl)
|
||||
@ -18,11 +19,16 @@ final class RestAppImpl[F[_]: Sync](val config: Config, val backend: BackendApp[
|
||||
|
||||
object RestAppImpl {
|
||||
|
||||
def create[F[_]: ConcurrentEffect: ContextShift](cfg: Config, connectEC: ExecutionContext, httpClientEc: ExecutionContext, blocker: Blocker): Resource[F, RestApp[F]] =
|
||||
def create[F[_]: ConcurrentEffect: ContextShift](
|
||||
cfg: Config,
|
||||
connectEC: ExecutionContext,
|
||||
httpClientEc: ExecutionContext,
|
||||
blocker: Blocker
|
||||
): Resource[F, RestApp[F]] =
|
||||
for {
|
||||
backend <- BackendApp(cfg.backend, connectEC, httpClientEc, blocker)
|
||||
app = new RestAppImpl[F](cfg, backend)
|
||||
appR <- Resource.make(app.init.map(_ => app))(_.shutdown)
|
||||
backend <- BackendApp(cfg.backend, connectEC, httpClientEc, blocker)
|
||||
app = new RestAppImpl[F](cfg, backend)
|
||||
appR <- Resource.make(app.init.map(_ => app))(_.shutdown)
|
||||
} yield appR
|
||||
|
||||
}
|
||||
|
@ -15,54 +15,64 @@ import scala.concurrent.ExecutionContext
|
||||
|
||||
object RestServer {
|
||||
|
||||
def stream[F[_]: ConcurrentEffect](cfg: Config, connectEC: ExecutionContext, httpClientEc: ExecutionContext, blocker: Blocker)
|
||||
(implicit T: Timer[F], CS: ContextShift[F]): Stream[F, Nothing] = {
|
||||
def stream[F[_]: ConcurrentEffect](
|
||||
cfg: Config,
|
||||
connectEC: ExecutionContext,
|
||||
httpClientEc: ExecutionContext,
|
||||
blocker: Blocker
|
||||
)(implicit T: Timer[F], CS: ContextShift[F]): Stream[F, Nothing] = {
|
||||
|
||||
val app = for {
|
||||
restApp <- RestAppImpl.create[F](cfg, connectEC, httpClientEc, blocker)
|
||||
restApp <- RestAppImpl.create[F](cfg, connectEC, httpClientEc, blocker)
|
||||
|
||||
httpApp = Router(
|
||||
"/api/info" -> routes.InfoRoutes(),
|
||||
"/api/info" -> routes.InfoRoutes(),
|
||||
"/api/v1/open/" -> openRoutes(cfg, restApp),
|
||||
"/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) {
|
||||
token => securedRoutes(cfg, restApp, token)
|
||||
"/api/v1/sec/" -> Authenticate(restApp.backend.login, cfg.auth) { token =>
|
||||
securedRoutes(cfg, restApp, token)
|
||||
},
|
||||
"/app/assets" -> WebjarRoutes.appRoutes[F](blocker),
|
||||
"/app" -> TemplateRoutes[F](blocker, cfg)
|
||||
"/app" -> TemplateRoutes[F](blocker, cfg)
|
||||
).orNotFound
|
||||
|
||||
finalHttpApp = Logger.httpApp(logHeaders = false, logBody = false)(httpApp)
|
||||
|
||||
} yield finalHttpApp
|
||||
|
||||
Stream.resource(app).flatMap(httpApp =>
|
||||
BlazeServerBuilder[F].
|
||||
bindHttp(cfg.bind.port, cfg.bind.address).
|
||||
withHttpApp(httpApp).
|
||||
withoutBanner.
|
||||
serve)
|
||||
Stream
|
||||
.resource(app)
|
||||
.flatMap(httpApp =>
|
||||
BlazeServerBuilder[F]
|
||||
.bindHttp(cfg.bind.port, cfg.bind.address)
|
||||
.withHttpApp(httpApp)
|
||||
.withoutBanner
|
||||
.serve
|
||||
)
|
||||
}.drain
|
||||
|
||||
|
||||
def securedRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F], token: AuthToken): HttpRoutes[F] =
|
||||
def securedRoutes[F[_]: Effect](
|
||||
cfg: Config,
|
||||
restApp: RestApp[F],
|
||||
token: AuthToken
|
||||
): HttpRoutes[F] =
|
||||
Router(
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg),
|
||||
"tag" -> TagRoutes(restApp.backend, token),
|
||||
"equipment" -> EquipmentRoutes(restApp.backend, token),
|
||||
"auth" -> LoginRoutes.session(restApp.backend.login, cfg),
|
||||
"tag" -> TagRoutes(restApp.backend, token),
|
||||
"equipment" -> EquipmentRoutes(restApp.backend, token),
|
||||
"organization" -> OrganizationRoutes(restApp.backend, token),
|
||||
"person" -> PersonRoutes(restApp.backend, token),
|
||||
"source" -> SourceRoutes(restApp.backend, token),
|
||||
"user" -> UserRoutes(restApp.backend, token),
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
"item" -> ItemRoutes(restApp.backend, token),
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token)
|
||||
"person" -> PersonRoutes(restApp.backend, token),
|
||||
"source" -> SourceRoutes(restApp.backend, token),
|
||||
"user" -> UserRoutes(restApp.backend, token),
|
||||
"collective" -> CollectiveRoutes(restApp.backend, token),
|
||||
"queue" -> JobQueueRoutes(restApp.backend, token),
|
||||
"item" -> ItemRoutes(restApp.backend, token),
|
||||
"attachment" -> AttachmentRoutes(restApp.backend, token),
|
||||
"upload" -> UploadRoutes.secured(restApp.backend, cfg, token)
|
||||
)
|
||||
|
||||
def openRoutes[F[_]: Effect](cfg: Config, restApp: RestApp[F]): HttpRoutes[F] =
|
||||
Router(
|
||||
"auth" -> LoginRoutes.login(restApp.backend.login, cfg),
|
||||
"auth" -> LoginRoutes.login(restApp.backend.login, cfg),
|
||||
"signup" -> RegisterRoutes(restApp.backend, cfg),
|
||||
"upload" -> UploadRoutes.open(restApp.backend, cfg)
|
||||
)
|
||||
|
@ -8,13 +8,20 @@ import docspell.restserver.Config
|
||||
|
||||
case class CookieData(auth: AuthToken) {
|
||||
def accountId: AccountId = auth.account
|
||||
def asString: String = auth.asString
|
||||
def asString: String = auth.asString
|
||||
|
||||
def asCookie(cfg: Config): ResponseCookie = {
|
||||
val domain = cfg.baseUrl.host
|
||||
val sec = cfg.baseUrl.scheme.exists(_.endsWith("s"))
|
||||
val path = cfg.baseUrl.path/"api"/"v1"/"sec"
|
||||
ResponseCookie(CookieData.cookieName, asString, domain = domain, path = Some(path.asString), httpOnly = true, secure = sec)
|
||||
val sec = cfg.baseUrl.scheme.exists(_.endsWith("s"))
|
||||
val path = cfg.baseUrl.path / "api" / "v1" / "sec"
|
||||
ResponseCookie(
|
||||
CookieData.cookieName,
|
||||
asString,
|
||||
domain = domain,
|
||||
path = Some(path.asString),
|
||||
httpOnly = true,
|
||||
secure = sec
|
||||
)
|
||||
}
|
||||
}
|
||||
object CookieData {
|
||||
@ -22,18 +29,21 @@ object CookieData {
|
||||
val headerName = "X-Docspell-Auth"
|
||||
|
||||
def authenticator[F[_]](r: Request[F]): Either[String, String] =
|
||||
fromCookie(r) orElse fromHeader(r)
|
||||
fromCookie(r).orElse(fromHeader(r))
|
||||
|
||||
def fromCookie[F[_]](req: Request[F]): Either[String, String] = {
|
||||
def fromCookie[F[_]](req: Request[F]): Either[String, String] =
|
||||
for {
|
||||
header <- headers.Cookie.from(req.headers).toRight("Cookie parsing error")
|
||||
cookie <- header.values.toList.find(_.name == cookieName).toRight("Couldn't find the authcookie")
|
||||
header <- headers.Cookie.from(req.headers).toRight("Cookie parsing error")
|
||||
cookie <- header.values.toList
|
||||
.find(_.name == cookieName)
|
||||
.toRight("Couldn't find the authcookie")
|
||||
} yield cookie.content
|
||||
}
|
||||
|
||||
def fromHeader[F[_]](req: Request[F]): Either[String, String] = {
|
||||
req.headers.get(CaseInsensitiveString(headerName)).map(_.value).toRight("Couldn't find an authenticator")
|
||||
}
|
||||
def fromHeader[F[_]](req: Request[F]): Either[String, String] =
|
||||
req.headers
|
||||
.get(CaseInsensitiveString(headerName))
|
||||
.map(_.value)
|
||||
.toRight("Couldn't find an authenticator")
|
||||
|
||||
def deleteCookie(cfg: Config): ResponseCookie =
|
||||
ResponseCookie(
|
||||
|
@ -24,31 +24,37 @@ trait Conversions {
|
||||
|
||||
// insights
|
||||
def mkItemInsights(d: InsightData): ItemInsights =
|
||||
ItemInsights(d.incoming, d.outgoing, d.bytes, TagCloud(d.tags.toList.map(p => NameCount(p._1, p._2))))
|
||||
ItemInsights(
|
||||
d.incoming,
|
||||
d.outgoing,
|
||||
d.bytes,
|
||||
TagCloud(d.tags.toList.map(p => NameCount(p._1, p._2)))
|
||||
)
|
||||
|
||||
// attachment meta
|
||||
def mkAttachmentMeta(rm: RAttachmentMeta): AttachmentMeta =
|
||||
AttachmentMeta(rm.content.getOrElse("")
|
||||
, rm.nerlabels.map(nl => Label(nl.tag, nl.label, nl.startPosition, nl.endPosition))
|
||||
, mkItemProposals(rm.proposals))
|
||||
|
||||
AttachmentMeta(
|
||||
rm.content.getOrElse(""),
|
||||
rm.nerlabels.map(nl => Label(nl.tag, nl.label, nl.startPosition, nl.endPosition)),
|
||||
mkItemProposals(rm.proposals)
|
||||
)
|
||||
|
||||
// item proposal
|
||||
def mkItemProposals(ml: MetaProposalList): ItemProposals = {
|
||||
def get(mpt: MetaProposalType) =
|
||||
ml.find(mpt).
|
||||
map(mp => mp.values.toList.map(_.ref).map(mkIdName)).
|
||||
getOrElse(Nil)
|
||||
ml.find(mpt).map(mp => mp.values.toList.map(_.ref).map(mkIdName)).getOrElse(Nil)
|
||||
def getDates(mpt: MetaProposalType): List[Timestamp] =
|
||||
ml.find(mpt).
|
||||
map(mp => mp.values.toList.
|
||||
map(cand => cand.ref.id.id).
|
||||
flatMap(str => Either.catchNonFatal(LocalDate.parse(str)).toOption).
|
||||
map(_.atTime(12, 0).atZone(ZoneId.of("GMT"))).
|
||||
map(zdt => Timestamp(zdt.toInstant))).
|
||||
getOrElse(Nil).
|
||||
distinct.
|
||||
take(5)
|
||||
ml.find(mpt)
|
||||
.map(mp =>
|
||||
mp.values.toList
|
||||
.map(cand => cand.ref.id.id)
|
||||
.flatMap(str => Either.catchNonFatal(LocalDate.parse(str)).toOption)
|
||||
.map(_.atTime(12, 0).atZone(ZoneId.of("GMT")))
|
||||
.map(zdt => Timestamp(zdt.toInstant))
|
||||
)
|
||||
.getOrElse(Nil)
|
||||
.distinct
|
||||
.take(5)
|
||||
|
||||
ItemProposals(
|
||||
corrOrg = get(MetaProposalType.CorrOrg),
|
||||
@ -62,23 +68,25 @@ trait Conversions {
|
||||
|
||||
// item detail
|
||||
def mkItemDetail(data: OItem.ItemData): ItemDetail =
|
||||
ItemDetail(data.item.id
|
||||
, data.item.direction
|
||||
, data.item.name
|
||||
, data.item.source
|
||||
, data.item.state
|
||||
, data.item.created
|
||||
, data.item.updated
|
||||
, data.item.itemDate
|
||||
, data.corrOrg.map(o => IdName(o.oid, o.name))
|
||||
, data.corrPerson.map(p => IdName(p.pid, p.name))
|
||||
, data.concPerson.map(p => IdName(p.pid, p.name))
|
||||
, data.concEquip.map(e => IdName(e.eid, e.name))
|
||||
, data.inReplyTo.map(mkIdName)
|
||||
, data.item.dueDate
|
||||
, data.item.notes
|
||||
, data.attachments.map((mkAttachment _).tupled).toList
|
||||
, data.tags.map(mkTag).toList)
|
||||
ItemDetail(
|
||||
data.item.id,
|
||||
data.item.direction,
|
||||
data.item.name,
|
||||
data.item.source,
|
||||
data.item.state,
|
||||
data.item.created,
|
||||
data.item.updated,
|
||||
data.item.itemDate,
|
||||
data.corrOrg.map(o => IdName(o.oid, o.name)),
|
||||
data.corrPerson.map(p => IdName(p.pid, p.name)),
|
||||
data.concPerson.map(p => IdName(p.pid, p.name)),
|
||||
data.concEquip.map(e => IdName(e.eid, e.name)),
|
||||
data.inReplyTo.map(mkIdName),
|
||||
data.item.dueDate,
|
||||
data.item.notes,
|
||||
data.attachments.map((mkAttachment _).tupled).toList,
|
||||
data.tags.map(mkTag).toList
|
||||
)
|
||||
|
||||
def mkAttachment(ra: RAttachment, m: FileMeta): Attachment =
|
||||
Attachment(ra.id, ra.name, m.length, MimeType.unsafe(m.mimetype.asString))
|
||||
@ -86,20 +94,21 @@ trait Conversions {
|
||||
// item list
|
||||
|
||||
def mkQuery(m: ItemSearch, coll: Ident): OItem.Query =
|
||||
OItem.Query(coll
|
||||
, m.name
|
||||
, if (m.inbox) Seq(ItemState.Created) else Seq(ItemState.Created, ItemState.Confirmed)
|
||||
, m.direction
|
||||
, m.corrPerson
|
||||
, m.corrOrg
|
||||
, m.concPerson
|
||||
, m.concEquip
|
||||
, m.tagsInclude.map(Ident.unsafe)
|
||||
, m.tagsExclude.map(Ident.unsafe)
|
||||
, m.dateFrom
|
||||
, m.dateUntil
|
||||
, m.dueDateFrom
|
||||
, m.dueDateUntil
|
||||
OItem.Query(
|
||||
coll,
|
||||
m.name,
|
||||
if (m.inbox) Seq(ItemState.Created) else Seq(ItemState.Created, ItemState.Confirmed),
|
||||
m.direction,
|
||||
m.corrPerson,
|
||||
m.corrOrg,
|
||||
m.concPerson,
|
||||
m.concEquip,
|
||||
m.tagsInclude.map(Ident.unsafe),
|
||||
m.tagsExclude.map(Ident.unsafe),
|
||||
m.dateFrom,
|
||||
m.dateUntil,
|
||||
m.dueDateFrom,
|
||||
m.dueDateUntil
|
||||
)
|
||||
|
||||
def mkItemList(v: Vector[OItem.ListItem]): ItemLightList = {
|
||||
@ -113,8 +122,20 @@ trait Conversions {
|
||||
}
|
||||
|
||||
def mkItemLight(i: OItem.ListItem): ItemLight =
|
||||
ItemLight(i.id, i.name, i.state, i.date, i.dueDate, i.source, i.direction.name.some, i.corrOrg.map(mkIdName),
|
||||
i.corrPerson.map(mkIdName), i.concPerson.map(mkIdName), i.concEquip.map(mkIdName), i.fileCount)
|
||||
ItemLight(
|
||||
i.id,
|
||||
i.name,
|
||||
i.state,
|
||||
i.date,
|
||||
i.dueDate,
|
||||
i.source,
|
||||
i.direction.name.some,
|
||||
i.corrOrg.map(mkIdName),
|
||||
i.corrPerson.map(mkIdName),
|
||||
i.concPerson.map(mkIdName),
|
||||
i.concEquip.map(mkIdName),
|
||||
i.fileCount
|
||||
)
|
||||
|
||||
// job
|
||||
def mkJobQueueState(state: OJob.CollectiveQueueState): JobQueueState = {
|
||||
@ -128,46 +149,57 @@ trait Conversions {
|
||||
val t2 = f(j2).getOrElse(Timestamp.Epoch)
|
||||
t1.value.isBefore(t2.value)
|
||||
}
|
||||
JobQueueState(state.running.map(mkJobDetail).toList.sortWith(asc(_.started))
|
||||
, state.done.map(mkJobDetail).toList.sortWith(desc(_.finished))
|
||||
, state.queued.map(mkJobDetail).toList.sortWith(asc(_.submitted.some)))
|
||||
JobQueueState(
|
||||
state.running.map(mkJobDetail).toList.sortWith(asc(_.started)),
|
||||
state.done.map(mkJobDetail).toList.sortWith(desc(_.finished)),
|
||||
state.queued.map(mkJobDetail).toList.sortWith(asc(_.submitted.some))
|
||||
)
|
||||
}
|
||||
|
||||
def mkJobDetail(jd: OJob.JobDetail): JobDetail =
|
||||
JobDetail(jd.job.id
|
||||
, jd.job.subject
|
||||
, jd.job.submitted
|
||||
, jd.job.priority
|
||||
, jd.job.state
|
||||
, jd.job.retries
|
||||
, jd.logs.map(mkJobLog).toList
|
||||
, jd.job.progress
|
||||
, jd.job.worker
|
||||
, jd.job.started
|
||||
, jd.job.finished)
|
||||
JobDetail(
|
||||
jd.job.id,
|
||||
jd.job.subject,
|
||||
jd.job.submitted,
|
||||
jd.job.priority,
|
||||
jd.job.state,
|
||||
jd.job.retries,
|
||||
jd.logs.map(mkJobLog).toList,
|
||||
jd.job.progress,
|
||||
jd.job.worker,
|
||||
jd.job.started,
|
||||
jd.job.finished
|
||||
)
|
||||
|
||||
def mkJobLog(jl: RJobLog): JobLogEvent =
|
||||
JobLogEvent(jl.created, jl.level, jl.message)
|
||||
|
||||
// upload
|
||||
def readMultipart[F[_]: Effect](mp: Multipart[F], logger: Logger, prio: Priority, validFileTypes: Seq[MimeType]): F[UploadData[F]] = {
|
||||
def parseMeta(body: Stream[F, Byte]): F[ItemUploadMeta] = {
|
||||
body.through(fs2.text.utf8Decode).
|
||||
parseJsonAs[ItemUploadMeta].
|
||||
map(_.fold(ex => {
|
||||
def readMultipart[F[_]: Effect](
|
||||
mp: Multipart[F],
|
||||
logger: Logger,
|
||||
prio: Priority,
|
||||
validFileTypes: Seq[MimeType]
|
||||
): F[UploadData[F]] = {
|
||||
def parseMeta(body: Stream[F, Byte]): F[ItemUploadMeta] =
|
||||
body
|
||||
.through(fs2.text.utf8Decode)
|
||||
.parseJsonAs[ItemUploadMeta]
|
||||
.map(_.fold(ex => {
|
||||
logger.error(ex)("Reading upload metadata failed.")
|
||||
throw ex
|
||||
}, identity))
|
||||
}
|
||||
|
||||
val meta: F[(Boolean, UploadMeta)] = mp.parts.find(_.name.exists(_ equalsIgnoreCase "meta")).
|
||||
map(p => parseMeta(p.body)).
|
||||
map(fm => fm.map(m => (m.multiple, UploadMeta(m.direction, "webapp", validFileTypes)))).
|
||||
getOrElse((true, UploadMeta(None, "webapp", validFileTypes)).pure[F])
|
||||
val meta: F[(Boolean, UploadMeta)] = mp.parts
|
||||
.find(_.name.exists(_.equalsIgnoreCase("meta")))
|
||||
.map(p => parseMeta(p.body))
|
||||
.map(fm => fm.map(m => (m.multiple, UploadMeta(m.direction, "webapp", validFileTypes))))
|
||||
.getOrElse((true, UploadMeta(None, "webapp", validFileTypes)).pure[F])
|
||||
|
||||
val files = mp.parts.
|
||||
filter(p => p.name.forall(s => !s.equalsIgnoreCase("meta"))).
|
||||
map(p => OUpload.File(p.filename, p.headers.get(`Content-Type`).map(fromContentType), p.body))
|
||||
val files = mp.parts
|
||||
.filter(p => p.name.forall(s => !s.equalsIgnoreCase("meta")))
|
||||
.map(p => OUpload.File(p.filename, p.headers.get(`Content-Type`).map(fromContentType), p.body)
|
||||
)
|
||||
for {
|
||||
metaData <- meta
|
||||
_ <- Effect[F].delay(logger.debug(s"Parsed upload meta data: $metaData"))
|
||||
@ -178,8 +210,14 @@ trait Conversions {
|
||||
// organization and person
|
||||
def mkOrg(v: OOrganization.OrgAndContacts): Organization = {
|
||||
val ro = v.org
|
||||
Organization(ro.oid, ro.name, Address(ro.street, ro.zip, ro.city, ro.country),
|
||||
v.contacts.map(mkContact).toList, ro.notes, ro.created)
|
||||
Organization(
|
||||
ro.oid,
|
||||
ro.name,
|
||||
Address(ro.street, ro.zip, ro.city, ro.country),
|
||||
v.contacts.map(mkContact).toList,
|
||||
ro.notes,
|
||||
ro.created
|
||||
)
|
||||
}
|
||||
|
||||
def newOrg[F[_]: Sync](v: Organization, cid: Ident): F[OOrganization.OrgAndContacts] = {
|
||||
@ -189,7 +227,17 @@ trait Conversions {
|
||||
now <- Timestamp.current[F]
|
||||
oid <- Ident.randomId[F]
|
||||
cont <- contacts(oid)
|
||||
org = ROrganization(oid, cid, v.name, v.address.street, v.address.zip, v.address.city, v.address.country, v.notes, now)
|
||||
org = ROrganization(
|
||||
oid,
|
||||
cid,
|
||||
v.name,
|
||||
v.address.street,
|
||||
v.address.zip,
|
||||
v.address.city,
|
||||
v.address.country,
|
||||
v.notes,
|
||||
now
|
||||
)
|
||||
} yield OOrganization.OrgAndContacts(org, cont)
|
||||
}
|
||||
|
||||
@ -197,15 +245,32 @@ trait Conversions {
|
||||
def contacts(oid: Ident) =
|
||||
v.contacts.traverse(c => newContact(c, oid.some, None))
|
||||
for {
|
||||
cont <- contacts(v.id)
|
||||
org = ROrganization(v.id, cid, v.name, v.address.street, v.address.zip, v.address.city, v.address.country, v.notes, v.created)
|
||||
cont <- contacts(v.id)
|
||||
org = ROrganization(
|
||||
v.id,
|
||||
cid,
|
||||
v.name,
|
||||
v.address.street,
|
||||
v.address.zip,
|
||||
v.address.city,
|
||||
v.address.country,
|
||||
v.notes,
|
||||
v.created
|
||||
)
|
||||
} yield OOrganization.OrgAndContacts(org, cont)
|
||||
}
|
||||
|
||||
def mkPerson(v: OOrganization.PersonAndContacts): Person = {
|
||||
val ro = v.person
|
||||
Person(ro.pid, ro.name, Address(ro.street, ro.zip, ro.city, ro.country),
|
||||
v.contacts.map(mkContact).toList, ro.notes, ro.concerning, ro.created)
|
||||
Person(
|
||||
ro.pid,
|
||||
ro.name,
|
||||
Address(ro.street, ro.zip, ro.city, ro.country),
|
||||
v.contacts.map(mkContact).toList,
|
||||
ro.notes,
|
||||
ro.concerning,
|
||||
ro.created
|
||||
)
|
||||
}
|
||||
|
||||
def newPerson[F[_]: Sync](v: Person, cid: Ident): F[OOrganization.PersonAndContacts] = {
|
||||
@ -215,7 +280,18 @@ trait Conversions {
|
||||
now <- Timestamp.current[F]
|
||||
pid <- Ident.randomId[F]
|
||||
cont <- contacts(pid)
|
||||
org = RPerson(pid, cid, v.name, v.address.street, v.address.zip, v.address.city, v.address.country, v.notes, v.concerning, now)
|
||||
org = RPerson(
|
||||
pid,
|
||||
cid,
|
||||
v.name,
|
||||
v.address.street,
|
||||
v.address.zip,
|
||||
v.address.city,
|
||||
v.address.country,
|
||||
v.notes,
|
||||
v.concerning,
|
||||
now
|
||||
)
|
||||
} yield OOrganization.PersonAndContacts(org, cont)
|
||||
}
|
||||
|
||||
@ -223,8 +299,19 @@ trait Conversions {
|
||||
def contacts(pid: Ident) =
|
||||
v.contacts.traverse(c => newContact(c, None, pid.some))
|
||||
for {
|
||||
cont <- contacts(v.id)
|
||||
org = RPerson(v.id, cid, v.name, v.address.street, v.address.zip, v.address.city, v.address.country, v.notes, v.concerning, v.created)
|
||||
cont <- contacts(v.id)
|
||||
org = RPerson(
|
||||
v.id,
|
||||
cid,
|
||||
v.name,
|
||||
v.address.street,
|
||||
v.address.zip,
|
||||
v.address.city,
|
||||
v.address.country,
|
||||
v.notes,
|
||||
v.concerning,
|
||||
v.created
|
||||
)
|
||||
} yield OOrganization.PersonAndContacts(org, cont)
|
||||
}
|
||||
|
||||
@ -233,7 +320,8 @@ trait Conversions {
|
||||
Contact(rc.contactId, rc.value, rc.kind)
|
||||
|
||||
def newContact[F[_]: Sync](c: Contact, oid: Option[Ident], pid: Option[Ident]): F[RContact] =
|
||||
timeId.map { case (id, now) =>
|
||||
timeId.map {
|
||||
case (id, now) =>
|
||||
RContact(id, c.value, c.kind, pid, oid, now)
|
||||
}
|
||||
|
||||
@ -242,12 +330,33 @@ trait Conversions {
|
||||
User(ru.login, ru.state, None, ru.email, ru.lastLogin, ru.loginCount, ru.created)
|
||||
|
||||
def newUser[F[_]: Sync](u: User, cid: Ident): F[RUser] =
|
||||
timeId.map { case (id, now) =>
|
||||
RUser(id, u.login, cid, u.password.getOrElse(Password.empty), u.state, u.email, 0, None, now)
|
||||
timeId.map {
|
||||
case (id, now) =>
|
||||
RUser(
|
||||
id,
|
||||
u.login,
|
||||
cid,
|
||||
u.password.getOrElse(Password.empty),
|
||||
u.state,
|
||||
u.email,
|
||||
0,
|
||||
None,
|
||||
now
|
||||
)
|
||||
}
|
||||
|
||||
def changeUser(u: User, cid: Ident): RUser =
|
||||
RUser(Ident.unsafe(""), u.login, cid, u.password.getOrElse(Password.empty), u.state, u.email, u.loginCount, u.lastLogin, u.created)
|
||||
RUser(
|
||||
Ident.unsafe(""),
|
||||
u.login,
|
||||
cid,
|
||||
u.password.getOrElse(Password.empty),
|
||||
u.state,
|
||||
u.email,
|
||||
u.loginCount,
|
||||
u.lastLogin,
|
||||
u.created
|
||||
)
|
||||
|
||||
// tags
|
||||
|
||||
@ -255,34 +364,36 @@ trait Conversions {
|
||||
Tag(rt.tagId, rt.name, rt.category, rt.created)
|
||||
|
||||
def newTag[F[_]: Sync](t: Tag, cid: Ident): F[RTag] =
|
||||
timeId.map { case (id, now) =>
|
||||
RTag(id, cid, t.name, t.category, now)
|
||||
timeId.map {
|
||||
case (id, now) =>
|
||||
RTag(id, cid, t.name, t.category, now)
|
||||
}
|
||||
|
||||
def changeTag(t: Tag, cid: Ident): RTag =
|
||||
RTag(t.id, cid, t.name, t.category, t.created)
|
||||
|
||||
|
||||
// sources
|
||||
|
||||
def mkSource(s: RSource): Source =
|
||||
Source(s.sid, s.abbrev, s.description, s.counter, s.enabled, s.priority, s.created)
|
||||
|
||||
def newSource[F[_]: Sync](s: Source, cid: Ident): F[RSource] =
|
||||
timeId.map({ case (id, now) =>
|
||||
RSource(id, cid, s.abbrev, s.description, 0, s.enabled, s.priority, now)
|
||||
timeId.map({
|
||||
case (id, now) =>
|
||||
RSource(id, cid, s.abbrev, s.description, 0, s.enabled, s.priority, now)
|
||||
})
|
||||
|
||||
def changeSource[F[_]: Sync](s: Source, coll: Ident): RSource =
|
||||
RSource(s.id, coll, s.abbrev, s.description, s.counter, s.enabled, s.priority, s.created)
|
||||
RSource(s.id, coll, s.abbrev, s.description, s.counter, s.enabled, s.priority, s.created)
|
||||
|
||||
// equipment
|
||||
def mkEquipment(re: REquipment): Equipment =
|
||||
Equipment(re.eid, re.name, re.created)
|
||||
|
||||
def newEquipment[F[_]: Sync](e: Equipment, cid: Ident): F[REquipment] =
|
||||
timeId.map({ case (id, now) =>
|
||||
REquipment(id, cid, e.name, now)
|
||||
timeId.map({
|
||||
case (id, now) =>
|
||||
REquipment(id, cid, e.name, now)
|
||||
})
|
||||
|
||||
def changeEquipment(e: Equipment, cid: Ident): REquipment =
|
||||
@ -298,26 +409,28 @@ trait Conversions {
|
||||
def basicResult(cr: JobCancelResult): BasicResult =
|
||||
cr match {
|
||||
case JobCancelResult.JobNotFound => BasicResult(false, "Job not found")
|
||||
case JobCancelResult.CancelRequested => BasicResult(true, "Cancel was requested at the job executor")
|
||||
case JobCancelResult.CancelRequested =>
|
||||
BasicResult(true, "Cancel was requested at the job executor")
|
||||
case JobCancelResult.Removed => BasicResult(true, "The job has been removed from the queue.")
|
||||
}
|
||||
|
||||
def basicResult(ar: AddResult, successMsg: String): BasicResult = ar match {
|
||||
case AddResult.Success => BasicResult(true, successMsg)
|
||||
case AddResult.Success => BasicResult(true, successMsg)
|
||||
case AddResult.EntityExists(msg) => BasicResult(false, msg)
|
||||
case AddResult.Failure(ex) => BasicResult(false, s"Internal error: ${ex.getMessage}")
|
||||
case AddResult.Failure(ex) => BasicResult(false, s"Internal error: ${ex.getMessage}")
|
||||
}
|
||||
|
||||
def basicResult(ur: OUpload.UploadResult): BasicResult = ur match {
|
||||
case UploadResult.Success => BasicResult(true, "Files submitted.")
|
||||
case UploadResult.NoFiles => BasicResult(false, "There were no files to submit.")
|
||||
case UploadResult.Success => BasicResult(true, "Files submitted.")
|
||||
case UploadResult.NoFiles => BasicResult(false, "There were no files to submit.")
|
||||
case UploadResult.NoSource => BasicResult(false, "The source id is not valid.")
|
||||
}
|
||||
|
||||
def basicResult(cr: PassChangeResult): BasicResult = cr match {
|
||||
case PassChangeResult.Success => BasicResult(true, "Password changed.")
|
||||
case PassChangeResult.Success => BasicResult(true, "Password changed.")
|
||||
case PassChangeResult.UpdateFailed => BasicResult(false, "The database update failed.")
|
||||
case PassChangeResult.PasswordMismatch => BasicResult(false, "The current password is incorrect.")
|
||||
case PassChangeResult.PasswordMismatch =>
|
||||
BasicResult(false, "The current password is incorrect.")
|
||||
case PassChangeResult.UserNotFound => BasicResult(false, "User not found.")
|
||||
}
|
||||
|
||||
|
@ -8,28 +8,26 @@ import org.http4s.dsl.Http4sDsl
|
||||
trait ResponseGenerator[F[_]] {
|
||||
self: Http4sDsl[F] =>
|
||||
|
||||
|
||||
implicit final class EitherResponses[A,B](e: Either[A, B]) {
|
||||
def toResponse(headers: Header*)
|
||||
(implicit F: Applicative[F]
|
||||
, w0: EntityEncoder[F, A]
|
||||
, w1: EntityEncoder[F, B]): F[Response[F]] =
|
||||
implicit final class EitherResponses[A, B](e: Either[A, B]) {
|
||||
def toResponse(headers: Header*)(
|
||||
implicit F: Applicative[F],
|
||||
w0: EntityEncoder[F, A],
|
||||
w1: EntityEncoder[F, B]
|
||||
): F[Response[F]] =
|
||||
e.fold(
|
||||
a => UnprocessableEntity(a),
|
||||
b => Ok(b)
|
||||
).map(_.withHeaders(headers: _*))
|
||||
a => UnprocessableEntity(a),
|
||||
b => Ok(b)
|
||||
)
|
||||
.map(_.withHeaders(headers: _*))
|
||||
}
|
||||
|
||||
implicit final class OptionResponse[A](o: Option[A]) {
|
||||
def toResponse(headers: Header*)
|
||||
(implicit F: Applicative[F]
|
||||
, w0: EntityEncoder[F, A]): F[Response[F]] =
|
||||
def toResponse(
|
||||
headers: Header*
|
||||
)(implicit F: Applicative[F], w0: EntityEncoder[F, A]): F[Response[F]] =
|
||||
o.map(a => Ok(a)).getOrElse(NotFound()).map(_.withHeaders(headers: _*))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object ResponseGenerator {
|
||||
|
||||
|
||||
}
|
||||
object ResponseGenerator {}
|
||||
|
@ -18,17 +18,18 @@ import org.http4s.headers.ETag.EntityTag
|
||||
object AttachmentRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
def makeByteResp(data: OItem.AttachmentData[F]): F[Response[F]] = {
|
||||
val mt = MediaType.unsafeParse(data.meta.mimetype.asString)
|
||||
val mt = MediaType.unsafeParse(data.meta.mimetype.asString)
|
||||
val cntLen: Header = `Content-Length`.unsafeFromLong(data.meta.length)
|
||||
val eTag: Header = ETag(data.meta.checksum)
|
||||
val disp: Header = `Content-Disposition`("inline", Map("filename" -> data.ra.name.getOrElse("")))
|
||||
Ok(data.data.take(data.meta.length)).
|
||||
map(r => r.withContentType(`Content-Type`(mt)).
|
||||
withHeaders(cntLen, eTag, disp))
|
||||
val eTag: Header = ETag(data.meta.checksum)
|
||||
val disp: Header =
|
||||
`Content-Disposition`("inline", Map("filename" -> data.ra.name.getOrElse("")))
|
||||
Ok(data.data.take(data.meta.length)).map(r =>
|
||||
r.withContentType(`Content-Type`(mt)).withHeaders(cntLen, eTag, disp)
|
||||
)
|
||||
}
|
||||
|
||||
HttpRoutes.of {
|
||||
@ -37,21 +38,24 @@ object AttachmentRoutes {
|
||||
fileData <- backend.item.findAttachment(id, user.account.collective)
|
||||
inm = req.headers.get(`If-None-Match`).flatMap(_.tags)
|
||||
matches = matchETag(fileData, inm)
|
||||
resp <- if (matches) NotModified()
|
||||
else fileData.map(makeByteResp).getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
resp <- if (matches) NotModified()
|
||||
else
|
||||
fileData.map(makeByteResp).getOrElse(NotFound(BasicResult(false, "Not found")))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) / "meta" =>
|
||||
for {
|
||||
rm <- backend.item.findAttachmentMeta(id, user.account.collective)
|
||||
rm <- backend.item.findAttachmentMeta(id, user.account.collective)
|
||||
md = rm.map(Conversions.mkAttachmentMeta)
|
||||
resp <- md.map(Ok(_)).getOrElse(NotFound(BasicResult(false, "Not found.")))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
private def matchETag[F[_]]( fileData: Option[OItem.AttachmentData[F]]
|
||||
, noneMatch: Option[NonEmptyList[EntityTag]]): Boolean =
|
||||
private def matchETag[F[_]](
|
||||
fileData: Option[OItem.AttachmentData[F]],
|
||||
noneMatch: Option[NonEmptyList[EntityTag]]
|
||||
): Boolean =
|
||||
(fileData, noneMatch) match {
|
||||
case (Some(fd), Some(nm)) =>
|
||||
fd.meta.checksum == nm.head.tag
|
||||
|
@ -12,14 +12,17 @@ import org.http4s.server._
|
||||
|
||||
object Authenticate {
|
||||
|
||||
def authenticateRequest[F[_]: Effect](auth: String => F[Login.Result])(req: Request[F]): F[Login.Result] =
|
||||
def authenticateRequest[F[_]: Effect](
|
||||
auth: String => F[Login.Result]
|
||||
)(req: Request[F]): F[Login.Result] =
|
||||
CookieData.authenticator(req) match {
|
||||
case Right(str) => auth(str)
|
||||
case Left(_) => Login.Result.invalidAuth.pure[F]
|
||||
case Left(_) => Login.Result.invalidAuth.pure[F]
|
||||
}
|
||||
|
||||
|
||||
def of[F[_]: Effect](S: Login[F], cfg: Login.Config)(pf: PartialFunction[AuthedRequest[F, AuthToken], F[Response[F]]]): HttpRoutes[F] = {
|
||||
def of[F[_]: Effect](S: Login[F], cfg: Login.Config)(
|
||||
pf: PartialFunction[AuthedRequest[F, AuthToken], F[Response[F]]]
|
||||
): HttpRoutes[F] = {
|
||||
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
@ -34,7 +37,9 @@ object Authenticate {
|
||||
middleware(AuthedRoutes.of(pf))
|
||||
}
|
||||
|
||||
def apply[F[_]: Effect](S: Login[F], cfg: Login.Config)(f: AuthToken => HttpRoutes[F]): HttpRoutes[F] = {
|
||||
def apply[F[_]: Effect](S: Login[F], cfg: Login.Config)(
|
||||
f: AuthToken => HttpRoutes[F]
|
||||
): HttpRoutes[F] = {
|
||||
val dsl: Http4sDsl[F] = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
@ -49,6 +54,8 @@ object Authenticate {
|
||||
middleware(AuthedRoutes(authReq => f(authReq.context).run(authReq.req)))
|
||||
}
|
||||
|
||||
private def getUser[F[_]: Effect](auth: String => F[Login.Result]): Kleisli[F, Request[F], Either[String, AuthToken]] =
|
||||
private def getUser[F[_]: Effect](
|
||||
auth: String => F[Login.Result]
|
||||
): Kleisli[F, Request[F], Either[String, AuthToken]] =
|
||||
Kleisli(r => authenticateRequest(auth)(r).map(_.toEither))
|
||||
}
|
||||
|
@ -25,25 +25,25 @@ object CollectiveRoutes {
|
||||
resp <- Ok(Conversions.mkItemInsights(ins))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / "settings" =>
|
||||
case req @ POST -> Root / "settings" =>
|
||||
for {
|
||||
settings <- req.as[CollectiveSettings]
|
||||
res <- backend.collective.updateLanguage(user.account.collective, settings.language)
|
||||
resp <- Ok(Conversions.basicResult(res, "Language updated."))
|
||||
settings <- req.as[CollectiveSettings]
|
||||
res <- backend.collective.updateLanguage(user.account.collective, settings.language)
|
||||
resp <- Ok(Conversions.basicResult(res, "Language updated."))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / "settings" =>
|
||||
for {
|
||||
collDb <- backend.collective.find(user.account.collective)
|
||||
sett = collDb.map(c => CollectiveSettings(c.language))
|
||||
resp <- sett.toResponse()
|
||||
collDb <- backend.collective.find(user.account.collective)
|
||||
sett = collDb.map(c => CollectiveSettings(c.language))
|
||||
resp <- sett.toResponse()
|
||||
} yield resp
|
||||
|
||||
case GET -> Root =>
|
||||
for {
|
||||
collDb <- backend.collective.find(user.account.collective)
|
||||
coll = collDb.map(c => Collective(c.id, c.state, c.created))
|
||||
resp <- coll.toResponse()
|
||||
collDb <- backend.collective.find(user.account.collective)
|
||||
coll = collDb.map(c => Collective(c.id, c.state, c.created))
|
||||
resp <- coll.toResponse()
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import org.http4s.dsl.Http4sDsl
|
||||
object EquipmentRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
@ -36,12 +36,12 @@ object EquipmentRoutes {
|
||||
case req @ PUT -> Root =>
|
||||
for {
|
||||
data <- req.as[Equipment]
|
||||
equip = changeEquipment(data, user.account.collective)
|
||||
equip = changeEquipment(data, user.account.collective)
|
||||
res <- backend.equipment.update(equip)
|
||||
resp <- Ok(basicResult(res, "Equipment updated."))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
del <- backend.equipment.delete(id, user.account.collective)
|
||||
resp <- Ok(basicResult(del, "Equipment deleted."))
|
||||
|
@ -10,15 +10,19 @@ import org.http4s.dsl.Http4sDsl
|
||||
object InfoRoutes {
|
||||
|
||||
def apply[F[_]: Sync](): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
HttpRoutes.of[F] {
|
||||
case GET -> (Root / "version") =>
|
||||
Ok(VersionInfo(BuildInfo.version
|
||||
, BuildInfo.builtAtMillis
|
||||
, BuildInfo.builtAtString
|
||||
, BuildInfo.gitHeadCommit.getOrElse("")
|
||||
, BuildInfo.gitDescribedVersion.getOrElse("")))
|
||||
Ok(
|
||||
VersionInfo(
|
||||
BuildInfo.version,
|
||||
BuildInfo.builtAtMillis,
|
||||
BuildInfo.builtAtString,
|
||||
BuildInfo.gitHeadCommit.getOrElse(""),
|
||||
BuildInfo.gitDescribedVersion.getOrElse("")
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,24 +18,24 @@ object ItemRoutes {
|
||||
private[this] val logger = getLogger
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / "search" =>
|
||||
for {
|
||||
mask <- req.as[ItemSearch]
|
||||
_ <- logger.ftrace(s"Got search mask: $mask")
|
||||
query = Conversions.mkQuery(mask, user.account.collective)
|
||||
_ <- logger.ftrace(s"Running query: $query")
|
||||
items <- backend.item.findItems(query, 100)
|
||||
resp <- Ok(Conversions.mkItemList(items))
|
||||
mask <- req.as[ItemSearch]
|
||||
_ <- logger.ftrace(s"Got search mask: $mask")
|
||||
query = Conversions.mkQuery(mask, user.account.collective)
|
||||
_ <- logger.ftrace(s"Running query: $query")
|
||||
items <- backend.item.findItems(query, 100)
|
||||
resp <- Ok(Conversions.mkItemList(items))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) =>
|
||||
for {
|
||||
item <- backend.item.findItem(id, user.account.collective)
|
||||
result = item.map(Conversions.mkItemDetail)
|
||||
result = item.map(Conversions.mkItemDetail)
|
||||
resp <- result.map(r => Ok(r)).getOrElse(NotFound(BasicResult(false, "Not found.")))
|
||||
} yield resp
|
||||
|
||||
@ -51,89 +51,89 @@ object ItemRoutes {
|
||||
resp <- Ok(Conversions.basicResult(res, "Item back to created."))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "tags" =>
|
||||
case req @ POST -> Root / Ident(id) / "tags" =>
|
||||
for {
|
||||
tags <- req.as[ReferenceList].map(_.items)
|
||||
res <- backend.item.setTags(id, tags.map(_.id), user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Tags updated"))
|
||||
tags <- req.as[ReferenceList].map(_.items)
|
||||
res <- backend.item.setTags(id, tags.map(_.id), user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Tags updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "direction" =>
|
||||
case req @ POST -> Root / Ident(id) / "direction" =>
|
||||
for {
|
||||
dir <- req.as[DirectionValue]
|
||||
res <- backend.item.setDirection(id, dir.direction, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Direction updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "corrOrg" =>
|
||||
case req @ POST -> Root / Ident(id) / "corrOrg" =>
|
||||
for {
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setCorrOrg(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setCorrOrg(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Correspondent organization updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "corrPerson" =>
|
||||
case req @ POST -> Root / Ident(id) / "corrPerson" =>
|
||||
for {
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setCorrPerson(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setCorrPerson(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Correspondent person updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "concPerson" =>
|
||||
case req @ POST -> Root / Ident(id) / "concPerson" =>
|
||||
for {
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setConcPerson(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned person updated"))
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setConcPerson(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned person updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "concEquipment" =>
|
||||
case req @ POST -> Root / Ident(id) / "concEquipment" =>
|
||||
for {
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setConcEquip(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "notes" =>
|
||||
for {
|
||||
text <- req.as[OptionalText]
|
||||
res <- backend.item.setNotes(id, text.text, user.account.collective)
|
||||
idref <- req.as[OptionalId]
|
||||
res <- backend.item.setConcEquip(id, idref.id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "name" =>
|
||||
case req @ POST -> Root / Ident(id) / "notes" =>
|
||||
for {
|
||||
text <- req.as[OptionalText]
|
||||
res <- backend.item.setName(id, text.text.getOrElse(""), user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
text <- req.as[OptionalText]
|
||||
res <- backend.item.setNotes(id, text.text, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "duedate" =>
|
||||
case req @ POST -> Root / Ident(id) / "name" =>
|
||||
for {
|
||||
date <- req.as[OptionalDate]
|
||||
_ <- logger.fdebug(s"Setting item due date to ${date.date}")
|
||||
res <- backend.item.setItemDueDate(id, date.date, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
|
||||
text <- req.as[OptionalText]
|
||||
res <- backend.item.setName(id, text.text.getOrElse(""), user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Concerned equipment updated"))
|
||||
} yield resp
|
||||
|
||||
case req@POST -> Root / Ident(id) / "date" =>
|
||||
case req @ POST -> Root / Ident(id) / "duedate" =>
|
||||
for {
|
||||
date <- req.as[OptionalDate]
|
||||
_ <- logger.fdebug(s"Setting item date to ${date.date}")
|
||||
res <- backend.item.setItemDate(id, date.date, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item date updated"))
|
||||
date <- req.as[OptionalDate]
|
||||
_ <- logger.fdebug(s"Setting item due date to ${date.date}")
|
||||
res <- backend.item.setItemDueDate(id, date.date, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item due date updated"))
|
||||
} yield resp
|
||||
|
||||
case req @ POST -> Root / Ident(id) / "date" =>
|
||||
for {
|
||||
date <- req.as[OptionalDate]
|
||||
_ <- logger.fdebug(s"Setting item date to ${date.date}")
|
||||
res <- backend.item.setItemDate(id, date.date, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(res, "Item date updated"))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root / Ident(id) / "proposals" =>
|
||||
for {
|
||||
ml <- backend.item.getProposals(id, user.account.collective)
|
||||
ip = Conversions.mkItemProposals(ml)
|
||||
ip = Conversions.mkItemProposals(ml)
|
||||
resp <- Ok(ip)
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
n <- backend.item.delete(id, user.account.collective)
|
||||
res = BasicResult(n > 0, if (n > 0) "Item deleted" else "Item deletion failed.")
|
||||
res = BasicResult(n > 0, if (n > 0) "Item deleted" else "Item deletion failed.")
|
||||
resp <- Ok(res)
|
||||
} yield resp
|
||||
}
|
||||
|
@ -19,15 +19,15 @@ object JobQueueRoutes {
|
||||
HttpRoutes.of {
|
||||
case GET -> Root / "state" =>
|
||||
for {
|
||||
js <- backend.job.queueState(user.account.collective, 200)
|
||||
js <- backend.job.queueState(user.account.collective, 200)
|
||||
res = Conversions.mkJobQueueState(js)
|
||||
resp <- Ok(res)
|
||||
} yield resp
|
||||
|
||||
case POST -> Root / Ident(id) / "cancel" =>
|
||||
for {
|
||||
result <- backend.job.cancelJob(id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(result))
|
||||
result <- backend.job.cancelJob(id, user.account.collective)
|
||||
resp <- Ok(Conversions.basicResult(result))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
@ -18,10 +18,10 @@ object LoginRoutes {
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of[F] {
|
||||
case req@POST -> Root / "login" =>
|
||||
case req @ POST -> Root / "login" =>
|
||||
for {
|
||||
up <- req.as[UserPass]
|
||||
res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password))
|
||||
up <- req.as[UserPass]
|
||||
res <- S.loginUserPass(cfg.auth)(Login.UserPass(up.account, up.password))
|
||||
resp <- makeResponse(dsl, cfg, res, up.account)
|
||||
} yield resp
|
||||
}
|
||||
@ -33,22 +33,36 @@ object LoginRoutes {
|
||||
|
||||
HttpRoutes.of[F] {
|
||||
case req @ POST -> Root / "session" =>
|
||||
Authenticate.authenticateRequest(S.loginSession(cfg.auth))(req).
|
||||
flatMap(res => makeResponse(dsl, cfg, res, ""))
|
||||
Authenticate
|
||||
.authenticateRequest(S.loginSession(cfg.auth))(req)
|
||||
.flatMap(res => makeResponse(dsl, cfg, res, ""))
|
||||
|
||||
case POST -> Root / "logout" =>
|
||||
Ok().map(_.addCookie(CookieData.deleteCookie(cfg)))
|
||||
}
|
||||
}
|
||||
|
||||
def makeResponse[F[_]: Effect](dsl: Http4sDsl[F], cfg: Config, res: Login.Result, account: String): F[Response[F]] = {
|
||||
def makeResponse[F[_]: Effect](
|
||||
dsl: Http4sDsl[F],
|
||||
cfg: Config,
|
||||
res: Login.Result,
|
||||
account: String
|
||||
): F[Response[F]] = {
|
||||
import dsl._
|
||||
res match {
|
||||
case Login.Result.Ok(token) =>
|
||||
for {
|
||||
cd <- AuthToken.user(token.account, cfg.auth.serverSecret).map(CookieData.apply)
|
||||
resp <- Ok(AuthResult(token.account.collective.id, token.account.user.id, true, "Login successful", Some(cd.asString), cfg.auth.sessionValid.millis)).
|
||||
map(_.addCookie(cd.asCookie(cfg)))
|
||||
resp <- Ok(
|
||||
AuthResult(
|
||||
token.account.collective.id,
|
||||
token.account.user.id,
|
||||
true,
|
||||
"Login successful",
|
||||
Some(cd.asString),
|
||||
cfg.auth.sessionValid.millis
|
||||
)
|
||||
).map(_.addCookie(cd.asCookie(cfg)))
|
||||
} yield resp
|
||||
case _ =>
|
||||
Ok(AuthResult("", account, false, "Login failed.", None, 0L))
|
||||
|
@ -16,15 +16,15 @@ import org.http4s.dsl.Http4sDsl
|
||||
object OrganizationRoutes {
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? FullQueryParamMatcher(full) =>
|
||||
if (full.getOrElse(false)) {
|
||||
for {
|
||||
data <- backend.organization.findAllOrg(user.account)
|
||||
resp <- Ok(OrganizationList(data.map(mkOrg).toList))
|
||||
data <- backend.organization.findAllOrg(user.account)
|
||||
resp <- Ok(OrganizationList(data.map(mkOrg).toList))
|
||||
} yield resp
|
||||
} else {
|
||||
for {
|
||||
@ -38,7 +38,7 @@ object OrganizationRoutes {
|
||||
data <- req.as[Organization]
|
||||
newOrg <- newOrg(data, user.account.collective)
|
||||
added <- backend.organization.addOrg(newOrg)
|
||||
resp <- Ok(basicResult(added, "New organization saved."))
|
||||
resp <- Ok(basicResult(added, "New organization saved."))
|
||||
} yield resp
|
||||
|
||||
case req @ PUT -> Root =>
|
||||
@ -49,10 +49,10 @@ object OrganizationRoutes {
|
||||
resp <- Ok(basicResult(update, "Organization updated."))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
delOrg <- backend.organization.deleteOrg(id, user.account.collective)
|
||||
resp <- Ok(basicResult(delOrg, "Organization deleted."))
|
||||
delOrg <- backend.organization.deleteOrg(id, user.account.collective)
|
||||
resp <- Ok(basicResult(delOrg, "Organization deleted."))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ import org.http4s.dsl.impl.OptionalQueryParamDecoderMatcher
|
||||
object ParamDecoder {
|
||||
|
||||
implicit val booleanDecoder: QueryParamDecoder[Boolean] =
|
||||
QueryParamDecoder.fromUnsafeCast(qp => Option(qp.value).exists(_ equalsIgnoreCase "true"))("Boolean")
|
||||
QueryParamDecoder.fromUnsafeCast(qp => Option(qp.value).exists(_.equalsIgnoreCase("true")))(
|
||||
"Boolean"
|
||||
)
|
||||
|
||||
object FullQueryParamMatcher extends OptionalQueryParamDecoderMatcher[Boolean]("full")
|
||||
|
||||
|
||||
}
|
||||
|
@ -19,15 +19,15 @@ object PersonRoutes {
|
||||
private[this] val logger = getLogger
|
||||
|
||||
def apply[F[_]: Effect](backend: BackendApp[F], user: AuthToken): HttpRoutes[F] = {
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case GET -> Root :? FullQueryParamMatcher(full) =>
|
||||
case GET -> Root :? FullQueryParamMatcher(full) =>
|
||||
if (full.getOrElse(false)) {
|
||||
for {
|
||||
data <- backend.organization.findAllPerson(user.account)
|
||||
resp <- Ok(PersonList(data.map(mkPerson).toList))
|
||||
data <- backend.organization.findAllPerson(user.account)
|
||||
resp <- Ok(PersonList(data.map(mkPerson).toList))
|
||||
} yield resp
|
||||
} else {
|
||||
for {
|
||||
@ -41,7 +41,7 @@ object PersonRoutes {
|
||||
data <- req.as[Person]
|
||||
newPer <- newPerson(data, user.account.collective)
|
||||
added <- backend.organization.addPerson(newPer)
|
||||
resp <- Ok(basicResult(added, "New person saved."))
|
||||
resp <- Ok(basicResult(added, "New person saved."))
|
||||
} yield resp
|
||||
|
||||
case req @ PUT -> Root =>
|
||||
@ -52,11 +52,11 @@ object PersonRoutes {
|
||||
resp <- Ok(basicResult(update, "Person updated."))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
_ <- logger.fdebug(s"Deleting person ${id.id}")
|
||||
delOrg <- backend.organization.deletePerson(id, user.account.collective)
|
||||
resp <- Ok(basicResult(delOrg, "Person deleted."))
|
||||
_ <- logger.fdebug(s"Deleting person ${id.id}")
|
||||
delOrg <- backend.organization.deletePerson(id, user.account.collective)
|
||||
resp <- Ok(basicResult(delOrg, "Person deleted."))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
@ -24,16 +24,16 @@ object RegisterRoutes {
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / "register" =>
|
||||
for {
|
||||
data <- req.as[Registration]
|
||||
res <- backend.signup.register(cfg.backend.signup)(convert(data))
|
||||
resp <- Ok(convert(res))
|
||||
data <- req.as[Registration]
|
||||
res <- backend.signup.register(cfg.backend.signup)(convert(data))
|
||||
resp <- Ok(convert(res))
|
||||
} yield resp
|
||||
|
||||
case req@ POST -> Root / "newinvite" =>
|
||||
case req @ POST -> Root / "newinvite" =>
|
||||
for {
|
||||
data <- req.as[GenInvite]
|
||||
res <- backend.signup.newInvite(cfg.backend.signup)(data.password)
|
||||
resp <- Ok(convert(res))
|
||||
data <- req.as[GenInvite]
|
||||
res <- backend.signup.newInvite(cfg.backend.signup)(data.password)
|
||||
resp <- Ok(convert(res))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
@ -47,7 +47,6 @@ object RegisterRoutes {
|
||||
InviteResult(false, "Password is invalid.", None)
|
||||
}
|
||||
|
||||
|
||||
def convert(r: SignupResult): BasicResult = r match {
|
||||
case SignupResult.CollectiveExists =>
|
||||
BasicResult(false, "A collective with this name already exists.")
|
||||
@ -62,7 +61,6 @@ object RegisterRoutes {
|
||||
BasicResult(true, "Signup successful")
|
||||
}
|
||||
|
||||
|
||||
def convert(r: Registration): RegisterData =
|
||||
RegisterData(r.collectiveName, r.login, r.password, r.invite)
|
||||
}
|
||||
|
@ -22,8 +22,8 @@ object SourceRoutes {
|
||||
HttpRoutes.of {
|
||||
case GET -> Root =>
|
||||
for {
|
||||
all <- backend.source.findAll(user.account)
|
||||
res <- Ok(SourceList(all.map(mkSource).toList))
|
||||
all <- backend.source.findAll(user.account)
|
||||
res <- Ok(SourceList(all.map(mkSource).toList))
|
||||
} yield res
|
||||
|
||||
case req @ POST -> Root =>
|
||||
@ -37,12 +37,12 @@ object SourceRoutes {
|
||||
case req @ PUT -> Root =>
|
||||
for {
|
||||
data <- req.as[Source]
|
||||
src = changeSource(data, user.account.collective)
|
||||
src = changeSource(data, user.account.collective)
|
||||
updated <- backend.source.update(src)
|
||||
resp <- Ok(basicResult(updated, "Source updated."))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
del <- backend.source.delete(id, user.account.collective)
|
||||
resp <- Ok(basicResult(del, "Source deleted."))
|
||||
|
@ -28,21 +28,21 @@ object TagRoutes {
|
||||
|
||||
case req @ POST -> Root =>
|
||||
for {
|
||||
data <- req.as[Tag]
|
||||
tag <- newTag(data, user.account.collective)
|
||||
res <- backend.tag.add(tag)
|
||||
resp <- Ok(basicResult(res, "Tag successfully created."))
|
||||
data <- req.as[Tag]
|
||||
tag <- newTag(data, user.account.collective)
|
||||
res <- backend.tag.add(tag)
|
||||
resp <- Ok(basicResult(res, "Tag successfully created."))
|
||||
} yield resp
|
||||
|
||||
case req @ PUT -> Root =>
|
||||
for {
|
||||
data <- req.as[Tag]
|
||||
tag = changeTag(data, user.account.collective)
|
||||
res <- backend.tag.update(tag)
|
||||
resp <- Ok(basicResult(res, "Tag successfully updated."))
|
||||
data <- req.as[Tag]
|
||||
tag = changeTag(data, user.account.collective)
|
||||
res <- backend.tag.update(tag)
|
||||
resp <- Ok(basicResult(res, "Tag successfully updated."))
|
||||
} yield resp
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
del <- backend.tag.delete(id, user.account.collective)
|
||||
resp <- Ok(basicResult(del, "Tag successfully deleted."))
|
||||
|
@ -26,9 +26,14 @@ object UploadRoutes {
|
||||
case req @ POST -> Root / "item" =>
|
||||
for {
|
||||
multipart <- req.as[Multipart[F]]
|
||||
updata <- readMultipart(multipart, logger, Priority.High, cfg.backend.files.validMimeTypes)
|
||||
result <- backend.upload.submit(updata, user.account)
|
||||
res <- Ok(basicResult(result))
|
||||
updata <- readMultipart(
|
||||
multipart,
|
||||
logger,
|
||||
Priority.High,
|
||||
cfg.backend.files.validMimeTypes
|
||||
)
|
||||
result <- backend.upload.submit(updata, user.account)
|
||||
res <- Ok(basicResult(result))
|
||||
} yield res
|
||||
|
||||
}
|
||||
@ -39,12 +44,12 @@ object UploadRoutes {
|
||||
import dsl._
|
||||
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / "item" / Ident(id)=>
|
||||
case req @ POST -> Root / "item" / Ident(id) =>
|
||||
for {
|
||||
multipart <- req.as[Multipart[F]]
|
||||
updata <- readMultipart(multipart, logger, Priority.Low, cfg.backend.files.validMimeTypes)
|
||||
result <- backend.upload.submit(updata, id)
|
||||
res <- Ok(basicResult(result))
|
||||
res <- Ok(basicResult(result))
|
||||
} yield res
|
||||
}
|
||||
}
|
||||
|
@ -22,15 +22,19 @@ object UserRoutes {
|
||||
HttpRoutes.of {
|
||||
case req @ POST -> Root / "changePassword" =>
|
||||
for {
|
||||
data <- req.as[PasswordChange]
|
||||
res <- backend.collective.changePassword(user.account, data.currentPassword, data.newPassword)
|
||||
resp <- Ok(basicResult(res))
|
||||
data <- req.as[PasswordChange]
|
||||
res <- backend.collective.changePassword(
|
||||
user.account,
|
||||
data.currentPassword,
|
||||
data.newPassword
|
||||
)
|
||||
resp <- Ok(basicResult(res))
|
||||
} yield resp
|
||||
|
||||
case GET -> Root =>
|
||||
for {
|
||||
all <- backend.collective.listUser(user.account.collective)
|
||||
res <- Ok(UserList(all.map(mkUser).toList))
|
||||
all <- backend.collective.listUser(user.account.collective)
|
||||
res <- Ok(UserList(all.map(mkUser).toList))
|
||||
} yield res
|
||||
|
||||
case req @ POST -> Root =>
|
||||
@ -51,7 +55,7 @@ object UserRoutes {
|
||||
|
||||
case DELETE -> Root / Ident(id) =>
|
||||
for {
|
||||
ar <- backend.collective.deleteUser(id, user.account.collective)
|
||||
ar <- backend.collective.deleteUser(id, user.account.collective)
|
||||
resp <- Ok(basicResult(ar, "User deleted."))
|
||||
} yield resp
|
||||
}
|
||||
|
@ -8,14 +8,21 @@ import docspell.backend.signup.{Config => SignupConfig}
|
||||
import yamusca.imports._
|
||||
import yamusca.implicits._
|
||||
|
||||
case class Flags( appName: String
|
||||
, baseUrl: LenientUri
|
||||
, signupMode: SignupConfig.Mode
|
||||
, docspellAssetPath: String)
|
||||
case class Flags(
|
||||
appName: String,
|
||||
baseUrl: LenientUri,
|
||||
signupMode: SignupConfig.Mode,
|
||||
docspellAssetPath: String
|
||||
)
|
||||
|
||||
object Flags {
|
||||
def apply(cfg: Config): Flags =
|
||||
Flags(cfg.appName, cfg.baseUrl, cfg.backend.signup.mode, s"assets/docspell-webapp/${BuildInfo.version}")
|
||||
Flags(
|
||||
cfg.appName,
|
||||
cfg.baseUrl,
|
||||
cfg.backend.signup.mode,
|
||||
s"assets/docspell-webapp/${BuildInfo.version}"
|
||||
)
|
||||
|
||||
implicit val jsonEncoder: Encoder[Flags] =
|
||||
deriveEncoder[Flags]
|
||||
|
@ -21,90 +21,100 @@ object TemplateRoutes {
|
||||
|
||||
val `text/html` = new MediaType("text", "html")
|
||||
|
||||
def apply[F[_]: Effect](blocker: Blocker, cfg: Config)(implicit C: ContextShift[F]): HttpRoutes[F] = {
|
||||
def apply[F[_]: Effect](blocker: Blocker, cfg: Config)(
|
||||
implicit C: ContextShift[F]
|
||||
): HttpRoutes[F] = {
|
||||
val indexTemplate = memo(loadResource("/index.html").flatMap(loadTemplate(_, blocker)))
|
||||
val docTemplate = memo(loadResource("/doc.html").flatMap(loadTemplate(_, blocker)))
|
||||
val docTemplate = memo(loadResource("/doc.html").flatMap(loadTemplate(_, blocker)))
|
||||
|
||||
val dsl = new Http4sDsl[F]{}
|
||||
val dsl = new Http4sDsl[F] {}
|
||||
import dsl._
|
||||
HttpRoutes.of[F] {
|
||||
case GET -> Root / "index.html" =>
|
||||
for {
|
||||
templ <- indexTemplate
|
||||
resp <- Ok(IndexData(cfg).render(templ), `Content-Type`(`text/html`))
|
||||
templ <- indexTemplate
|
||||
resp <- Ok(IndexData(cfg).render(templ), `Content-Type`(`text/html`))
|
||||
} yield resp
|
||||
case GET -> Root / "doc" =>
|
||||
for {
|
||||
templ <- docTemplate
|
||||
resp <- Ok(DocData().render(templ), `Content-Type`(`text/html`))
|
||||
templ <- docTemplate
|
||||
resp <- Ok(DocData().render(templ), `Content-Type`(`text/html`))
|
||||
} yield resp
|
||||
}
|
||||
}
|
||||
|
||||
def loadResource[F[_]: Sync](name: String): F[URL] = {
|
||||
def loadResource[F[_]: Sync](name: String): F[URL] =
|
||||
Option(getClass.getResource(name)) match {
|
||||
case None =>
|
||||
Sync[F].raiseError(new Exception("Unknown resource: "+ name))
|
||||
Sync[F].raiseError(new Exception("Unknown resource: " + name))
|
||||
case Some(r) =>
|
||||
r.pure[F]
|
||||
}
|
||||
}
|
||||
|
||||
def loadUrl[F[_]: Sync](url: URL, blocker: Blocker)(implicit C: ContextShift[F]): F[String] =
|
||||
Stream.bracket(Sync[F].delay(url.openStream))(in => Sync[F].delay(in.close())).
|
||||
flatMap(in => io.readInputStream(in.pure[F], 64 * 1024, blocker, false)).
|
||||
through(text.utf8Decode).
|
||||
compile.fold("")(_ + _)
|
||||
Stream
|
||||
.bracket(Sync[F].delay(url.openStream))(in => Sync[F].delay(in.close()))
|
||||
.flatMap(in => io.readInputStream(in.pure[F], 64 * 1024, blocker, false))
|
||||
.through(text.utf8Decode)
|
||||
.compile
|
||||
.fold("")(_ + _)
|
||||
|
||||
def parseTemplate[F[_]: Sync](str: String): F[Template] =
|
||||
Sync[F].delay {
|
||||
mustache.parse(str) match {
|
||||
case Right(t) => t
|
||||
case Right(t) => t
|
||||
case Left((_, err)) => sys.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
def loadTemplate[F[_]: Sync](url: URL, blocker: Blocker)(implicit C: ContextShift[F]): F[Template] = {
|
||||
loadUrl[F](url, blocker).flatMap(s => parseTemplate(s)).
|
||||
map(t => {
|
||||
logger.info(s"Compiled template $url")
|
||||
t
|
||||
})
|
||||
}
|
||||
def loadTemplate[F[_]: Sync](url: URL, blocker: Blocker)(
|
||||
implicit C: ContextShift[F]
|
||||
): F[Template] =
|
||||
loadUrl[F](url, blocker).flatMap(s => parseTemplate(s)).map { t =>
|
||||
logger.info(s"Compiled template $url")
|
||||
t
|
||||
}
|
||||
|
||||
case class DocData(swaggerRoot: String, openapiSpec: String)
|
||||
object DocData {
|
||||
|
||||
def apply(): DocData =
|
||||
DocData("/app/assets" + Webjars.swaggerui, s"/app/assets/${BuildInfo.name}/${BuildInfo.version}/docspell-openapi.yml")
|
||||
DocData(
|
||||
"/app/assets" + Webjars.swaggerui,
|
||||
s"/app/assets/${BuildInfo.name}/${BuildInfo.version}/docspell-openapi.yml"
|
||||
)
|
||||
|
||||
implicit def yamuscaValueConverter: ValueConverter[DocData] =
|
||||
ValueConverter.deriveConverter[DocData]
|
||||
}
|
||||
|
||||
case class IndexData(flags: Flags
|
||||
, cssUrls: Seq[String]
|
||||
, jsUrls: Seq[String]
|
||||
, faviconBase: String
|
||||
, appExtraJs: String
|
||||
, flagsJson: String)
|
||||
case class IndexData(
|
||||
flags: Flags,
|
||||
cssUrls: Seq[String],
|
||||
jsUrls: Seq[String],
|
||||
faviconBase: String,
|
||||
appExtraJs: String,
|
||||
flagsJson: String
|
||||
)
|
||||
|
||||
object IndexData {
|
||||
|
||||
def apply(cfg: Config): IndexData =
|
||||
IndexData(Flags(cfg)
|
||||
, Seq(
|
||||
IndexData(
|
||||
Flags(cfg),
|
||||
Seq(
|
||||
"/app/assets" + Webjars.semanticui + "/semantic.min.css",
|
||||
s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell.css"
|
||||
)
|
||||
, Seq(
|
||||
),
|
||||
Seq(
|
||||
"/app/assets" + Webjars.jquery + "/jquery.min.js",
|
||||
"/app/assets" + Webjars.semanticui + "/semantic.min.js",
|
||||
s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell-app.js"
|
||||
)
|
||||
, s"/app/assets/docspell-webapp/${BuildInfo.version}/favicon"
|
||||
, s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell.js"
|
||||
, Flags(cfg).asJson.spaces2 )
|
||||
),
|
||||
s"/app/assets/docspell-webapp/${BuildInfo.version}/favicon",
|
||||
s"/app/assets/docspell-webapp/${BuildInfo.version}/docspell.js",
|
||||
Flags(cfg).asJson.spaces2
|
||||
)
|
||||
|
||||
implicit def yamuscaValueConverter: ValueConverter[IndexData] =
|
||||
ValueConverter.deriveConverter[IndexData]
|
||||
@ -116,10 +126,10 @@ object TemplateRoutes {
|
||||
Option(ref.get) match {
|
||||
case Some(a) => a.pure[F]
|
||||
case None =>
|
||||
fa.map(a => {
|
||||
fa.map { a =>
|
||||
ref.set(a)
|
||||
a
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import org.http4s.server.staticcontent.WebjarService.{WebjarAsset, Config => Web
|
||||
|
||||
object WebjarRoutes {
|
||||
|
||||
def appRoutes[F[_]: Effect](blocker: Blocker)(implicit C: ContextShift[F]): HttpRoutes[F] = {
|
||||
def appRoutes[F[_]: Effect](blocker: Blocker)(implicit C: ContextShift[F]): HttpRoutes[F] =
|
||||
webjarService(
|
||||
WebjarConfig(
|
||||
filter = assetFilter,
|
||||
@ -17,10 +17,23 @@ object WebjarRoutes {
|
||||
cacheStrategy = NoopCacheStrategy[F]
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
def assetFilter(asset: WebjarAsset): Boolean =
|
||||
List(".js", ".css", ".html", ".json", ".jpg", ".png", ".eot", ".woff", ".woff2", ".svg", ".otf", ".ttf", ".yml", ".xml").
|
||||
exists(e => asset.asset.endsWith(e))
|
||||
List(
|
||||
".js",
|
||||
".css",
|
||||
".html",
|
||||
".json",
|
||||
".jpg",
|
||||
".png",
|
||||
".eot",
|
||||
".woff",
|
||||
".woff2",
|
||||
".svg",
|
||||
".otf",
|
||||
".ttf",
|
||||
".yml",
|
||||
".xml"
|
||||
).exists(e => asset.asset.endsWith(e))
|
||||
|
||||
}
|
||||
|
Reference in New Issue
Block a user