Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
424b954
Modpack/dependencies v2
Villagers654 Jun 12, 2026
a1562cf
Fix tests
Villagers654 Jun 12, 2026
db84996
Fix deployment
Villagers654 Jun 12, 2026
2b5ecb4
Put mods in the root
Villagers654 Jun 12, 2026
283164a
Make external sources even more clear
Villagers654 Jun 12, 2026
dc3ede2
Merge branch 'develop' into modpack-v2
Villagers654 Jun 13, 2026
8efa8d6
Merge branch 'develop' into modpack-v2
Villagers654 Jun 13, 2026
ef53d49
Merge branch 'develop' into modpack-v2
Villagers654 Jun 15, 2026
563bef3
Merge branch 'develop' into modpack-v2
Villagers654 Jun 15, 2026
5db1d95
Make the orgder of navbar and category selector line up
Villagers654 Jun 16, 2026
f411cf7
Merge develop into modpack-v2
Villagers654 Jun 19, 2026
7ce7ba7
Merge branch 'develop' into modpack-v2
Villagers654 Jun 20, 2026
ca2ac5b
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
dfcf35f
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
7dbfed0
Attempt to redeploy
Villagers654 Jun 21, 2026
003b5db
Use account-owned Cloudflare token endpoints
Villagers654 Jun 21, 2026
70dbd3b
Attempt to redeploy
Villagers654 Jun 21, 2026
79dc398
Load branch R2 runtime credentials in current step
Villagers654 Jun 21, 2026
6c9a29e
Attempt to redeploy
Villagers654 Jun 21, 2026
007d796
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
193211e
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
0a214d3
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
04c1fd1
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
2435c35
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
11e20d4
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
330e63d
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
28e8886
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
40541c3
Merge branch 'develop' into modpack-v2
Villagers654 Jun 21, 2026
3e87bea
Merge branch 'develop' into modpack-v2
Villagers654 Jun 27, 2026
2a702d4
Fix modpack dependency update fallout
Villagers654 Jun 27, 2026
5705316
Merge branch 'develop' into modpack-v2
Villagers654 Jun 27, 2026
4c09893
Fix backend startup and render budget
Villagers654 Jun 28, 2026
1e4d19b
Merge branch 'develop' into modpack-v2
Villagers654 Jul 24, 2026
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
2 changes: 1 addition & 1 deletion backend/cloudbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@ substitutions:
_TAG: latest

options:
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
defaultLogsBucketBehavior: REGIONAL_USER_OWNED_BUCKET
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package net.modtale.config.db;

import com.mongodb.client.MongoCollection;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.ReplaceOptions;
import java.util.List;
import java.util.UUID;
import net.modtale.model.project.ProjectDependency;
import org.bson.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;

@Component
public class ProjectDependencySchemaMigration {

private static final Logger logger = LoggerFactory.getLogger(ProjectDependencySchemaMigration.class);

private final MongoTemplate mongoTemplate;

public ProjectDependencySchemaMigration(MongoTemplate mongoTemplate) {
this.mongoTemplate = mongoTemplate;
}

@EventListener(ApplicationReadyEvent.class)
public void migrateLegacyDependencyDocuments() {
MongoCollection<Document> projects = mongoTemplate.getCollection("projects");
int changedProjects = 0;
int changedDependencies = 0;

for (Document project : projects.find(Filters.or(
Filters.exists("versions.dependencies.modId"),
Filters.exists("versions.dependencies.id", false),
Filters.exists("modIds")
))) {
int projectChanges = normalizeProject(project);
if (projectChanges == 0) {
continue;
}

projects.replaceOne(
Filters.eq("_id", project.get("_id")),
project,
new ReplaceOptions().upsert(false)
);
changedProjects++;
changedDependencies += projectChanges;
}

if (changedProjects > 0) {
logger.info("Migrated {} legacy dependency references across {} projects.", changedDependencies, changedProjects);
}
}

private int normalizeProject(Document project) {
int changes = normalizeProjectDependencyIndex(project);
Object rawVersions = project.get("versions");
if (!(rawVersions instanceof List<?> versions)) {
return changes;
}

for (Object versionObj : versions) {
if (!(versionObj instanceof Document version)) {
continue;
}
Object rawDependencies = version.get("dependencies");
if (!(rawDependencies instanceof List<?> dependencies)) {
continue;
}
for (Object dependencyObj : dependencies) {
if (dependencyObj instanceof Document dependency && normalizeDependency(dependency)) {
changes++;
}
}
}
return changes;
}

private int normalizeProjectDependencyIndex(Document project) {
if (!project.containsKey("modIds")) {
return 0;
}
Object legacyModIds = project.get("modIds");
if (!project.containsKey("childProjectIds") && legacyModIds instanceof List<?>) {
project.put("childProjectIds", legacyModIds);
}
project.remove("modIds");
return 1;
}

private boolean normalizeDependency(Document dependency) {
if (!dependency.containsKey("modId")
&& dependency.containsKey("projectId")
&& dependency.containsKey("dependencyType")
&& dependency.containsKey("id")) {
return false;
}

Object projectId = firstPresent(dependency, "projectId", "modId");
Object projectTitle = firstPresent(dependency, "projectTitle", "modTitle");
dependency.putIfAbsent("id", UUID.randomUUID().toString());
if (projectId != null) {
dependency.put("projectId", projectId);
}
if (projectTitle != null) {
dependency.put("projectTitle", projectTitle);
}
dependency.putIfAbsent("source", ProjectDependency.Source.MODTALE.name());
dependency.putIfAbsent("hytaleProjectConfirmed", false);
dependency.put("dependencyType", inferDependencyType(dependency));

dependency.remove("modId");
dependency.remove("modTitle");
dependency.remove("isOptional");
dependency.remove("isEmbedded");
return true;
}

private Object firstPresent(Document document, String primary, String fallback) {
Object primaryValue = document.get(primary);
return primaryValue != null ? primaryValue : document.get(fallback);
}

private String inferDependencyType(Document dependency) {
if (Boolean.TRUE.equals(dependency.getBoolean("isEmbedded"))) {
return ProjectDependency.DependencyType.EMBEDDED.name();
}
if (Boolean.TRUE.equals(dependency.getBoolean("isOptional"))) {
return ProjectDependency.DependencyType.OPTIONAL.name();
}
Object existingType = dependency.get("dependencyType");
if (existingType != null) {
return existingType.toString();
}
return ProjectDependency.DependencyType.REQUIRED.name();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package net.modtale.controller.project;

import net.modtale.model.dto.project.ExternalProjectReferenceDTO;
import net.modtale.model.project.ProjectDependency;
import net.modtale.service.project.version.ExternalProjectReferenceService;
import org.springframework.http.CacheControl;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("/api/v1")
public class ExternalProjectController {

private final ExternalProjectReferenceService externalProjectReferenceService;

public ExternalProjectController(ExternalProjectReferenceService externalProjectReferenceService) {
this.externalProjectReferenceService = externalProjectReferenceService;
}

@GetMapping("/projects/external/resolve")
@PreAuthorize("@apiSecurity.hasAnyPerm('PROJECT_READ', authentication)")
public ResponseEntity<ExternalProjectReferenceDTO> resolveExternalProject(
@RequestParam String url,
@RequestParam(required = false) ProjectDependency.Source source
) {
return ResponseEntity.ok()
.cacheControl(CacheControl.maxAge(10, TimeUnit.MINUTES).cachePublic())
.body(externalProjectReferenceService.resolve(url, source));
}
}
16 changes: 12 additions & 4 deletions backend/src/main/java/net/modtale/mapper/ProjectMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,6 @@ public static ProjectDTO toDTO(Project project, boolean isSummary, String curren
if (!isSummary) {
dto.setAbout(project.getAbout());
dto.setChildProjectIds(project.getChildProjectIds());
dto.setModIds(project.getModIds());

dto.setProjectRoles(project.getProjectRoles());
dto.setTeamMembers(project.getTeamMembers());
Expand Down Expand Up @@ -369,11 +368,20 @@ public static List<AdminProjectVersionSummaryDTO> toAdminVersionSummaryDTOs(List
public static ProjectDependencyDTO toDependencyDTO(ProjectDependency dependency) {
if (dependency == null) return null;
return new ProjectDependencyDTO(
dependency.getModId(),
dependency.getModTitle(),
dependency.getId(),
dependency.getProjectId(),
dependency.getProjectTitle(),
dependency.getVersionNumber(),
dependency.getDependencyType(),
dependency.getSource(),
dependency.getExternalId(),
dependency.getExternalUrl(),
dependency.getExternalFileUrl(),
dependency.getExternalFileName(),
dependency.getCachedFileUrl(),
dependency.isHytaleProjectConfirmed(),
dependency.getIcon(),
dependency.getTitle() != null ? dependency.getTitle() : dependency.getModTitle(),
dependency.getTitle() != null ? dependency.getTitle() : dependency.getProjectTitle(),
dependency.getClassification(),
dependency.getSlug(),
dependency.isOptional(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package net.modtale.model.dto.project;

import com.fasterxml.jackson.annotation.JsonInclude;
import java.util.List;
import net.modtale.model.project.ProjectDependency;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record ExternalProjectReferenceDTO(
ProjectDependency.Source source,
String externalId,
String title,
String versionNumber,
String externalUrl,
String iconUrl,
String summary,
boolean hytaleProjectConfirmed,
List<ExternalFileDTO> files
) {
@JsonInclude(JsonInclude.Include.NON_NULL)
public record ExternalFileDTO(
String id,
String displayName,
String fileName,
String versionNumber,
String releaseType,
String downloadUrl
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ public class ManifestDependencySuggestion {
private String versionNumber;
private boolean optional;
private int confidence;
private String dependencyEntry;

public ManifestDependencySuggestion() {}

Expand All @@ -20,7 +19,6 @@ public ManifestDependencySuggestion(String manifestKey, String requestedVersion,
this.versionNumber = versionNumber;
this.optional = optional;
this.confidence = confidence;
this.dependencyEntry = projectId + ":" + versionNumber + (optional ? ":optional" : "");
}

public String getManifestKey() { return manifestKey; }
Expand All @@ -43,7 +41,4 @@ public ManifestDependencySuggestion(String manifestKey, String requestedVersion,

public int getConfidence() { return confidence; }
public void setConfidence(int confidence) { this.confidence = confidence; }

public String getDependencyEntry() { return dependencyEntry; }
public void setDependencyEntry(String dependencyEntry) { this.dependencyEntry = dependencyEntry; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ public class ProjectDTO {
private Map<String, String> links;
private List<String> types;
private List<String> childProjectIds;
private List<String> modIds;
private boolean allowModpacks;
private boolean allowComments;
private boolean hmWikiEnabled;
Expand Down Expand Up @@ -107,8 +106,6 @@ public class ProjectDTO {
public void setTypes(List<String> types) { this.types = types; }
public List<String> getChildProjectIds() { return childProjectIds; }
public void setChildProjectIds(List<String> childProjectIds) { this.childProjectIds = childProjectIds; }
public List<String> getModIds() { return modIds; }
public void setModIds(List<String> modIds) { this.modIds = modIds; }
public boolean isAllowModpacks() { return allowModpacks; }
public void setAllowModpacks(boolean allowModpacks) { this.allowModpacks = allowModpacks; }
public boolean isAllowComments() { return allowComments; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@

import com.fasterxml.jackson.annotation.JsonInclude;
import net.modtale.model.project.ProjectClassification;
import net.modtale.model.project.ProjectDependency;

@JsonInclude(JsonInclude.Include.NON_NULL)
public record ProjectDependencyDTO(
String id,
String projectId,
String projectTitle,
String versionNumber,
ProjectDependency.DependencyType dependencyType,
ProjectDependency.Source source,
String externalId,
String externalUrl,
String externalFileUrl,
String externalFileName,
String cachedFileUrl,
boolean hytaleProjectConfirmed,
String icon,
String title,
ProjectClassification classification,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class CreateVersionRequest {

private List<@NotBlank(message = "Game version entries cannot be blank.") String> gameVersions;
private MultipartFile file;
private List<@NotBlank(message = "Dependency entries cannot be blank.") String> modIds;
private List<DependencyReferenceRequest> dependencies;
private List<@NotBlank(message = "Incompatible project entries cannot be blank.") String> incompatibleProjectIds;

@Size(max = 50000, message = "Version changelogs cannot exceed 50,000 characters.")
Expand Down Expand Up @@ -46,12 +46,12 @@ public void setFile(MultipartFile file) {
this.file = file;
}

public List<String> getModIds() {
return modIds;
public List<DependencyReferenceRequest> getDependencies() {
return dependencies;
}

public void setModIds(List<String> modIds) {
this.modIds = modIds;
public void setDependencies(List<DependencyReferenceRequest> dependencies) {
this.dependencies = dependencies;
}

public List<String> getIncompatibleProjectIds() {
Expand Down
Loading
Loading