custom field rest endpoints
- Dominant language
- Elm
- Stars
- 2.3k
- Forks
- 184
- Avg merge
- 8h 2m
- Merged PRs (30d)
- 4
Description
Hi
I have a question about API for custom fields, API doesn't return anything and I can't understand why
There is a scala-cli script (`project.scala`):
```scala
//> using scala 3.6.4
//> using toolkit typelevel:0.1.29
//> using dep org.typelevel::log4cats-core:2.7.0
//> using dep org.typelevel::log4cats-slf4j:2.7.0
//> using dep ch.qos.logback:logback-classic:1.5.18
import cats.effect.kernel.Async
import cats.effect.{ExitCode, IO}
import cats.syntax.all.*
import com.monovore.decline.Opts
import com.monovore.decline.effect.CommandIOApp
import io.circe.syntax.*
import io.circe.{Decoder, Encoder, Json, JsonObject}
import org.http4s.circe.*
import org.http4s.circe.CirceEntityCodec.*
import org.http4s.client.Client
import org.http4s.client.middleware.Logger as LoggerMiddleware
import org.http4s.ember.client.EmberClientBuilder
import org.http4s.headers.Accept
import org.http4s.{
EntityEncoder,
Header,
Headers,
MediaType,
Method,
Request,
Uri
}
import org.typelevel.ci.CIString
import org.typelevel.log4cats.slf4j.Slf4jFactory
import org.typelevel.log4cats.{Logger, LoggerFactory}
trait Auth[F[_]]:
def login(host: String, user: String, password: String): F[String]
object Auth:
def make[F[_]: Logger: Async](client: Client[F]): Auth[F] = new Auth[F]:
override def login(
host: String,
user: String,
password: String
): F[String] =
val body = JsonObject(
"account" -> user.asJson,
"password" -> password.asJson,
"rememberMe" -> true.asJson
).asJson
val req = Request[F](
method = Method.POST,
uri = Uri
.unsafeFromString(s"https://$host")
.withPath("/api/v1/open/auth/login"),
headers = Headers(Accept(MediaType.application.json))
).withEntity(body)
client
.expect[Json](req)(jsonOf[F, Json])
.flatTap: json =>
Logger[F].info(s"got response $json")
.flatMap: json =>
Async[F].fromEither(
json.hcursor.downField("token").as[String]
)
trait RestClient[F[_]]:
def get[A: Decoder](path: String): F[A]
def post[A: Decoder, B: Encoder: Decoder](path: String, body: B)(using
EntityEncoder[F, B]
): F[A]
object RestClient:
def make[F[_]: Logger: Async](
client: Client[F],
token: String,
host: String
): RestClient[F] = new RestClient[F]:
override def get[A: Decoder](path: String): F[A] =
client.expect(mkRequest(path, Method.GET))(jsonOf[F, A])
override def post[A: Decoder, B: Encoder: Decoder](path: String, body: B)(
using EntityEncoder[F, B]
): F[A] =
client.expect(mkRequest(path, Method.POST).withEntity(body.asJson))(
jsonOf[F, A]
)
private def mkRequest(path: String, method: Method): Request[F] =
Request[F](
method = method,
uri = Uri
.unsafeFromString(s"https://$host")
.withPath(path),
headers = Headers(
Header.Raw(CIString("X-Docspell-Auth"), token),
Accept(MediaType.application.json)
)
)
object MainApp
extends CommandIOApp(
name = "customfields",
header = "test customfields api",
helpFlag = true,
version = "0.0.0"
):
override def main: Opts[IO[ExitCode]] =
given LoggerFactory[IO] = Slf4jFactory.create[IO]
given Logger[IO] = LoggerFactory[IO].getLogger
val hostOpt =
Opts.option[String]("host", help = "Docspell host")
val userOpt =
Opts.option[String]("user", help = "Docspell username")
val passwordOpt =
Opts.option[String]("password", help = "Docspell password")
(hostOpt, userOpt, passwordOpt).mapN:
case (host, user, password) =>
EmberClientBuilder
.default[IO]
.build
.map: client =>
LoggerMiddleware[IO](
logHeaders = true,
logBody = true,
logAction = Some((msg: String) => Logger[IO].info(msg))
)(client)
.use: client =>
val auth = Auth.make[IO](client = client)
for
token <- auth.login(host = host, user = user, password = password)
rest = RestClient
.make[IO](token = token, host = host, client = client)
_ <- rest.post[Json, Json](
"/api/v1/sec/customfield",
JsonObject(
"name" -> Json.fromString("asn"),
"ftype" -> Json.fromString("text"),
"label" -> Json.fromString("ASN")
).asJson
)
_ <- rest.get[Json]("/api/v1/sec/customfield?q=asn")
yield ExitCode.Success
```
When I run it:
```sh
scala run project.scala -- --host myhost.com --user mycollective/myuser --password mypassword
```
I receive an empty collection for custom fields `"{"items":[]}"`:
```
11:44:29.405 [io-compute-3] INFO .MainApp -- HTTP/1.1 POST https://myhost.com/api/v1/open/auth/login Headers(Content-Length: 56, Accept: application/json, Content-Type: application/json, Accept: application/json) body="{"account":"mycolletive/myuser","password":"mypassword","rememberMe":true}"
11:44:29.534 [io-compute-3] INFO .MainApp -- HTTP/1.1 200 OK Headers(Server: nginx, Date: Wed, 07 May 2025 09:44:29 GMT, Content-Type: application/json, Content-Length: 294, Connection: keep-alive, Keep-Alive: timeout=20, Set-Cookie: , Set-Cookie: ) body="{"collective":"mycollective","user":"myuser","success":true,"message":"Login successful","token":"mytoken","validMs":300000,"requireSecondFactor":false}"
11:44:29.535 [io-compute-3] INFO .MainApp -- got response {
"collective" : "mycollective",
"user" : "myuser",
"success" : true,
"message" : "Login successful",
"token" : "mytoken",
"validMs" : 300000,
"requireSecondFactor" : false
}
11:44:29.541 [io-compute-6] INFO .MainApp -- HTTP/1.1 POST https://myhost.com/api/v1/sec/customfield Headers(Content-Length: 43, X-Docspell-Auth: mytoken, Accept: application/json, Content-Type: application/json, Accept: application/json) body="{"name":"asn","ftype":"text","label":"ASN"}"
11:44:29.573 [io-compute-5] INFO .MainApp -- HTTP/1.1 200 OK Headers(Server: nginx, Date: Wed, 07 May 2025 09:44:29 GMT, Content-Type: application/json, Content-Length: 47, Connection: keep-alive, Keep-Alive: timeout=20) body="{"success":true,"message":"New field created."}"
11:44:29.577 [io-compute-6] INFO .MainApp -- HTTP/1.1 GET https://myhost.com/api/v1/sec/customfield?q=asn Headers(X-Docspell-Auth: mytoken, Accept application/json, Accept: application/json) body=""
11:44:29.598 [io-compute-2] INFO .MainApp -- HTTP/1.1 200 OK Headers(Server: nginx, Date: Wed, 07 May 2025 09:44:29 GMT, Content-Type: application/json, Content-Length: 12, Connection: keep-alive, Keep-Alive: timeout=20) body="{"items":[]}"
```
Contributor guide
Research direction
Start with the reproduction in project.scala and run the Scala CLI script against the documented custom-field endpoints. Compare the successful POST response with the subsequent GET response and trace the custom-field API implementation to determine whether the query, scope, or response behavior is responsible. Done means the cause and expected API behavior are established.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- scala
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100