-
-
Notifications
You must be signed in to change notification settings - Fork 30
Add minimal ephemeral model setup #1180
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
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| package com.faforever.api.config; | ||
|
|
||
| import com.faforever.api.data.annotation.Ephemeral; | ||
| import com.yahoo.elide.core.datastore.DataStore; | ||
| import com.yahoo.elide.datastores.noop.NoopDataStore; | ||
| import org.springframework.context.annotation.Bean; | ||
| import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; | ||
| import org.springframework.context.annotation.Configuration; | ||
| import org.springframework.core.type.filter.AnnotationTypeFilter; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Configuration | ||
| public class EphemeralDatastoreConfig { | ||
|
|
||
| @Bean | ||
| DataStore ephemeralDataStore() { | ||
| ClassPathScanningCandidateComponentProvider ephemeralScanner = new ClassPathScanningCandidateComponentProvider( | ||
| false); | ||
| ephemeralScanner.addIncludeFilter(new AnnotationTypeFilter(Ephemeral.class)); | ||
| List<Class> ephemeralModels = ephemeralScanner.findCandidateComponents("com.faforever.api.data.domain") | ||
| .stream() | ||
| .map(beanDefinition -> { | ||
| try { | ||
| return Class.forName(beanDefinition.getBeanClassName()); | ||
| } catch (ClassNotFoundException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| }) | ||
| .map(Class.class::cast) | ||
| .toList(); | ||
|
|
||
| return new NoopDataStore(ephemeralModels); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| package com.faforever.api.data.annotation; | ||
|
|
||
| import java.lang.annotation.ElementType; | ||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| /** | ||
| * Use to mark domain model as ephemeral that is not saved to the database | ||
| */ | ||
| @Target(ElementType.TYPE) | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| public @interface Ephemeral { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| package com.faforever.api.data.domain; | ||
|
|
||
| import com.faforever.api.data.annotation.Ephemeral; | ||
| import com.faforever.api.data.checks.Prefab; | ||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| import com.yahoo.elide.annotation.Audit; | ||
| import com.yahoo.elide.annotation.CreatePermission; | ||
| import com.yahoo.elide.annotation.DeletePermission; | ||
| import com.yahoo.elide.annotation.Include; | ||
| import com.yahoo.elide.annotation.ReadPermission; | ||
| import com.yahoo.elide.annotation.ToOne; | ||
| import com.yahoo.elide.annotation.UpdatePermission; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.Id; | ||
| import jakarta.persistence.Transient; | ||
| import lombok.Setter; | ||
|
|
||
| @Setter | ||
| @Include(name = "gameReviewRequest", rootLevel = false) | ||
| @Ephemeral | ||
| @CreatePermission(expression = Prefab.ALL) | ||
| @UpdatePermission(expression = Prefab.NONE) | ||
| @ReadPermission(expression = Prefab.ALL) | ||
| @DeletePermission(expression = Prefab.NONE) | ||
| @Audit(action = Audit.Action.CREATE, logStatement = "Review has been requested for game ''{0}''", logExpressions = {"${gameReviewRequest.game.id}"}) | ||
| public class GameReviewRequest implements OwnableEntity { | ||
|
|
||
| private Game game; | ||
| private Player player; | ||
| private String requestDescription; | ||
|
|
||
| @Id | ||
| @GeneratedValue | ||
| public String getId() { | ||
| return "N/A"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 388 🏁 Script executed: #!/bin/bash
set -eu
file='src/main/java/com/faforever/api/data/domain/GameReviewRequest.java'
printf '%s\n' '--- target file ---'
cat -n "$file"
printf '%s\n' '--- direct references ---'
rg -n -C 3 'GameReviewRequest|ReviewRequests|reviewRequests|getId\(\)|getEntityOwner|CreatePermission|UpdatePermission|DeletePermission' src/main/java src/test 2>/dev/null | head -n 240Repository: FAForever/faf-java-api Length of output: 23981 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- Ephemeral and GameReviewRequest bindings ---'
rg -n -C 5 'interface Ephemeral|`@Ephemeral`|class Game|Set<GameReviewRequest>|List<GameReviewRequest>|getReviewRequests|GameReviewRequest' src/main/java src/test 2>/dev/null | head -n 260
printf '%s\n' '--- Elide/datastore configuration ---'
rg -n -C 4 'Ephemeral|DataStore|EntityDictionary|Elide|elide|ephemeral' src/main/java src/test 2>/dev/null | head -n 260Repository: FAForever/faf-java-api Length of output: 39038 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- ephemeral datastore files ---'
fd -i 'ephemeral|datastore|config' src/main/java
printf '%s\n' '--- ephemeral datastore declarations and Game relationship ---'
rg -n -C 8 'ephemeralDataStore|EphemeralDataStore|InMemory|Memory|GameReviewRequest|getReviewRequests|`@ComputedRelationship`|`@ToMany`' src/main/java
printf '%s\n' '--- relevant dependency versions ---'
rg -n -C 2 'elide|jackson' pom.xml build.gradle* gradle.properties 2>/dev/null || trueRepository: FAForever/faf-java-api Length of output: 16735 🏁 Script executed: #!/bin/bash
set -eu
cat -n src/main/java/com/faforever/api/config/EphemeralDatastoreConfig.java
printf '%s\n' '--- permission context ---'
sed -n '145,162p' src/main/java/com/faforever/api/data/domain/Game.java
sed -n '18,38p' src/main/java/com/faforever/api/data/domain/GameReviewRequest.javaRepository: FAForever/faf-java-api Length of output: 3187 Return a unique resource ID.
Store a generated unique ID in 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| @ToOne | ||
| public Game getGame() { | ||
| return game; | ||
| } | ||
|
|
||
| @ToOne | ||
| public Player getPlayer() { | ||
| return player; | ||
| } | ||
|
|
||
| public String getRequestDescription() { | ||
| return requestDescription; | ||
| } | ||
|
|
||
| @Transient | ||
| @Override | ||
| @JsonIgnore | ||
| public Login getEntityOwner() { | ||
| return getPlayer(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🛡️ Analyzed with Security Review | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🤖 get_repo_knowledge executed:
Length of output: 401 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- GameReviewRequest ---'
cat -n src/main/java/com/faforever/api/data/domain/GameReviewRequest.java
printf '%s\n' '--- direct symbols and Elide configuration ---'
rg -n -S "GameReviewRequest|CreatePermission|UpdatePermission|getEntityOwner|Prefab\.ALL|Elide|reviewRequests" src/main/java src/test || trueRepository: FAForever/faf-java-api Length of output: 34456 🏁 Script executed: #!/bin/bash
set -e
printf '%s\n' '--- Game relationship and API dispatch ---'
cat -n src/main/java/com/faforever/api/data/domain/Game.java | sed -n '45,175p'
cat -n src/main/java/com/faforever/api/data/DataController.java | sed -n '45,125p'
printf '%s\n' '--- ownership and identity contracts ---'
cat -n src/main/java/com/faforever/api/data/checks/IsEntityOwner.java
cat -n src/main/java/com/faforever/api/data/domain/OwnableEntity.java
cat -n src/main/java/com/faforever/api/data/domain/Player.java | sed -n '1,115p'
cat -n src/main/java/com/faforever/api/data/domain/Login.java | sed -n '1,150p'
printf '%s\n' '--- relevant test/config references ---'
rg -n -S "gameReviewRequest|review-requests|reviewRequests|GameReviewRequest|nested|post\\(" src/test src/main/resources src/main/java | head -200 || trueRepository: FAForever/faf-java-api Length of output: 20349 🌐 Web query:
💡 Result: In the Elide framework, which implements JSON API, the annotations Citations:
Authorization Bypass Reachability: External Do not derive ownership from a client-selected
Set 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
🔎 Supported by static analysis
🤖 get_repo_knowledge executed:
get_repo_knowledge FAForever/faf-java-api /tmp/coderabbit-repo-knowledge/faforever-faf-java-api-5f3ae194Length of output: 402
🏁 Script executed:
Repository: FAForever/faf-java-api
Length of output: 25980
🏁 Script executed:
Repository: FAForever/faf-java-api
Length of output: 3283
🏁 Script executed:
Repository: FAForever/faf-java-api
Length of output: 50379
🏁 Script executed:
Repository: FAForever/faf-java-api
Length of output: 1852
🌐 Web query:
Elide Java@ComputedRelationshipcomputed relationship getter semantics💡 Result:
In Elide, the
@ComputedRelationshipannotation is used to mark a field or method as a relationship whose value is computed in code rather than retrieved directly from a persistent data store [1][2]. Its primary semantic purpose is to ensure that Elide exposes the relationship through its API, even if the underlying field or method is marked as@Transient(which would otherwise cause it to be ignored by data store frameworks like Hibernate) [2][3]. Key semantics and usage include: 1. Exposure: When a field or method is annotated with@ComputedRelationship, Elide includes it in the exposed data model [1][2]. This is critical for scenarios where the data is derived, such as traversing a relationship chain or performing logic to determine a related entity [2][4]. 2. Compatibility: It is frequently used in conjunction with@Transientto prevent persistence layers from attempting to map or store the computed value while allowing the Elide API to serve it [2][4]. 3. Implementation: The method annotated with@ComputedRelationshipshould contain the necessary logic to calculate or return the related entity [2][4]. For example, a common pattern involves checking for the existence of an object and then returning a property or sub-relationship from that object [4]. 4. Applicability: The annotation can be applied to both fields and methods [1][5]. In essence,@ComputedRelationshipacts as an override instruction to Elide, telling the framework to treat a computed value as a formal relationship in the API schema regardless of its transient status in the database layer [2][3].Citations:
🏁 Script executed:
Repository: FAForever/faf-java-api
Length of output: 4417
🌐 Web query:
site:elide.io/pages/guide/v7@ComputedRelationshipgetter method computed relationship💡 Result:
The
@ComputedRelationshipannotation is a feature provided by the Elide framework used to mark a field or a method as a computed relationship [1][2]. This annotation allows developers to define custom relationships that are computed at query time rather than being retrieved directly from a persistent data store [3]. When you apply@ComputedRelationshipto a getter method, it instructs Elide to expose that method as a relationship in the API, even if the field or method is marked as@Transient[1][2]. This is particularly useful for scenarios where you want to include data in your API model that is generated or calculated in the JVM but is not stored in the underlying database [2]. For example, if you annotate a getter method with@Transientto ensure Hibernate ignores it, adding@ComputedRelationshipensures that Elide still exposes this as a relationship in your API schema [2]. The annotation can be applied at the field or method level [1].Citations:
Populate
reviewRequestsbefore exposing the computed relationship.Game.getReviewRequests()only returns the transientreviewRequestsfield. No code assigns this field, andGameEnricherdoes not populate it.GameReviewRequeststores only the forwardgamereference and uses the ephemeral no-op datastore. Therefore, an Elide read cannot obtain review requests from this relationship. Add a population path and an integration test for create-then-read behavior.🤖 Prompt for AI Agents