All Downloads are FREE. Search and download functionalities are using the official Maven repository.

lucuma.core.model.UserInvitation.scala Maven / Gradle / Ivy

There is a newer version: 0.108.0
Show newest version
// Copyright (c) 2016-2023 Association of Universities for Research in Astronomy, Inc. (AURA)
// For license information see LICENSE or https://opensource.org/licenses/BSD-3-Clause

package lucuma.core.model

import cats.Order
import cats.syntax.all.*
import eu.timepit.refined.cats.*
import eu.timepit.refined.numeric.Positive
import eu.timepit.refined.types.numeric.PosLong
import io.circe.Decoder
import io.circe.DecodingFailure
import io.circe.Encoder
import io.circe.Json
import lucuma.core.util.RefinedNewType
import monocle.Prism

import scala.util.matching.Regex

/**
 * An invitation consists of an id (a positive Long) and a cleartext body (a 96-char lowercase hex
 * string). Invitations have a canonical string representation `id.body` where `id` is in lowercase
 * hex, for example:
 * {{{
 * 10d.3b9e2adc5bffdac72f487a2760061bcfebc44037b69f2085c9a1ba10aa5d2d338421fc0d79f45cfd07666617ac4e2c89
 * }}}
 */
case class UserInvitation(id: UserInvitation.Id, body: String)

object UserInvitation {

  object Id extends RefinedNewType[Long, Positive] {

    /** Id from hex string. */
    val fromString: Prism[String, Id] =
      Prism[String, Id] { sid =>
        Either.catchOnly[NumberFormatException](java.lang.Long.parseLong(sid, 16)).flatMap { n =>
          PosLong.from(n).map(Id.apply)
        } .toOption
      } { id =>
        id.value.value.toHexString
      }

    given Encoder[Id] = id => Json.fromString(Id.fromString.reverseGet(id))
    given Decoder[Id] = hc => hc.as[String].flatMap: s =>
      Id.fromString.getOption(s).toRight(DecodingFailure("Invalid invitation id: $s", hc.history))

  }
  type Id = Id.Type

  extension(invitation: UserInvitation)
    def token: String = fromString.reverseGet(invitation)

  private val R: Regex =
    raw"^([0-9a-f]{3,})\.([0-9a-f]{96})$$".r

  val fromString: Prism[String, UserInvitation] =
    Prism[String, UserInvitation] {
      case R(sid, body) => Id.fromString.getOption(sid).map(UserInvitation(_, body))
      case _            => None
     } { k =>
      s"${k.id.value.value.toHexString}.${k.body}"
    }

  given OrderUserInvitation: Order[UserInvitation] =
    Order.by(k => (k.id, k.body))

  given OrderingUserInvitation: Ordering[UserInvitation] =
    OrderUserInvitation.toOrdering

  given Encoder[UserInvitation] =
    Encoder.encodeString.contramap(fromString.reverseGet)

  given Decoder[UserInvitation] =
    Decoder.decodeString.emap(s => fromString.getOption(s).toRight(s"Invalid user invitation: $s"))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy