diff options
| author | vincent van beek <vincent@vlogic.nl> | 2026-03-27 14:22:41 +0100 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-03-27 13:22:41 +0000 |
| commit | 235057cd170f1583db14bf93ea7d2de39e492356 (patch) | |
| tree | 157e9214c3f835d007bdbd265e3ca883e1326fcb /opendc-web/opendc-web-server/src/main/java/org/opendc/web/server | |
| parent | 0ffde21b0337c606e2d0ece5bd5434a930a87dcd (diff) | |
add prefabs for racks (#392)
* add prefabs for racks
Diffstat (limited to 'opendc-web/opendc-web-server/src/main/java/org/opendc/web/server')
4 files changed, 353 insertions, 0 deletions
diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java index ca032e21..a0c20443 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/Project.java @@ -77,6 +77,14 @@ import java.util.Set; UPDATE Project p SET p.scenariosCreated = :oldState + 1, p.updatedAt = :now WHERE p.id = :id AND p.scenariosCreated = :oldState + """), + @NamedQuery( + name = "Project.allocateRackPrefab", + query = + """ + UPDATE Project p + SET p.rackPrefabsCreated = :oldState + 1, p.updatedAt = :now + WHERE p.id = :id AND p.rackPrefabsCreated = :oldState """) }) public class Project extends PanacheEntityBase { @@ -153,6 +161,22 @@ public class Project extends PanacheEntityBase { public int scenariosCreated = 0; /** + * The rack prefabs belonging to this project. + */ + @OneToMany( + cascade = {CascadeType.ALL}, + mappedBy = "project", + orphanRemoval = true) + @OrderBy("id ASC") + public Set<RackPrefab> rackPrefabs = new HashSet<>(); + + /** + * The number of rack prefabs created for this project (including deleted rack prefabs). + */ + @Column(name = "rack_prefabs_created", nullable = false) + public int rackPrefabsCreated = 0; + + /** * The users authorized to access the project. */ @OneToMany( @@ -234,4 +258,26 @@ public class Project extends PanacheEntityBase { throw new IllegalStateException("Failed to allocate next scenario"); } + + /** + * Allocate the next rack prefab number for the specified [project]. + * + * @param time The time at which the new rack prefab is created. + */ + public int allocateRackPrefab(Instant time) { + for (int i = 0; i < 4; i++) { + long count = update( + "#Project.allocateRackPrefab", + Parameters.with("id", id) + .and("oldState", rackPrefabsCreated) + .and("now", time)); + if (count > 0) { + return rackPrefabsCreated + 1; + } else { + Panache.getEntityManager().refresh(this); + } + } + + throw new IllegalStateException("Failed to allocate next rack prefab"); + } } diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/RackPrefab.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/RackPrefab.java new file mode 100644 index 00000000..53b8466e --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/model/RackPrefab.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2024 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.model; + +import io.hypersistence.utils.hibernate.type.json.JsonType; +import io.quarkus.hibernate.orm.panache.PanacheEntityBase; +import io.quarkus.hibernate.orm.panache.PanacheQuery; +import io.quarkus.panache.common.Parameters; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.NamedQueries; +import jakarta.persistence.NamedQuery; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import jakarta.persistence.UniqueConstraint; +import java.time.Instant; +import org.hibernate.annotations.Type; +import org.opendc.web.proto.topology.Rack; + +/** + * A rack prefab in OpenDC. + */ +@Entity +@Table( + name = "rack_prefabs", + uniqueConstraints = { + @UniqueConstraint( + name = "uk_rack_prefabs_number", + columnNames = {"project_id", "number"}) + }, + indexes = {@Index(name = "ux_rack_prefabs_number", columnList = "project_id, number")}) +@NamedQueries({ + @NamedQuery( + name = "RackPrefab.findByProject", + query = "SELECT r FROM RackPrefab r WHERE r.project.id = :projectId"), + @NamedQuery( + name = "RackPrefab.findOneByProject", + query = "SELECT r FROM RackPrefab r WHERE r.project.id = :projectId AND r.number = :number") +}) +public class RackPrefab extends PanacheEntityBase { + /** + * The main ID of a rack prefab. + */ + @Id + @SequenceGenerator(name = "rackPrefabSeq", sequenceName = "rack_prefab_id_seq", allocationSize = 1) + @GeneratedValue(generator = "rackPrefabSeq") + public Long id; + + /** + * The {@link Project} to which the rack prefab belongs. + */ + @ManyToOne(optional = false) + @JoinColumn(name = "project_id", nullable = false) + public Project project; + + /** + * Unique number of the rack prefab for the project. + */ + @Column(nullable = false) + public int number; + + /** + * The name of the rack prefab. + */ + @Column(nullable = false) + public String name; + + /** + * The instant at which the rack prefab was created. + */ + @Column(name = "created_at", nullable = false, updatable = false) + public Instant createdAt; + + /** + * The instant at which the rack prefab was updated. + */ + @Column(name = "updated_at", nullable = false) + public Instant updatedAt; + + /** + * The rack design in JSON. + */ + @Column(columnDefinition = "jsonb", nullable = false) + @Type(JsonType.class) + public Rack rack; + + /** + * Construct a {@link RackPrefab} object. + */ + public RackPrefab(Project project, int number, String name, Instant createdAt, Rack rack) { + this.project = project; + this.number = number; + this.name = name; + this.createdAt = createdAt; + this.updatedAt = createdAt; + this.rack = rack; + } + + /** + * JPA constructor + */ + protected RackPrefab() {} + + /** + * Find all [RackPrefab]s that belong to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @return The query of rack prefabs that belong to the specified project. + */ + public static PanacheQuery<RackPrefab> findByProject(long projectId) { + return find("#RackPrefab.findByProject", Parameters.with("projectId", projectId)); + } + + /** + * Find the [RackPrefab] with the specified [number] belonging to [project][projectId]. + * + * @param projectId The unique identifier of the project. + * @param number The number of the rack prefab. + * @return The rack prefab or `null` if it does not exist. + */ + public static RackPrefab findByProject(long projectId, int number) { + return find( + "#RackPrefab.findOneByProject", + Parameters.with("projectId", projectId).and("number", number)) + .firstResult(); + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/RackPrefabResource.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/RackPrefabResource.java new file mode 100644 index 00000000..c0051b2b --- /dev/null +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/RackPrefabResource.java @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 AtLarge Research + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package org.opendc.web.server.rest.user; + +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.annotation.security.RolesAllowed; +import jakarta.transaction.Transactional; +import jakarta.validation.Valid; +import jakarta.ws.rs.Consumes; +import jakarta.ws.rs.DELETE; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.POST; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.WebApplicationException; +import java.time.Instant; +import java.util.List; +import org.opendc.web.server.model.Project; +import org.opendc.web.server.model.ProjectAuthorization; +import org.opendc.web.server.model.RackPrefab; + +/** + * A resource representing rack prefabs. + */ +@Produces("application/json") +@Path("/projects/{project}/rack-prefabs") +@RolesAllowed("openid") +public final class RackPrefabResource { + /** + * The identity of the current user. + */ + private final SecurityIdentity identity; + + /** + * Construct a {@link RackPrefabResource}. + * + * @param identity The {@link SecurityIdentity} of the current user. + */ + public RackPrefabResource(SecurityIdentity identity) { + this.identity = identity; + } + + /** + * Get all rack prefabs that belong to the specified project. + */ + @GET + public List<org.opendc.web.proto.user.RackPrefab> getAll(@PathParam("project") long projectId) { + // User must have access to project + ProjectAuthorization auth = + ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); + + if (auth == null) { + return List.of(); + } + + return RackPrefab.findByProject(projectId).list().stream() + .map(p -> UserProtocol.toDto(p, auth)) + .toList(); + } + + /** + * Create a rack prefab for this project. + */ + @POST + @Consumes("application/json") + @Transactional + public org.opendc.web.proto.user.RackPrefab create( + @PathParam("project") long projectId, @Valid org.opendc.web.proto.user.RackPrefab.Create request) { + // User must have access to project + ProjectAuthorization auth = + ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); + + if (auth == null) { + throw new WebApplicationException("Project not found", 404); + } else if (!auth.canEdit()) { + throw new WebApplicationException("Not permitted to edit project", 403); + } + + Instant now = Instant.now(); + Project project = auth.project; + int number = project.allocateRackPrefab(now); + + RackPrefab prefab = new RackPrefab(project, number, request.name(), now, request.rack()); + + project.rackPrefabs.add(prefab); + prefab.persist(); + + return UserProtocol.toDto(prefab, auth); + } + + /** + * Delete the specified rack prefab. + */ + @Path("{number}") + @DELETE + @Transactional + public org.opendc.web.proto.user.RackPrefab delete( + @PathParam("project") long projectId, @PathParam("number") int number) { + // User must have access to project + ProjectAuthorization auth = + ProjectAuthorization.findByUser(identity.getPrincipal().getName(), projectId); + + if (auth == null) { + throw new WebApplicationException("Project not found", 404); + } else if (!auth.canEdit()) { + throw new WebApplicationException("Not permitted to edit project", 403); + } + + RackPrefab entity = RackPrefab.findByProject(projectId, number); + + if (entity == null) { + throw new WebApplicationException("Rack prefab not found", 404); + } + + entity.delete(); + + return UserProtocol.toDto(entity, auth); + } +} diff --git a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java index 8196a9d6..3ee5d72e 100644 --- a/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java +++ b/opendc-web/opendc-web-server/src/main/java/org/opendc/web/server/rest/user/UserProtocol.java @@ -26,6 +26,7 @@ import org.opendc.web.server.model.Job; import org.opendc.web.server.model.Portfolio; import org.opendc.web.server.model.Project; import org.opendc.web.server.model.ProjectAuthorization; +import org.opendc.web.server.model.RackPrefab; import org.opendc.web.server.model.Scenario; import org.opendc.web.server.model.Topology; import org.opendc.web.server.rest.BaseProtocol; @@ -92,6 +93,20 @@ public final class UserProtocol { } /** + * Convert a {@link RackPrefab} entity into a {@link org.opendc.web.proto.user.RackPrefab} DTO. + */ + public static org.opendc.web.proto.user.RackPrefab toDto(RackPrefab rackPrefab, ProjectAuthorization auth) { + return new org.opendc.web.proto.user.RackPrefab( + rackPrefab.id, + rackPrefab.number, + toDto(auth), + rackPrefab.name, + rackPrefab.rack, + rackPrefab.createdAt, + rackPrefab.updatedAt); + } + + /** * Convert a {@link Scenario} entity into a {@link org.opendc.web.proto.user.Scenario} DTO. */ public static org.opendc.web.proto.user.Scenario toDto(Scenario scenario, ProjectAuthorization auth) { |
