Apply scalafmt to all files

This commit is contained in:
Eike Kettner
2019-12-30 21:44:13 +01:00
parent 57e274e2b0
commit fc3e22e399
133 changed files with 3003 additions and 2112 deletions

View File

@ -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)
}

View File

@ -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))
}
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
)

View File

@ -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(

View File

@ -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.")
}

View File

@ -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 {}

View File

@ -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

View File

@ -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))
}

View File

@ -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
}
}

View File

@ -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."))

View File

@ -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("")
)
)
}
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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))

View File

@ -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
}
}

View File

@ -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")
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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."))

View File

@ -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."))

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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]

View File

@ -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
})
}
}
}
}

View File

@ -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))
}