mirror of
https://github.com/TheAnachronism/docspell.git
synced 2025-03-25 16:45:05 +00:00
Amend source form with tags and file-filter
Allow to define tags and a file filter per source.
This commit is contained in:
parent
4fd6e02ec0
commit
04ba14f802
@ -4,44 +4,50 @@ import cats.effect.{Effect, Resource}
|
|||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common.{AccountId, Ident}
|
import docspell.common.{AccountId, Ident}
|
||||||
|
import docspell.store.UpdateResult
|
||||||
import docspell.store.records.RSource
|
import docspell.store.records.RSource
|
||||||
|
import docspell.store.records.SourceData
|
||||||
import docspell.store.{AddResult, Store}
|
import docspell.store.{AddResult, Store}
|
||||||
|
|
||||||
trait OSource[F[_]] {
|
trait OSource[F[_]] {
|
||||||
|
|
||||||
def findAll(account: AccountId): F[Vector[RSource]]
|
def findAll(account: AccountId): F[Vector[SourceData]]
|
||||||
|
|
||||||
def add(s: RSource): F[AddResult]
|
def add(s: RSource, tags: List[String]): F[AddResult]
|
||||||
|
|
||||||
def update(s: RSource): F[AddResult]
|
def update(s: RSource, tags: List[String]): F[AddResult]
|
||||||
|
|
||||||
def delete(id: Ident, collective: Ident): F[AddResult]
|
def delete(id: Ident, collective: Ident): F[UpdateResult]
|
||||||
}
|
}
|
||||||
|
|
||||||
object OSource {
|
object OSource {
|
||||||
|
|
||||||
def apply[F[_]: Effect](store: Store[F]): Resource[F, OSource[F]] =
|
def apply[F[_]: Effect](store: Store[F]): Resource[F, OSource[F]] =
|
||||||
Resource.pure[F, OSource[F]](new OSource[F] {
|
Resource.pure[F, OSource[F]](new OSource[F] {
|
||||||
def findAll(account: AccountId): F[Vector[RSource]] =
|
def findAll(account: AccountId): F[Vector[SourceData]] =
|
||||||
store.transact(RSource.findAll(account.collective, _.abbrev))
|
store
|
||||||
|
.transact(SourceData.findAll(account.collective, _.abbrev))
|
||||||
|
.compile
|
||||||
|
.to(Vector)
|
||||||
|
|
||||||
def add(s: RSource): F[AddResult] = {
|
def add(s: RSource, tags: List[String]): F[AddResult] = {
|
||||||
def insert = RSource.insert(s)
|
def insert = SourceData.insert(s, tags)
|
||||||
def exists = RSource.existsByAbbrev(s.cid, s.abbrev)
|
def exists = RSource.existsByAbbrev(s.cid, s.abbrev)
|
||||||
|
|
||||||
val msg = s"A source with abbrev '${s.abbrev}' already exists"
|
val msg = s"A source with abbrev '${s.abbrev}' already exists"
|
||||||
store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
|
store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
def update(s: RSource): F[AddResult] = {
|
def update(s: RSource, tags: List[String]): F[AddResult] = {
|
||||||
def insert = RSource.updateNoCounter(s)
|
def insert = SourceData.update(s, tags)
|
||||||
def exists = RSource.existsByAbbrev(s.cid, s.abbrev)
|
def exists = RSource.existsByAbbrev(s.cid, s.abbrev)
|
||||||
|
|
||||||
val msg = s"A source with abbrev '${s.abbrev}' already exists"
|
val msg = s"A source with abbrev '${s.abbrev}' already exists"
|
||||||
store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
|
store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
|
||||||
}
|
}
|
||||||
|
|
||||||
def delete(id: Ident, collective: Ident): F[AddResult] =
|
def delete(id: Ident, collective: Ident): F[UpdateResult] =
|
||||||
store.transact(RSource.delete(id, collective)).attempt.map(AddResult.fromUpdate)
|
UpdateResult.fromUpdate(store.transact(SourceData.delete(id, collective)))
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import cats.effect.{Effect, Resource}
|
|||||||
import cats.implicits._
|
import cats.implicits._
|
||||||
|
|
||||||
import docspell.common.{AccountId, Ident}
|
import docspell.common.{AccountId, Ident}
|
||||||
|
import docspell.store.records.RTagSource
|
||||||
import docspell.store.records.{RTag, RTagItem}
|
import docspell.store.records.{RTag, RTagItem}
|
||||||
import docspell.store.{AddResult, Store}
|
import docspell.store.{AddResult, Store}
|
||||||
|
|
||||||
@ -49,8 +50,9 @@ object OTag {
|
|||||||
val io = for {
|
val io = for {
|
||||||
optTag <- RTag.findByIdAndCollective(id, collective)
|
optTag <- RTag.findByIdAndCollective(id, collective)
|
||||||
n0 <- optTag.traverse(t => RTagItem.deleteTag(t.tagId))
|
n0 <- optTag.traverse(t => RTagItem.deleteTag(t.tagId))
|
||||||
n1 <- optTag.traverse(t => RTag.delete(t.tagId, collective))
|
n1 <- optTag.traverse(t => RTagSource.deleteTag(t.tagId))
|
||||||
} yield n0.getOrElse(0) + n1.getOrElse(0)
|
n2 <- optTag.traverse(t => RTag.delete(t.tagId, collective))
|
||||||
|
} yield (n0 |+| n1 |+| n2).getOrElse(0)
|
||||||
store.transact(io).attempt.map(AddResult.fromUpdate)
|
store.transact(io).attempt.map(AddResult.fromUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,11 @@ trait OUpload[F[_]] {
|
|||||||
itemId: Option[Ident]
|
itemId: Option[Ident]
|
||||||
): F[OUpload.UploadResult]
|
): F[OUpload.UploadResult]
|
||||||
|
|
||||||
|
/** Submit files via a given source identifier. The source is looked
|
||||||
|
* up to identify the collective the files belong to. Metadata
|
||||||
|
* defined in the source is used as a fallback to those specified
|
||||||
|
* here (in UploadData).
|
||||||
|
*/
|
||||||
def submit(
|
def submit(
|
||||||
data: OUpload.UploadData[F],
|
data: OUpload.UploadData[F],
|
||||||
sourceId: Ident,
|
sourceId: Ident,
|
||||||
@ -153,15 +158,19 @@ object OUpload {
|
|||||||
itemId: Option[Ident]
|
itemId: Option[Ident]
|
||||||
): F[OUpload.UploadResult] =
|
): F[OUpload.UploadResult] =
|
||||||
(for {
|
(for {
|
||||||
src <- OptionT(store.transact(RSource.findEnabled(sourceId)))
|
src <- OptionT(store.transact(SourceData.findEnabled(sourceId)))
|
||||||
updata = data.copy(
|
updata = data.copy(
|
||||||
meta = data.meta.copy(
|
meta = data.meta.copy(
|
||||||
sourceAbbrev = src.abbrev,
|
sourceAbbrev = src.source.abbrev,
|
||||||
folderId = data.meta.folderId.orElse(src.folderId)
|
folderId = data.meta.folderId.orElse(src.source.folderId),
|
||||||
|
fileFilter =
|
||||||
|
if (data.meta.fileFilter == Glob.all) src.source.fileFilterOrAll
|
||||||
|
else data.meta.fileFilter,
|
||||||
|
tags = (data.meta.tags ++ src.tags.map(_.tagId.id)).distinct
|
||||||
),
|
),
|
||||||
priority = src.priority
|
priority = src.source.priority
|
||||||
)
|
)
|
||||||
accId = AccountId(src.cid, src.sid)
|
accId = AccountId(src.source.cid, src.source.sid)
|
||||||
result <- OptionT.liftF(submit(updata, accId, notifyJoex, itemId))
|
result <- OptionT.liftF(submit(updata, accId, notifyJoex, itemId))
|
||||||
} yield result).getOrElse(UploadResult.noSource)
|
} yield result).getOrElse(UploadResult.noSource)
|
||||||
|
|
||||||
|
@ -1211,7 +1211,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Source"
|
$ref: "#/components/schemas/SourceTagIn"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Ok
|
description: Ok
|
||||||
@ -1231,7 +1231,7 @@ paths:
|
|||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/Source"
|
$ref: "#/components/schemas/SourceTagIn"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: Ok
|
description: Ok
|
||||||
@ -4366,7 +4366,7 @@ components:
|
|||||||
items:
|
items:
|
||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/Source"
|
$ref: "#/components/schemas/SourceAndTags"
|
||||||
Source:
|
Source:
|
||||||
description: |
|
description: |
|
||||||
Data about a Source. A source defines the endpoint where
|
Data about a Source. A source defines the endpoint where
|
||||||
@ -4400,10 +4400,38 @@ components:
|
|||||||
folder:
|
folder:
|
||||||
type: string
|
type: string
|
||||||
format: ident
|
format: ident
|
||||||
|
fileFilter:
|
||||||
|
type: string
|
||||||
|
format: glob
|
||||||
created:
|
created:
|
||||||
description: DateTime
|
description: DateTime
|
||||||
type: integer
|
type: integer
|
||||||
format: date-time
|
format: date-time
|
||||||
|
SourceTagIn:
|
||||||
|
description: |
|
||||||
|
A source and optional tags (ids or names) for updating/adding.
|
||||||
|
required:
|
||||||
|
- source
|
||||||
|
- tags
|
||||||
|
properties:
|
||||||
|
source:
|
||||||
|
$ref: "#/components/schema/Source"
|
||||||
|
tags:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
SourceAndTags:
|
||||||
|
description: |
|
||||||
|
A source and optional tags.
|
||||||
|
required:
|
||||||
|
- source
|
||||||
|
- tags
|
||||||
|
properties:
|
||||||
|
source:
|
||||||
|
$ref: "#/components/schema/Source"
|
||||||
|
tags:
|
||||||
|
$ref: "#/components/schema/TagList"
|
||||||
|
|
||||||
EquipmentList:
|
EquipmentList:
|
||||||
description: |
|
description: |
|
||||||
A list of equipments.
|
A list of equipments.
|
||||||
|
@ -524,21 +524,36 @@ trait Conversions {
|
|||||||
|
|
||||||
// sources
|
// sources
|
||||||
|
|
||||||
def mkSource(s: RSource): Source =
|
def mkSource(s: SourceData): SourceAndTags =
|
||||||
Source(
|
SourceAndTags(
|
||||||
s.sid,
|
Source(
|
||||||
s.abbrev,
|
s.source.sid,
|
||||||
s.description,
|
s.source.abbrev,
|
||||||
s.counter,
|
s.source.description,
|
||||||
s.enabled,
|
s.source.counter,
|
||||||
s.priority,
|
s.source.enabled,
|
||||||
s.folderId,
|
s.source.priority,
|
||||||
s.created
|
s.source.folderId,
|
||||||
|
s.source.fileFilter,
|
||||||
|
s.source.created
|
||||||
|
),
|
||||||
|
TagList(s.tags.length, s.tags.map(mkTag).toList)
|
||||||
)
|
)
|
||||||
|
|
||||||
def newSource[F[_]: Sync](s: Source, cid: Ident): F[RSource] =
|
def newSource[F[_]: Sync](s: Source, cid: Ident): F[RSource] =
|
||||||
timeId.map({ case (id, now) =>
|
timeId.map({ case (id, now) =>
|
||||||
RSource(id, cid, s.abbrev, s.description, 0, s.enabled, s.priority, now, s.folder)
|
RSource(
|
||||||
|
id,
|
||||||
|
cid,
|
||||||
|
s.abbrev,
|
||||||
|
s.description,
|
||||||
|
0,
|
||||||
|
s.enabled,
|
||||||
|
s.priority,
|
||||||
|
now,
|
||||||
|
s.folder,
|
||||||
|
s.fileFilter
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
def changeSource[F[_]: Sync](s: Source, coll: Ident): RSource =
|
def changeSource[F[_]: Sync](s: Source, coll: Ident): RSource =
|
||||||
@ -551,7 +566,8 @@ trait Conversions {
|
|||||||
s.enabled,
|
s.enabled,
|
||||||
s.priority,
|
s.priority,
|
||||||
s.created,
|
s.created,
|
||||||
s.folder
|
s.folder,
|
||||||
|
s.fileFilter
|
||||||
)
|
)
|
||||||
|
|
||||||
// equipment
|
// equipment
|
||||||
|
@ -30,17 +30,17 @@ object SourceRoutes {
|
|||||||
|
|
||||||
case req @ POST -> Root =>
|
case req @ POST -> Root =>
|
||||||
for {
|
for {
|
||||||
data <- req.as[Source]
|
data <- req.as[SourceTagIn]
|
||||||
src <- newSource(data, user.account.collective)
|
src <- newSource(data.source, user.account.collective)
|
||||||
added <- backend.source.add(src)
|
added <- backend.source.add(src, data.tags)
|
||||||
resp <- Ok(basicResult(added, "Source added."))
|
resp <- Ok(basicResult(added, "Source added."))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
case req @ PUT -> Root =>
|
case req @ PUT -> Root =>
|
||||||
for {
|
for {
|
||||||
data <- req.as[Source]
|
data <- req.as[SourceTagIn]
|
||||||
src = changeSource(data, user.account.collective)
|
src = changeSource(data.source, user.account.collective)
|
||||||
updated <- backend.source.update(src)
|
updated <- backend.source.update(src, data.tags)
|
||||||
resp <- Ok(basicResult(updated, "Source updated."))
|
resp <- Ok(basicResult(updated, "Source updated."))
|
||||||
} yield resp
|
} yield resp
|
||||||
|
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
ALTER TABLE "source"
|
||||||
|
ADD COLUMN "file_filter" varchar(254) NULL;
|
||||||
|
|
||||||
|
CREATE TABLE "tagsource" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"source_id" varchar(254) not null,
|
||||||
|
"tag_id" varchar(254) not null,
|
||||||
|
unique ("source_id", "tag_id"),
|
||||||
|
foreign key ("source_id") references "source"("sid"),
|
||||||
|
foreign key ("tag_id") references "tag"("tid")
|
||||||
|
);
|
@ -0,0 +1,11 @@
|
|||||||
|
ALTER TABLE `source`
|
||||||
|
ADD COLUMN `file_filter` varchar(254) NULL;
|
||||||
|
|
||||||
|
CREATE TABLE `tagsource` (
|
||||||
|
`id` varchar(254) not null primary key,
|
||||||
|
`source_id` varchar(254) not null,
|
||||||
|
`tag_id` varchar(254) not null,
|
||||||
|
unique (`source_id`, `tag_id`),
|
||||||
|
foreign key (`source_id`) references `source`(`sid`),
|
||||||
|
foreign key (`tag_id`) references `tag`(`tid`)
|
||||||
|
);
|
@ -0,0 +1,11 @@
|
|||||||
|
ALTER TABLE "source"
|
||||||
|
ADD COLUMN "file_filter" varchar(254) NULL;
|
||||||
|
|
||||||
|
CREATE TABLE "tagsource" (
|
||||||
|
"id" varchar(254) not null primary key,
|
||||||
|
"source_id" varchar(254) not null,
|
||||||
|
"tag_id" varchar(254) not null,
|
||||||
|
unique ("source_id", "tag_id"),
|
||||||
|
foreign key ("source_id") references "source"("sid"),
|
||||||
|
foreign key ("tag_id") references "tag"("tid")
|
||||||
|
);
|
@ -91,6 +91,9 @@ trait DoobieMeta extends EmilDoobieMeta {
|
|||||||
|
|
||||||
implicit val metaCalEvent: Meta[CalEvent] =
|
implicit val metaCalEvent: Meta[CalEvent] =
|
||||||
Meta[String].timap(CalEvent.unsafe)(_.asString)
|
Meta[String].timap(CalEvent.unsafe)(_.asString)
|
||||||
|
|
||||||
|
implicit val metaGlob: Meta[Glob] =
|
||||||
|
Meta[String].timap(Glob.apply)(_.asString)
|
||||||
}
|
}
|
||||||
|
|
||||||
object DoobieMeta extends DoobieMeta {
|
object DoobieMeta extends DoobieMeta {
|
||||||
|
@ -16,8 +16,13 @@ case class RSource(
|
|||||||
enabled: Boolean,
|
enabled: Boolean,
|
||||||
priority: Priority,
|
priority: Priority,
|
||||||
created: Timestamp,
|
created: Timestamp,
|
||||||
folderId: Option[Ident]
|
folderId: Option[Ident],
|
||||||
) {}
|
fileFilter: Option[Glob]
|
||||||
|
) {
|
||||||
|
|
||||||
|
def fileFilterOrAll: Glob =
|
||||||
|
fileFilter.getOrElse(Glob.all)
|
||||||
|
}
|
||||||
|
|
||||||
object RSource {
|
object RSource {
|
||||||
|
|
||||||
@ -34,9 +39,21 @@ object RSource {
|
|||||||
val priority = Column("priority")
|
val priority = Column("priority")
|
||||||
val created = Column("created")
|
val created = Column("created")
|
||||||
val folder = Column("folder_id")
|
val folder = Column("folder_id")
|
||||||
|
val fileFilter = Column("file_filter")
|
||||||
|
|
||||||
val all =
|
val all =
|
||||||
List(sid, cid, abbrev, description, counter, enabled, priority, created, folder)
|
List(
|
||||||
|
sid,
|
||||||
|
cid,
|
||||||
|
abbrev,
|
||||||
|
description,
|
||||||
|
counter,
|
||||||
|
enabled,
|
||||||
|
priority,
|
||||||
|
created,
|
||||||
|
folder,
|
||||||
|
fileFilter
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
import Columns._
|
import Columns._
|
||||||
@ -45,7 +62,7 @@ object RSource {
|
|||||||
val sql = insertRow(
|
val sql = insertRow(
|
||||||
table,
|
table,
|
||||||
all,
|
all,
|
||||||
fr"${v.sid},${v.cid},${v.abbrev},${v.description},${v.counter},${v.enabled},${v.priority},${v.created},${v.folderId}"
|
fr"${v.sid},${v.cid},${v.abbrev},${v.description},${v.counter},${v.enabled},${v.priority},${v.created},${v.folderId},${v.fileFilter}"
|
||||||
)
|
)
|
||||||
sql.update.run
|
sql.update.run
|
||||||
}
|
}
|
||||||
@ -60,7 +77,8 @@ object RSource {
|
|||||||
description.setTo(v.description),
|
description.setTo(v.description),
|
||||||
enabled.setTo(v.enabled),
|
enabled.setTo(v.enabled),
|
||||||
priority.setTo(v.priority),
|
priority.setTo(v.priority),
|
||||||
folder.setTo(v.folderId)
|
folder.setTo(v.folderId),
|
||||||
|
fileFilter.setTo(v.fileFilter)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
sql.update.run
|
sql.update.run
|
||||||
@ -83,10 +101,11 @@ object RSource {
|
|||||||
sql.query[Int].unique.map(_ > 0)
|
sql.query[Int].unique.map(_ > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
def findEnabled(id: Ident): ConnectionIO[Option[RSource]] = {
|
def findEnabled(id: Ident): ConnectionIO[Option[RSource]] =
|
||||||
val sql = selectSimple(all, table, and(sid.is(id), enabled.is(true)))
|
findEnabledSql(id).query[RSource].option
|
||||||
sql.query[RSource].option
|
|
||||||
}
|
private[records] def findEnabledSql(id: Ident): Fragment =
|
||||||
|
selectSimple(all, table, and(sid.is(id), enabled.is(true)))
|
||||||
|
|
||||||
def findCollective(sourceId: Ident): ConnectionIO[Option[Ident]] =
|
def findCollective(sourceId: Ident): ConnectionIO[Option[Ident]] =
|
||||||
selectSimple(List(cid), table, sid.is(sourceId)).query[Ident].option
|
selectSimple(List(cid), table, sid.is(sourceId)).query[Ident].option
|
||||||
@ -94,10 +113,11 @@ object RSource {
|
|||||||
def findAll(
|
def findAll(
|
||||||
coll: Ident,
|
coll: Ident,
|
||||||
order: Columns.type => Column
|
order: Columns.type => Column
|
||||||
): ConnectionIO[Vector[RSource]] = {
|
): ConnectionIO[Vector[RSource]] =
|
||||||
val sql = selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f)
|
findAllSql(coll, order).query[RSource].to[Vector]
|
||||||
sql.query[RSource].to[Vector]
|
|
||||||
}
|
private[records] def findAllSql(coll: Ident, order: Columns.type => Column): Fragment =
|
||||||
|
selectSimple(all, table, cid.is(coll)) ++ orderBy(order(Columns).f)
|
||||||
|
|
||||||
def delete(sourceId: Ident, coll: Ident): ConnectionIO[Int] =
|
def delete(sourceId: Ident, coll: Ident): ConnectionIO[Int] =
|
||||||
deleteFrom(table, and(sid.is(sourceId), cid.is(coll))).update.run
|
deleteFrom(table, and(sid.is(sourceId), cid.is(coll))).update.run
|
||||||
|
@ -104,6 +104,18 @@ object RTag {
|
|||||||
) ++ orderBy(name.prefix("t").asc)).query[RTag].to[Vector]
|
) ++ orderBy(name.prefix("t").asc)).query[RTag].to[Vector]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def findBySource(source: Ident): ConnectionIO[Vector[RTag]] = {
|
||||||
|
val rcol = all.map(_.prefix("t"))
|
||||||
|
(selectSimple(
|
||||||
|
rcol,
|
||||||
|
table ++ fr"t," ++ RTagSource.table ++ fr"s",
|
||||||
|
and(
|
||||||
|
RTagSource.Columns.sourceId.prefix("s").is(source),
|
||||||
|
RTagSource.Columns.tagId.prefix("s").is(tid.prefix("t"))
|
||||||
|
)
|
||||||
|
) ++ orderBy(name.prefix("t").asc)).query[RTag].to[Vector]
|
||||||
|
}
|
||||||
|
|
||||||
def findAllByNameOrId(
|
def findAllByNameOrId(
|
||||||
nameOrIds: List[String],
|
nameOrIds: List[String],
|
||||||
coll: Ident
|
coll: Ident
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
package docspell.store.records
|
||||||
|
|
||||||
|
import cats.effect.Sync
|
||||||
|
import cats.implicits._
|
||||||
|
|
||||||
|
import docspell.common._
|
||||||
|
import docspell.store.impl.Implicits._
|
||||||
|
import docspell.store.impl._
|
||||||
|
|
||||||
|
import doobie._
|
||||||
|
import doobie.implicits._
|
||||||
|
|
||||||
|
case class RTagSource(id: Ident, sourceId: Ident, tagId: Ident) {}
|
||||||
|
|
||||||
|
object RTagSource {
|
||||||
|
|
||||||
|
val table = fr"tagsource"
|
||||||
|
|
||||||
|
object Columns {
|
||||||
|
val id = Column("id")
|
||||||
|
val sourceId = Column("source_id")
|
||||||
|
val tagId = Column("tag_id")
|
||||||
|
val all = List(id, sourceId, tagId)
|
||||||
|
}
|
||||||
|
import Columns._
|
||||||
|
|
||||||
|
def createNew[F[_]: Sync](source: Ident, tag: Ident): F[RTagSource] =
|
||||||
|
Ident.randomId[F].map(id => RTagSource(id, source, tag))
|
||||||
|
|
||||||
|
def insert(v: RTagSource): ConnectionIO[Int] =
|
||||||
|
insertRow(table, all, fr"${v.id},${v.sourceId},${v.tagId}").update.run
|
||||||
|
|
||||||
|
def deleteSourceTags(source: Ident): ConnectionIO[Int] =
|
||||||
|
deleteFrom(table, sourceId.is(source)).update.run
|
||||||
|
|
||||||
|
def deleteTag(tid: Ident): ConnectionIO[Int] =
|
||||||
|
deleteFrom(table, tagId.is(tid)).update.run
|
||||||
|
|
||||||
|
def findBySource(source: Ident): ConnectionIO[Vector[RTagSource]] =
|
||||||
|
selectSimple(all, table, sourceId.is(source)).query[RTagSource].to[Vector]
|
||||||
|
|
||||||
|
def setAllTags(source: Ident, tags: Seq[Ident]): ConnectionIO[Int] =
|
||||||
|
if (tags.isEmpty) 0.pure[ConnectionIO]
|
||||||
|
else
|
||||||
|
for {
|
||||||
|
entities <- tags.toList.traverse(tagId =>
|
||||||
|
Ident.randomId[ConnectionIO].map(id => RTagSource(id, source, tagId))
|
||||||
|
)
|
||||||
|
n <- insertRows(
|
||||||
|
table,
|
||||||
|
all,
|
||||||
|
entities.map(v => fr"${v.id},${v.sourceId},${v.tagId}")
|
||||||
|
).update.run
|
||||||
|
} yield n
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,87 @@
|
|||||||
|
package docspell.store.records
|
||||||
|
|
||||||
|
import cats.effect.concurrent.Ref
|
||||||
|
import cats.implicits._
|
||||||
|
import fs2.Stream
|
||||||
|
|
||||||
|
import docspell.common._
|
||||||
|
import docspell.store.impl.Implicits._
|
||||||
|
import docspell.store.impl._
|
||||||
|
|
||||||
|
import doobie._
|
||||||
|
import doobie.implicits._
|
||||||
|
|
||||||
|
/** Combines a source record (RSource) and a list of associated tags.
|
||||||
|
*/
|
||||||
|
case class SourceData(source: RSource, tags: Vector[RTag])
|
||||||
|
|
||||||
|
object SourceData {
|
||||||
|
|
||||||
|
def fromSource(s: RSource): SourceData =
|
||||||
|
SourceData(s, Vector.empty)
|
||||||
|
|
||||||
|
def findAll(
|
||||||
|
coll: Ident,
|
||||||
|
order: RSource.Columns.type => Column
|
||||||
|
): Stream[ConnectionIO, SourceData] =
|
||||||
|
findAllWithTags(RSource.findAllSql(coll, order).query[RSource].stream)
|
||||||
|
|
||||||
|
private def findAllWithTags(
|
||||||
|
select: Stream[ConnectionIO, RSource]
|
||||||
|
): Stream[ConnectionIO, SourceData] = {
|
||||||
|
def findTag(
|
||||||
|
cache: Ref[ConnectionIO, Map[Ident, RTag]],
|
||||||
|
tagSource: RTagSource
|
||||||
|
): ConnectionIO[Option[RTag]] =
|
||||||
|
for {
|
||||||
|
cc <- cache.get
|
||||||
|
fromCache = cc.get(tagSource.tagId)
|
||||||
|
orFromDB <-
|
||||||
|
if (fromCache.isDefined) fromCache.pure[ConnectionIO]
|
||||||
|
else RTag.findById(tagSource.tagId)
|
||||||
|
_ <-
|
||||||
|
if (fromCache.isDefined) ().pure[ConnectionIO]
|
||||||
|
else
|
||||||
|
orFromDB match {
|
||||||
|
case Some(t) => cache.update(tmap => tmap.updated(t.tagId, t))
|
||||||
|
case None => ().pure[ConnectionIO]
|
||||||
|
}
|
||||||
|
} yield orFromDB
|
||||||
|
|
||||||
|
for {
|
||||||
|
resolvedTags <- Stream.eval(Ref.of[ConnectionIO, Map[Ident, RTag]](Map.empty))
|
||||||
|
source <- select
|
||||||
|
tagSources <- Stream.eval(RTagSource.findBySource(source.sid))
|
||||||
|
tags <- Stream.eval(tagSources.traverse(ti => findTag(resolvedTags, ti)))
|
||||||
|
} yield SourceData(source, tags.flatten)
|
||||||
|
}
|
||||||
|
|
||||||
|
def findEnabled(id: Ident): ConnectionIO[Option[SourceData]] =
|
||||||
|
findAllWithTags(RSource.findEnabledSql(id).query[RSource].stream).head.compile.last
|
||||||
|
|
||||||
|
def insert(data: RSource, tags: List[String]): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
n0 <- RSource.insert(data)
|
||||||
|
tags <- RTag.findAllByNameOrId(tags, data.cid)
|
||||||
|
n1 <- tags.traverse(tag =>
|
||||||
|
RTagSource.createNew[ConnectionIO](data.sid, tag.tagId).flatMap(RTagSource.insert)
|
||||||
|
)
|
||||||
|
} yield n0 + n1.sum
|
||||||
|
|
||||||
|
def update(data: RSource, tags: List[String]): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
n0 <- RSource.updateNoCounter(data)
|
||||||
|
tags <- RTag.findAllByNameOrId(tags, data.cid)
|
||||||
|
_ <- RTagSource.deleteSourceTags(data.sid)
|
||||||
|
n1 <- tags.traverse(tag =>
|
||||||
|
RTagSource.createNew[ConnectionIO](data.sid, tag.tagId).flatMap(RTagSource.insert)
|
||||||
|
)
|
||||||
|
} yield n0 + n1.sum
|
||||||
|
|
||||||
|
def delete(source: Ident, coll: Ident): ConnectionIO[Int] =
|
||||||
|
for {
|
||||||
|
n0 <- RTagSource.deleteSourceTags(source)
|
||||||
|
n1 <- RSource.delete(source, coll)
|
||||||
|
} yield n0 + n1
|
||||||
|
|
||||||
|
}
|
@ -175,8 +175,9 @@ import Api.Model.ScanMailboxSettings exposing (ScanMailboxSettings)
|
|||||||
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
|
import Api.Model.ScanMailboxSettingsList exposing (ScanMailboxSettingsList)
|
||||||
import Api.Model.SentMails exposing (SentMails)
|
import Api.Model.SentMails exposing (SentMails)
|
||||||
import Api.Model.SimpleMail exposing (SimpleMail)
|
import Api.Model.SimpleMail exposing (SimpleMail)
|
||||||
import Api.Model.Source exposing (Source)
|
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
||||||
import Api.Model.SourceList exposing (SourceList)
|
import Api.Model.SourceList exposing (SourceList)
|
||||||
|
import Api.Model.SourceTagIn exposing (SourceTagIn)
|
||||||
import Api.Model.StringList exposing (StringList)
|
import Api.Model.StringList exposing (StringList)
|
||||||
import Api.Model.Tag exposing (Tag)
|
import Api.Model.Tag exposing (Tag)
|
||||||
import Api.Model.TagCloud exposing (TagCloud)
|
import Api.Model.TagCloud exposing (TagCloud)
|
||||||
@ -1144,17 +1145,22 @@ getSources flags receive =
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
postSource : Flags -> Source -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
postSource : Flags -> SourceAndTags -> (Result Http.Error BasicResult -> msg) -> Cmd msg
|
||||||
postSource flags source receive =
|
postSource flags source receive =
|
||||||
let
|
let
|
||||||
|
st =
|
||||||
|
{ source = source.source
|
||||||
|
, tags = List.map .id source.tags.items
|
||||||
|
}
|
||||||
|
|
||||||
params =
|
params =
|
||||||
{ url = flags.config.baseUrl ++ "/api/v1/sec/source"
|
{ url = flags.config.baseUrl ++ "/api/v1/sec/source"
|
||||||
, account = getAccount flags
|
, account = getAccount flags
|
||||||
, body = Http.jsonBody (Api.Model.Source.encode source)
|
, body = Http.jsonBody (Api.Model.SourceTagIn.encode st)
|
||||||
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
, expect = Http.expectJson receive Api.Model.BasicResult.decoder
|
||||||
}
|
}
|
||||||
in
|
in
|
||||||
if source.id == "" then
|
if source.source.id == "" then
|
||||||
Http2.authPost params
|
Http2.authPost params
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -12,7 +12,9 @@ import Api
|
|||||||
import Api.Model.FolderItem exposing (FolderItem)
|
import Api.Model.FolderItem exposing (FolderItem)
|
||||||
import Api.Model.FolderList exposing (FolderList)
|
import Api.Model.FolderList exposing (FolderList)
|
||||||
import Api.Model.IdName exposing (IdName)
|
import Api.Model.IdName exposing (IdName)
|
||||||
import Api.Model.Source exposing (Source)
|
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
||||||
|
import Api.Model.Tag exposing (Tag)
|
||||||
|
import Api.Model.TagList exposing (TagList)
|
||||||
import Comp.Dropdown exposing (isDropdownChangeMsg)
|
import Comp.Dropdown exposing (isDropdownChangeMsg)
|
||||||
import Comp.FixedDropdown
|
import Comp.FixedDropdown
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
@ -24,10 +26,13 @@ import Html.Events exposing (onCheck, onInput)
|
|||||||
import Http
|
import Http
|
||||||
import Markdown
|
import Markdown
|
||||||
import Util.Folder exposing (mkFolderOption)
|
import Util.Folder exposing (mkFolderOption)
|
||||||
|
import Util.Maybe
|
||||||
|
import Util.Tag
|
||||||
|
import Util.Update
|
||||||
|
|
||||||
|
|
||||||
type alias Model =
|
type alias Model =
|
||||||
{ source : Source
|
{ source : SourceAndTags
|
||||||
, abbrev : String
|
, abbrev : String
|
||||||
, description : Maybe String
|
, description : Maybe String
|
||||||
, priorityModel : Comp.FixedDropdown.Model Priority
|
, priorityModel : Comp.FixedDropdown.Model Priority
|
||||||
@ -36,12 +41,14 @@ type alias Model =
|
|||||||
, folderModel : Comp.Dropdown.Model IdName
|
, folderModel : Comp.Dropdown.Model IdName
|
||||||
, allFolders : List FolderItem
|
, allFolders : List FolderItem
|
||||||
, folderId : Maybe String
|
, folderId : Maybe String
|
||||||
|
, tagModel : Comp.Dropdown.Model Tag
|
||||||
|
, fileFilter : Maybe String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
emptyModel : Model
|
emptyModel : Model
|
||||||
emptyModel =
|
emptyModel =
|
||||||
{ source = Api.Model.Source.empty
|
{ source = Api.Model.SourceAndTags.empty
|
||||||
, abbrev = ""
|
, abbrev = ""
|
||||||
, description = Nothing
|
, description = Nothing
|
||||||
, priorityModel =
|
, priorityModel =
|
||||||
@ -57,13 +64,18 @@ emptyModel =
|
|||||||
}
|
}
|
||||||
, allFolders = []
|
, allFolders = []
|
||||||
, folderId = Nothing
|
, folderId = Nothing
|
||||||
|
, tagModel = Util.Tag.makeDropdownModel
|
||||||
|
, fileFilter = Nothing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
init : Flags -> ( Model, Cmd Msg )
|
init : Flags -> ( Model, Cmd Msg )
|
||||||
init flags =
|
init flags =
|
||||||
( emptyModel
|
( emptyModel
|
||||||
, Api.getFolders flags "" False GetFolderResp
|
, Cmd.batch
|
||||||
|
[ Api.getFolders flags "" False GetFolderResp
|
||||||
|
, Api.getTags flags "" GetTagResp
|
||||||
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -72,29 +84,42 @@ isValid model =
|
|||||||
model.abbrev /= ""
|
model.abbrev /= ""
|
||||||
|
|
||||||
|
|
||||||
getSource : Model -> Source
|
getSource : Model -> SourceAndTags
|
||||||
getSource model =
|
getSource model =
|
||||||
let
|
let
|
||||||
s =
|
st =
|
||||||
model.source
|
model.source
|
||||||
|
|
||||||
|
s =
|
||||||
|
st.source
|
||||||
|
|
||||||
|
tags =
|
||||||
|
Comp.Dropdown.getSelected model.tagModel
|
||||||
|
|
||||||
|
n =
|
||||||
|
{ s
|
||||||
|
| abbrev = model.abbrev
|
||||||
|
, description = model.description
|
||||||
|
, enabled = model.enabled
|
||||||
|
, priority = Data.Priority.toName model.priority
|
||||||
|
, folder = model.folderId
|
||||||
|
, fileFilter = model.fileFilter
|
||||||
|
}
|
||||||
in
|
in
|
||||||
{ s
|
{ st | source = n, tags = TagList (List.length tags) tags }
|
||||||
| abbrev = model.abbrev
|
|
||||||
, description = model.description
|
|
||||||
, enabled = model.enabled
|
|
||||||
, priority = Data.Priority.toName model.priority
|
|
||||||
, folder = model.folderId
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= SetAbbrev String
|
= SetAbbrev String
|
||||||
| SetSource Source
|
| SetSource SourceAndTags
|
||||||
| SetDescr String
|
| SetDescr String
|
||||||
| ToggleEnabled
|
| ToggleEnabled
|
||||||
| PrioDropdownMsg (Comp.FixedDropdown.Msg Priority)
|
| PrioDropdownMsg (Comp.FixedDropdown.Msg Priority)
|
||||||
| GetFolderResp (Result Http.Error FolderList)
|
| GetFolderResp (Result Http.Error FolderList)
|
||||||
| FolderDropdownMsg (Comp.Dropdown.Msg IdName)
|
| FolderDropdownMsg (Comp.Dropdown.Msg IdName)
|
||||||
|
| GetTagResp (Result Http.Error TagList)
|
||||||
|
| TagDropdownMsg (Comp.Dropdown.Msg Tag)
|
||||||
|
| SetFileFilter String
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -106,29 +131,34 @@ update flags msg model =
|
|||||||
case msg of
|
case msg of
|
||||||
SetSource t ->
|
SetSource t ->
|
||||||
let
|
let
|
||||||
post =
|
stpost =
|
||||||
model.source
|
model.source
|
||||||
|
|
||||||
|
post =
|
||||||
|
stpost.source
|
||||||
|
|
||||||
np =
|
np =
|
||||||
{ post
|
{ post
|
||||||
| id = t.id
|
| id = t.source.id
|
||||||
, abbrev = t.abbrev
|
, abbrev = t.source.abbrev
|
||||||
, description = t.description
|
, description = t.source.description
|
||||||
, priority = t.priority
|
, priority = t.source.priority
|
||||||
, enabled = t.enabled
|
, enabled = t.source.enabled
|
||||||
, folder = t.folder
|
, folder = t.source.folder
|
||||||
|
, fileFilter = t.source.fileFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
newModel =
|
newModel =
|
||||||
{ model
|
{ model
|
||||||
| source = np
|
| source = { stpost | source = np }
|
||||||
, abbrev = t.abbrev
|
, abbrev = t.source.abbrev
|
||||||
, description = t.description
|
, description = t.source.description
|
||||||
, priority =
|
, priority =
|
||||||
Data.Priority.fromString t.priority
|
Data.Priority.fromString t.source.priority
|
||||||
|> Maybe.withDefault Data.Priority.Low
|
|> Maybe.withDefault Data.Priority.Low
|
||||||
, enabled = t.enabled
|
, enabled = t.source.enabled
|
||||||
, folderId = t.folder
|
, folderId = t.source.folder
|
||||||
|
, fileFilter = t.source.fileFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
mkIdName id =
|
mkIdName id =
|
||||||
@ -143,14 +173,21 @@ update flags msg model =
|
|||||||
model.allFolders
|
model.allFolders
|
||||||
|
|
||||||
sel =
|
sel =
|
||||||
case Maybe.map mkIdName t.folder of
|
case Maybe.map mkIdName t.source.folder of
|
||||||
Just idref ->
|
Just idref ->
|
||||||
idref
|
idref
|
||||||
|
|
||||||
Nothing ->
|
Nothing ->
|
||||||
[]
|
[]
|
||||||
|
|
||||||
|
tags =
|
||||||
|
Comp.Dropdown.SetSelection t.tags.items
|
||||||
in
|
in
|
||||||
update flags (FolderDropdownMsg (Comp.Dropdown.SetSelection sel)) newModel
|
Util.Update.andThen1
|
||||||
|
[ update flags (FolderDropdownMsg (Comp.Dropdown.SetSelection sel))
|
||||||
|
, update flags (TagDropdownMsg tags)
|
||||||
|
]
|
||||||
|
newModel
|
||||||
|
|
||||||
ToggleEnabled ->
|
ToggleEnabled ->
|
||||||
( { model | enabled = not model.enabled }, Cmd.none )
|
( { model | enabled = not model.enabled }, Cmd.none )
|
||||||
@ -159,14 +196,7 @@ update flags msg model =
|
|||||||
( { model | abbrev = n }, Cmd.none )
|
( { model | abbrev = n }, Cmd.none )
|
||||||
|
|
||||||
SetDescr d ->
|
SetDescr d ->
|
||||||
( { model
|
( { model | description = Util.Maybe.fromString d }
|
||||||
| description =
|
|
||||||
if d /= "" then
|
|
||||||
Just d
|
|
||||||
|
|
||||||
else
|
|
||||||
Nothing
|
|
||||||
}
|
|
||||||
, Cmd.none
|
, Cmd.none
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -226,6 +256,31 @@ update flags msg model =
|
|||||||
in
|
in
|
||||||
( model_, Cmd.map FolderDropdownMsg c2 )
|
( model_, Cmd.map FolderDropdownMsg c2 )
|
||||||
|
|
||||||
|
GetTagResp (Ok list) ->
|
||||||
|
let
|
||||||
|
opts =
|
||||||
|
Comp.Dropdown.SetOptions list.items
|
||||||
|
in
|
||||||
|
update flags (TagDropdownMsg opts) model
|
||||||
|
|
||||||
|
GetTagResp (Err _) ->
|
||||||
|
( model, Cmd.none )
|
||||||
|
|
||||||
|
TagDropdownMsg lm ->
|
||||||
|
let
|
||||||
|
( m2, c2 ) =
|
||||||
|
Comp.Dropdown.update lm model.tagModel
|
||||||
|
|
||||||
|
newModel =
|
||||||
|
{ model | tagModel = m2 }
|
||||||
|
in
|
||||||
|
( newModel, Cmd.map TagDropdownMsg c2 )
|
||||||
|
|
||||||
|
SetFileFilter d ->
|
||||||
|
( { model | fileFilter = Util.Maybe.fromString d }
|
||||||
|
, Cmd.none
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
--- View
|
--- View
|
||||||
@ -260,6 +315,7 @@ view flags settings model =
|
|||||||
, textarea
|
, textarea
|
||||||
[ onInput SetDescr
|
[ onInput SetDescr
|
||||||
, model.description |> Maybe.withDefault "" |> value
|
, model.description |> Maybe.withDefault "" |> value
|
||||||
|
, rows 3
|
||||||
]
|
]
|
||||||
[]
|
[]
|
||||||
]
|
]
|
||||||
@ -281,12 +337,26 @@ view flags settings model =
|
|||||||
(Just priorityItem)
|
(Just priorityItem)
|
||||||
model.priorityModel
|
model.priorityModel
|
||||||
)
|
)
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "The priority used by the scheduler when processing uploaded files."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div [ class "ui dividing header" ]
|
||||||
|
[ text "Metadata"
|
||||||
|
]
|
||||||
|
, div [ class "ui message" ]
|
||||||
|
[ text "Metadata specified here is automatically attached to each item uploaded "
|
||||||
|
, text "through this source, unless it is overriden in the upload request meta data. "
|
||||||
|
, text "Tags from the request are added to those defined here."
|
||||||
]
|
]
|
||||||
, div [ class "field" ]
|
, div [ class "field" ]
|
||||||
[ label []
|
[ label []
|
||||||
[ text "Folder"
|
[ text "Folder"
|
||||||
]
|
]
|
||||||
, Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
|
, Html.map FolderDropdownMsg (Comp.Dropdown.view settings model.folderModel)
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Choose a folder to automatically put items into."
|
||||||
|
]
|
||||||
, div
|
, div
|
||||||
[ classList
|
[ classList
|
||||||
[ ( "ui warning message", True )
|
[ ( "ui warning message", True )
|
||||||
@ -301,6 +371,33 @@ disappear then.
|
|||||||
"""
|
"""
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
, div [ class "field" ]
|
||||||
|
[ label [] [ text "Tags" ]
|
||||||
|
, Html.map TagDropdownMsg (Comp.Dropdown.view settings model.tagModel)
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Choose tags that should be applied to items."
|
||||||
|
]
|
||||||
|
]
|
||||||
|
, div
|
||||||
|
[ class "field"
|
||||||
|
]
|
||||||
|
[ label [] [ text "File Filter" ]
|
||||||
|
, input
|
||||||
|
[ type_ "text"
|
||||||
|
, onInput SetFileFilter
|
||||||
|
, placeholder "File Filter"
|
||||||
|
, model.fileFilter
|
||||||
|
|> Maybe.withDefault ""
|
||||||
|
|> value
|
||||||
|
]
|
||||||
|
[]
|
||||||
|
, div [ class "small-info" ]
|
||||||
|
[ text "Specify a file glob to filter files when uploading archives (e.g. for email and zip). For example, to only extract pdf files: "
|
||||||
|
, code []
|
||||||
|
[ text "*.pdf"
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,8 +8,9 @@ module Comp.SourceManage exposing
|
|||||||
|
|
||||||
import Api
|
import Api
|
||||||
import Api.Model.BasicResult exposing (BasicResult)
|
import Api.Model.BasicResult exposing (BasicResult)
|
||||||
import Api.Model.Source exposing (Source)
|
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
||||||
import Api.Model.SourceList exposing (SourceList)
|
import Api.Model.SourceList exposing (SourceList)
|
||||||
|
import Api.Model.SourceTagIn exposing (SourceTagIn)
|
||||||
import Comp.SourceForm
|
import Comp.SourceForm
|
||||||
import Comp.SourceTable exposing (SelectMode(..))
|
import Comp.SourceTable exposing (SelectMode(..))
|
||||||
import Comp.YesNoDimmer
|
import Comp.YesNoDimmer
|
||||||
@ -31,7 +32,7 @@ type alias Model =
|
|||||||
, formError : Maybe String
|
, formError : Maybe String
|
||||||
, loading : Bool
|
, loading : Bool
|
||||||
, deleteConfirm : Comp.YesNoDimmer.Model
|
, deleteConfirm : Comp.YesNoDimmer.Model
|
||||||
, sources : List Source
|
, sources : List SourceAndTags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -145,7 +146,7 @@ update flags msg model =
|
|||||||
InitNewSource ->
|
InitNewSource ->
|
||||||
let
|
let
|
||||||
source =
|
source =
|
||||||
Api.Model.Source.empty
|
Api.Model.SourceAndTags.empty
|
||||||
|
|
||||||
nm =
|
nm =
|
||||||
{ model | viewMode = Edit source, formError = Nothing }
|
{ model | viewMode = Edit source, formError = Nothing }
|
||||||
@ -196,7 +197,7 @@ update flags msg model =
|
|||||||
|
|
||||||
cmd =
|
cmd =
|
||||||
if confirmed then
|
if confirmed then
|
||||||
Api.deleteSource flags src.id SubmitResp
|
Api.deleteSource flags src.source.id SubmitResp
|
||||||
|
|
||||||
else
|
else
|
||||||
Cmd.none
|
Cmd.none
|
||||||
@ -248,22 +249,22 @@ viewTable model =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
viewLinks : Flags -> UiSettings -> Source -> Html Msg
|
viewLinks : Flags -> UiSettings -> SourceAndTags -> Html Msg
|
||||||
viewLinks flags _ source =
|
viewLinks flags _ source =
|
||||||
let
|
let
|
||||||
appUrl =
|
appUrl =
|
||||||
flags.config.baseUrl ++ "/app/upload/" ++ source.id
|
flags.config.baseUrl ++ "/app/upload/" ++ source.source.id
|
||||||
|
|
||||||
apiUrl =
|
apiUrl =
|
||||||
flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ source.id
|
flags.config.baseUrl ++ "/api/v1/open/upload/item/" ++ source.source.id
|
||||||
in
|
in
|
||||||
div
|
div
|
||||||
[]
|
[]
|
||||||
[ h3 [ class "ui dividing header" ]
|
[ h3 [ class "ui dividing header" ]
|
||||||
[ text "Public Uploads: "
|
[ text "Public Uploads: "
|
||||||
, text source.abbrev
|
, text source.source.abbrev
|
||||||
, div [ class "sub header" ]
|
, div [ class "sub header" ]
|
||||||
[ text source.id
|
[ text source.source.id
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, p []
|
, p []
|
||||||
@ -273,7 +274,7 @@ viewLinks flags _ source =
|
|||||||
]
|
]
|
||||||
, p []
|
, p []
|
||||||
[ text "There have been "
|
[ text "There have been "
|
||||||
, String.fromInt source.counter |> text
|
, String.fromInt source.source.counter |> text
|
||||||
, text " items created through this source."
|
, text " items created through this source."
|
||||||
]
|
]
|
||||||
, h4 [ class "ui header" ]
|
, h4 [ class "ui header" ]
|
||||||
@ -358,7 +359,7 @@ viewForm : Flags -> UiSettings -> Model -> List (Html Msg)
|
|||||||
viewForm flags settings model =
|
viewForm flags settings model =
|
||||||
let
|
let
|
||||||
newSource =
|
newSource =
|
||||||
model.formModel.source.id == ""
|
model.formModel.source.source.id == ""
|
||||||
in
|
in
|
||||||
[ if newSource then
|
[ if newSource then
|
||||||
h3 [ class "ui top attached header" ]
|
h3 [ class "ui top attached header" ]
|
||||||
@ -367,10 +368,10 @@ viewForm flags settings model =
|
|||||||
|
|
||||||
else
|
else
|
||||||
h3 [ class "ui top attached header" ]
|
h3 [ class "ui top attached header" ]
|
||||||
[ text ("Edit: " ++ model.formModel.source.abbrev)
|
[ text ("Edit: " ++ model.formModel.source.source.abbrev)
|
||||||
, div [ class "sub header" ]
|
, div [ class "sub header" ]
|
||||||
[ text "Id: "
|
[ text "Id: "
|
||||||
, text model.formModel.source.id
|
, text model.formModel.source.source.id
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
, Html.form [ class "ui attached segment", onSubmit Submit ]
|
, Html.form [ class "ui attached segment", onSubmit Submit ]
|
||||||
|
@ -6,7 +6,7 @@ module Comp.SourceTable exposing
|
|||||||
, view
|
, view
|
||||||
)
|
)
|
||||||
|
|
||||||
import Api.Model.Source exposing (Source)
|
import Api.Model.SourceAndTags exposing (SourceAndTags)
|
||||||
import Data.Flags exposing (Flags)
|
import Data.Flags exposing (Flags)
|
||||||
import Data.Priority
|
import Data.Priority
|
||||||
import Html exposing (..)
|
import Html exposing (..)
|
||||||
@ -15,8 +15,8 @@ import Html.Events exposing (onClick)
|
|||||||
|
|
||||||
|
|
||||||
type SelectMode
|
type SelectMode
|
||||||
= Edit Source
|
= Edit SourceAndTags
|
||||||
| Display Source
|
| Display SourceAndTags
|
||||||
| None
|
| None
|
||||||
|
|
||||||
|
|
||||||
@ -34,8 +34,8 @@ isEdit m =
|
|||||||
|
|
||||||
|
|
||||||
type Msg
|
type Msg
|
||||||
= Select Source
|
= Select SourceAndTags
|
||||||
| Show Source
|
| Show SourceAndTags
|
||||||
|
|
||||||
|
|
||||||
update : Flags -> Msg -> ( Cmd Msg, SelectMode )
|
update : Flags -> Msg -> ( Cmd Msg, SelectMode )
|
||||||
@ -48,7 +48,7 @@ update _ msg =
|
|||||||
( Cmd.none, Display source )
|
( Cmd.none, Display source )
|
||||||
|
|
||||||
|
|
||||||
view : List Source -> Html Msg
|
view : List SourceAndTags -> Html Msg
|
||||||
view sources =
|
view sources =
|
||||||
table [ class "ui table" ]
|
table [ class "ui table" ]
|
||||||
[ thead []
|
[ thead []
|
||||||
@ -66,7 +66,7 @@ view sources =
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
renderSourceLine : Source -> Html Msg
|
renderSourceLine : SourceAndTags -> Html Msg
|
||||||
renderSourceLine source =
|
renderSourceLine source =
|
||||||
tr
|
tr
|
||||||
[]
|
[]
|
||||||
@ -82,10 +82,10 @@ renderSourceLine source =
|
|||||||
, a
|
, a
|
||||||
[ classList
|
[ classList
|
||||||
[ ( "ui basic tiny primary button", True )
|
[ ( "ui basic tiny primary button", True )
|
||||||
, ( "disabled", not source.enabled )
|
, ( "disabled", not source.source.enabled )
|
||||||
]
|
]
|
||||||
, href "#"
|
, href "#"
|
||||||
, disabled (not source.enabled)
|
, disabled (not source.source.enabled)
|
||||||
, onClick (Show source)
|
, onClick (Show source)
|
||||||
]
|
]
|
||||||
[ i [ class "eye icon" ] []
|
[ i [ class "eye icon" ] []
|
||||||
@ -93,25 +93,25 @@ renderSourceLine source =
|
|||||||
]
|
]
|
||||||
]
|
]
|
||||||
, td [ class "collapsing" ]
|
, td [ class "collapsing" ]
|
||||||
[ text source.abbrev
|
[ text source.source.abbrev
|
||||||
]
|
]
|
||||||
, td [ class "collapsing" ]
|
, td [ class "collapsing" ]
|
||||||
[ if source.enabled then
|
[ if source.source.enabled then
|
||||||
i [ class "check square outline icon" ] []
|
i [ class "check square outline icon" ] []
|
||||||
|
|
||||||
else
|
else
|
||||||
i [ class "minus square outline icon" ] []
|
i [ class "minus square outline icon" ] []
|
||||||
]
|
]
|
||||||
, td [ class "collapsing" ]
|
, td [ class "collapsing" ]
|
||||||
[ source.counter |> String.fromInt |> text
|
[ source.source.counter |> String.fromInt |> text
|
||||||
]
|
]
|
||||||
, td [ class "collapsing" ]
|
, td [ class "collapsing" ]
|
||||||
[ Data.Priority.fromString source.priority
|
[ Data.Priority.fromString source.source.priority
|
||||||
|> Maybe.map Data.Priority.toName
|
|> Maybe.map Data.Priority.toName
|
||||||
|> Maybe.withDefault source.priority
|
|> Maybe.withDefault source.source.priority
|
||||||
|> text
|
|> text
|
||||||
]
|
]
|
||||||
, td []
|
, td []
|
||||||
[ text source.id
|
[ text source.source.id
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user