Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE-1254] Move domain APIs (contacts, calendar, firebase, etc) from jmap-extensions to jmap-extensions-api #1339

Merged
merged 16 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@
import com.linagora.tmail.blob.guice.BlobStoreCacheModulesChooser;
import com.linagora.tmail.blob.guice.BlobStoreConfiguration;
import com.linagora.tmail.blob.guice.BlobStoreModulesChooser;
import com.linagora.tmail.contact.RabbitMQEmailAddressContactModule;
import com.linagora.tmail.encrypted.ClearEmailContentFactory;
import com.linagora.tmail.encrypted.EncryptedMailboxManager;
import com.linagora.tmail.encrypted.KeystoreManager;
Expand All @@ -145,6 +144,7 @@
import com.linagora.tmail.imap.TMailIMAPModule;
import com.linagora.tmail.james.jmap.ContactSupportCapabilitiesModule;
import com.linagora.tmail.james.jmap.TMailJMAPModule;
import com.linagora.tmail.james.jmap.contact.RabbitMQEmailAddressContactModule;
import com.linagora.tmail.james.jmap.firebase.CassandraFirebaseSubscriptionRepositoryModule;
import com.linagora.tmail.james.jmap.firebase.FirebaseCommonModule;
import com.linagora.tmail.james.jmap.firebase.FirebaseModuleChooserConfiguration;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.linagora.tmail.contact;
package com.linagora.tmail.james.jmap.contact;

import jakarta.inject.Named;
import jakarta.inject.Provider;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import com.google.common.collect.ImmutableList
import com.linagora.tmail.james.common.LabelChangesMethodContract.firebasePushClient
import com.linagora.tmail.james.common.probe.JmapGuiceLabelProbe
import com.linagora.tmail.james.jmap.firebase.{FirebasePushClient, FirebasePushRequest}
import com.linagora.tmail.james.jmap.label.{LabelChange, LabelTypeName}
import com.linagora.tmail.james.jmap.model.LabelId
import com.linagora.tmail.james.jmap.label.{LabelTypeName}
import com.linagora.tmail.james.jmap.model.{LabelChange, LabelId}
import io.netty.handler.codec.http.HttpHeaderNames.ACCEPT
import io.restassured.RestAssured.{`given`, requestSpecification}
import io.restassured.http.ContentType.JSON
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@
import org.apache.james.core.Username;
import org.apache.james.utils.GuiceProbe;

import com.linagora.tmail.james.jmap.label.LabelChange;
import com.linagora.tmail.james.jmap.label.LabelChangeRepository;
import com.linagora.tmail.james.jmap.label.LabelRepository;
import com.linagora.tmail.james.jmap.model.Label;
import com.linagora.tmail.james.jmap.model.LabelChange;
import com.linagora.tmail.james.jmap.model.LabelCreationRequest;

import reactor.core.publisher.Flux;
Expand Down
67 changes: 67 additions & 0 deletions tmail-backend/jmap/extensions-api/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.linagora.tmail</groupId>
<artifactId>tmail-backend</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>

<artifactId>jmap-extensions-api</artifactId>
<name>Twake Mail :: JMAP :: Extensions :: API</name>

<properties>
<uuid-creator.version>5.3.7</uuid-creator.version>
</properties>

<dependencies>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-data-jmap</artifactId>
</dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-jmap-rfc-8621</artifactId>
</dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>james-server-testing</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>${james.groupId}</groupId>
<artifactId>testing-base</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.f4b6a3</groupId>
<artifactId>uuid-creator</artifactId>
<version>${uuid-creator.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>io.github.evis</groupId>
<artifactId>scalafix-maven-plugin_2.13</artifactId>
<configuration>
<config>${project.parent.parent.basedir}/.scalafix.conf</config>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.linagora.tmail.james.jmap

import eu.timepit.refined
import org.apache.james.jmap.core.UnsignedInt.{UnsignedInt, UnsignedIntConstraint}
import org.apache.james.util.Size

import scala.util.{Failure, Success, Try}

object PublicAssetTotalSizeLimit {
val DEFAULT: PublicAssetTotalSizeLimit = PublicAssetTotalSizeLimit.of(Size.of(20L, Size.Unit.M)).get

def of(size: Size): Try[PublicAssetTotalSizeLimit] = refined.refineV[UnsignedIntConstraint](size.asBytes()) match {
case Right(value) => Success(PublicAssetTotalSizeLimit(value))
case Left(error) => Failure(new NumberFormatException(error))
}
}

case class PublicAssetTotalSizeLimit(value: UnsignedInt) {
def asLong(): Long = value.value
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package com.linagora.tmail.james.jmap.contact

import java.util.Locale

import com.linagora.tmail.james.jmap.contact.TmailContactMessageScope.{DOMAIN, USER}
import com.linagora.tmail.james.jmap.contact.TmailContactMessageType.{ADDITION, REMOVAL, UPDATE}
import TmailContactMessageScope.{DOMAIN, USER}
import TmailContactMessageType.{ADDITION, REMOVAL, UPDATE}
import org.apache.james.core.{Domain, MailAddress, Username}

import scala.util.Try
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.linagora.tmail.james.jmap.label

import com.linagora.tmail.james.jmap.label.LabelChangeRepository.DEFAULT_MAX_IDS_TO_RETURN
import com.linagora.tmail.james.jmap.model.{LabelChange, LabelChanges}
import org.apache.james.jmap.api.change.{Limit, State}
import org.apache.james.jmap.api.model.AccountId
import org.reactivestreams.Publisher

object LabelChangeRepository {
val DEFAULT_MAX_IDS_TO_RETURN : Limit = Limit.of(256)
}

trait LabelChangeRepository {
def save(labelChange: LabelChange): Publisher[Void]

def getSinceState(accountId: AccountId, state: State, maxIdsToReturn: Option[Limit] = Some(DEFAULT_MAX_IDS_TO_RETURN)): Publisher[LabelChanges]

def getLatestState(accountId: AccountId): Publisher[State]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.linagora.tmail.james.jmap.label

import java.util

import com.linagora.tmail.james.jmap.model.{Color, DisplayName, Label, LabelCreationRequest, LabelId}
import org.apache.james.core.Username
import org.reactivestreams.Publisher

trait LabelRepository {
def addLabel(username: Username, labelCreationRequest: LabelCreationRequest): Publisher[Label]

def addLabel(username: Username, label: Label): Publisher[Void]

def addLabels(username: Username, labelCreationRequests: util.Collection[LabelCreationRequest]): Publisher[Label]

def updateLabel(username: Username, labelId: LabelId, newDisplayName: Option[DisplayName] = None, newColor: Option[Color] = None): Publisher[Void]

def getLabels(username: Username, ids: util.Collection[LabelId]): Publisher[Label]

def listLabels(username: Username): Publisher[Label]

def deleteLabel(username: Username, labelId: LabelId): Publisher[Void]

def deleteAllLabels(username: Username): Publisher[Void]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package com.linagora.tmail.james.jmap.model

import java.time.ZonedDateTime
import java.util.UUID

import eu.timepit.refined.auto._
import org.apache.james.jmap.api.model.ExpireTimeInvalidException.TIME_FORMATTER
import org.apache.james.jmap.api.model.TypeName
import org.apache.james.jmap.core.Id.Id
import org.apache.james.jmap.core.{Id, Properties}
import org.apache.james.jmap.method.WithoutAccountId

import scala.util.Try

object FirebaseSubscriptionId {
def generate(): FirebaseSubscriptionId = FirebaseSubscriptionId(UUID.randomUUID)

def liftOrThrow(unparsedId: UnparsedFirebaseSubscriptionId): Either[IllegalArgumentException, FirebaseSubscriptionId] =
liftOrThrow(unparsedId.id.value)

def liftOrThrow(value: String): Either[IllegalArgumentException, FirebaseSubscriptionId] =
Try(UUID.fromString(value))
.map(value1 => FirebaseSubscriptionId(value1))
.toEither
.left.map(e => new IllegalArgumentException("FirebaseSubscriptionId is invalid", e))
}

case class FirebaseSubscriptionId(value: UUID) {
def serialize: String = value.toString

def asUnparsedFirebaseSubscriptionId: UnparsedFirebaseSubscriptionId =
UnparsedFirebaseSubscriptionId(Id.validate(serialize).toOption.get)
}

case class DeviceClientId(value: String) extends AnyVal

case class FirebaseToken(value: String) extends AnyVal

case class FirebaseSubscriptionExpiredTime(value: ZonedDateTime) {
def isAfter(date: ZonedDateTime): Boolean = value.isAfter(date)

def isBefore(date: ZonedDateTime): Boolean = value.isBefore(date)
}

object FirebaseSubscriptionCreation {
val serverSetProperty: Set[String] = Set("id")
val assignableProperties: Set[String] = Set("deviceClientId", "token", "expires", "types")
val knownProperties: Set[String] = assignableProperties ++ serverSetProperty
}

case class FirebaseSubscriptionCreationRequest(deviceClientId: DeviceClientId,
token: FirebaseToken,
expires: Option[FirebaseSubscriptionExpiredTime] = None,
types: Seq[TypeName]) {

def validate: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] =
validateTypes

private def validateTypes: Either[IllegalArgumentException, FirebaseSubscriptionCreationRequest] =
if (types.isEmpty) {
scala.Left(new IllegalArgumentException("types must not be empty"))
} else {
Right(this)
}
}

object FirebaseSubscription {
val EXPIRES_TIME_MAX_DAY: Int = 7
val allProperties: Properties = Properties("id", "deviceClientId", "expires", "types")
val idProperty: Properties = Properties("id")

def from(creationRequest: FirebaseSubscriptionCreationRequest,
expireTime: FirebaseSubscriptionExpiredTime): FirebaseSubscription =
FirebaseSubscription(id = FirebaseSubscriptionId.generate(),
deviceClientId = creationRequest.deviceClientId,
token = creationRequest.token,
expires = expireTime,
types = creationRequest.types)
}

case class FirebaseSubscription(id: FirebaseSubscriptionId,
deviceClientId: DeviceClientId,
token: FirebaseToken,
expires: FirebaseSubscriptionExpiredTime,
types: Seq[TypeName]) {
def withTypes(types: Seq[TypeName]): FirebaseSubscription = copy(types = types)

def withExpires(expires: FirebaseSubscriptionExpiredTime): FirebaseSubscription = copy(expires = expires)
}

case class FirebaseSubscriptionNotFoundException(id: FirebaseSubscriptionId) extends RuntimeException

case class ExpireTimeInvalidException(expires: ZonedDateTime, message: String) extends IllegalStateException(s"`${expires.format(TIME_FORMATTER)}` $message")

case class DeviceClientIdInvalidException(deviceClientId: DeviceClientId, message: String) extends IllegalArgumentException(s"`${deviceClientId.value}` $message")

case class TokenInvalidException(message: String) extends IllegalArgumentException(message)

case class MissingOrInvalidFirebaseCredentialException(message: String) extends IllegalArgumentException(message)

case class UnparsedFirebaseSubscriptionId(id: Id)

case class FirebaseSubscriptionIds(list: List[UnparsedFirebaseSubscriptionId])

case class FirebaseSubscriptionGetRequest(ids: Option[FirebaseSubscriptionIds],
properties: Option[Properties]) extends WithoutAccountId {

def validateProperties: Either[IllegalArgumentException, Properties] =
properties match {
case None => Right(FirebaseSubscription.allProperties)
case Some(value) =>
value -- FirebaseSubscription.allProperties match {
case invalidProperties if invalidProperties.isEmpty() => Right(value ++ FirebaseSubscription.idProperty)
case invalidProperties: Properties => Left(new IllegalArgumentException(s"The following properties [${invalidProperties.format()}] do not exist."))
}
}
}
Loading