Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ ij_java_lambda_brace_style = end_of_line
ij_java_method_brace_style = end_of_line
ij_java_names_count_to_use_import_on_demand = 999

[*.kt]
ij_kotlin_name_count_to_use_star_import = 999
ij_kotlin_name_count_to_use_star_import_for_members = 999

[{*.gant,*.groovy,*.gy,*.gradle}]
ij_groovy_block_brace_style = end_of_line
ij_groovy_class_brace_style = end_of_line
Expand Down
12 changes: 12 additions & 0 deletions botfest/src/main/kotlin/net/modfest/botfest/Platform.kt
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,18 @@ class PlatformAuthenticated(var client: HttpClient, var discordUser: Snowflake)
}.unwrapErrors();
}

suspend fun addMinecraft(username: String) {
client.put("/user/@me/minecraft/$username") {
addAuth()
}.unwrapErrors();
}

suspend fun removeMinecraft(username: String) {
client.delete("/user/@me/minecraft/$username") {
addAuth()
}.unwrapErrors();
}

suspend fun submitModrinth(eventId: String, mrId: String): SubmissionResponseData {
return client.post("/event/$eventId/submissions?type=modrinth") {
addAuth()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package net.modfest.botfest.extensions

import dev.kordex.core.commands.Arguments
import dev.kordex.core.commands.application.slash.converters.ChoiceEnum
import dev.kordex.core.commands.application.slash.converters.impl.enumChoice
import dev.kordex.core.commands.application.slash.converters.impl.stringChoice
import dev.kordex.core.commands.application.slash.ephemeralSubCommand
import dev.kordex.core.commands.converters.impl.optionalString
import dev.kordex.core.commands.converters.impl.string
import dev.kordex.core.extensions.Extension
import dev.kordex.core.extensions.ephemeralSlashCommand
import dev.kordex.core.i18n.types.Key
import dev.kordex.core.i18n.withContext
import dev.kordex.core.koin.KordExKoinComponent
import net.modfest.botfest.MAIN_GUILD_ID
import net.modfest.botfest.Platform
import net.modfest.botfest.i18n.Translations
import net.modfest.platform.pojo.CurrentEventData
import net.modfest.platform.pojo.UserData
import net.modfest.platform.pojo.UserPatchData
import org.koin.core.component.inject

Expand Down Expand Up @@ -54,6 +48,38 @@ class UserCommands : Extension(), KordExKoinComponent {
}
}
}

// Allows the user to add a minecraft username
ephemeralSubCommand(::MinecraftUsernameArgs) {
name = Translations.Commands.User.Minecraft.Add.name
description = Translations.Commands.User.Minecraft.Add.description

action {
platform.withAuth(this.user).addMinecraft(this.arguments.username)

respond {
content = Translations.Commands.User.Minecraft.Add.response
.withContext(this@action)
.translateNamed()
}
}
}

// Allows the user to remove a minecraft username
ephemeralSubCommand(::MinecraftUsernameArgs) {
name = Translations.Commands.User.Minecraft.Remove.name
description = Translations.Commands.User.Minecraft.Remove.description

action {
platform.withAuth(this.user).removeMinecraft(this.arguments.username)

respond {
content = Translations.Commands.User.Minecraft.Remove.response
.withContext(this@action)
.translateNamed()
}
}
}
}
}

Expand All @@ -71,4 +97,11 @@ class UserCommands : Extension(), KordExKoinComponent {
description = Translations.Arguments.Setuser.Bio.description
}
}

inner class MinecraftUsernameArgs : Arguments() {
val username by string {
name = Translations.Arguments.Minecraft.Username.name
description = Translations.Arguments.Minecraft.Username.description
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ commands.whoami.response=\
commands.user.set.name=set
commands.user.set.description=Change information on your profile
commands.user.set.response=Successfully changed data
commands.user.minecraft.add.name=minecraft add
commands.user.minecraft.add.description=Associate a minecraft username with your profile
commands.user.minecraft.add.response=Successfully added {username} ({uuid}) to your user.
commands.user.minecraft.remove.name=minecraft remove
commands.user.minecraft.remove.description=Remove a minecraft username from your profile
commands.user.minecraft.remove.response=Successfully removed {username} ({uuid}) from your user.
commands.register.name=Register
commands.register.description=Register for the current ModFest event
commands.register.response.noevent=ModFest registrations are not currently open. \
Expand Down Expand Up @@ -110,6 +116,8 @@ arguments.submission.edit.name=submission
arguments.submission.edit.description=The submission you want to edit
arguments.submission.edit_image.name=image
arguments.submission.edit_image.description=Your image
arguments.minecraft.username.name=username
arguments.minecraft.username.description=Minecraft username

modal.register.title=Register for ModFest
modal.register.name.label=What name would you like on your profile?
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.modfest.platform.pojo;

public record MinecraftProfile(String name, String id) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public record UserData(
@Nullable String icon,
Set<String> badges,
Set<String> registered,
Set<String> minecraftAccounts,
@NonNull UserRole role
) implements Data {
public UserData withRegistration(EventData event, boolean registered) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

Expand Down Expand Up @@ -170,6 +171,61 @@ public void editUserData(@PathVariable String id, @RequestBody UserPatchData dat
service.save(newUser);
}

@PutMapping("/user/{id}/minecraft/{username}")
public void addUserMinecraft(@PathVariable String id, @PathVariable String username) {
var user = getSingleUser(id);

// Check permissions
// In order for the request to be allowed, the person making the request needs
// to either be editing their own data, or they need to have the EDIT_OTHERS permission
var subject = SecurityUtils.getSubject();
var edit_others = subject.isPermitted(Permissions.Users.EDIT_OTHERS);
var owns = PermissionUtils.owns(subject, user);

if (!owns && !edit_others) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You may not edit this user");
}

String uuid = service.getMinecraftId(username);
if (uuid == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "A minecraft profile with that username does not exist");
}

// Perform operation
var accounts = new HashSet<>(user.minecraftAccounts());
accounts.add(uuid);
service.save(user.withMinecraftAccounts(accounts));
}

@DeleteMapping("/user/{id}/minecraft/{username}")
public void deleteUserMinecraft(@PathVariable String id, @PathVariable String username) {
var user = getSingleUser(id);

// Check permissions
// In order for the request to be allowed, the person making the request needs
// to either be editing their own data, or they need to have the EDIT_OTHERS permission
var subject = SecurityUtils.getSubject();
var edit_others = subject.isPermitted(Permissions.Users.EDIT_OTHERS);
var owns = PermissionUtils.owns(subject, user);

if (!owns && !edit_others) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN, "You may not edit this user");
}

String uuid = service.getMinecraftId(username);
if (uuid == null) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "A minecraft profile with that username does not exist");
}

// Perform operation
var accounts = new HashSet<>(user.minecraftAccounts());
if (!accounts.contains(uuid)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "That minecraft account isn't associated with this user");
}
accounts.remove(uuid);
service.save(user.withMinecraftAccounts(accounts));
}

@PostMapping("/admin/update_user")
@RequiresPermissions(Permissions.Users.FORCE_EDIT)
public void forceUpdateUser(@RequestBody UserData data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* Contains ad-hoc migrations to our json format
*/
public record Migrator(JsonUtil json, Path root) {
static final int CURRENT_VERSION = 7;
static final int CURRENT_VERSION = 8;
static final Map<Integer,MigrationManager.Migration> MIGRATIONS = new HashMap<>();

static {
Expand All @@ -30,6 +30,7 @@ public record Migrator(JsonUtil json, Path root) {
MIGRATIONS.put(5, Migrator::migrateTo5);
MIGRATIONS.put(6, Migrator::migrateTo6);
MIGRATIONS.put(7, Migrator::migrateTo7);
MIGRATIONS.put(8, Migrator::migrateTo8);
}


Expand Down Expand Up @@ -306,4 +307,22 @@ public void migrateTo7() {
});
});
}

/**
* V6
* The "minecraft_accounts" field inside user data has been added
*/
public void migrateTo8() {
var userPath = root.resolve("users");
MigratorUtils.executeForAllFiles(userPath, path -> {
var userJson = json.readJson(path, JsonObject.class);
var minecraftAccounts = userJson.get("minecraft_accounts");
if (minecraftAccounts == null || !minecraftAccounts.isJsonArray()) {
// Default to the empty list
userJson.add("minecraft_accounts", new JsonArray());
// Write the new json
json.writeJson(path, userJson);
}
});
}
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
package net.modfest.platform.service;

import com.google.gson.Gson;
import net.modfest.platform.misc.EventSource;
import net.modfest.platform.misc.MfUserId;
import net.modfest.platform.misc.PlatformStandardException;
import net.modfest.platform.pojo.MinecraftProfile;
import net.modfest.platform.pojo.PlatformErrorResponse;
import net.modfest.platform.pojo.UserCreateData;
import net.modfest.platform.pojo.UserData;
import net.modfest.platform.pojo.UserRole;
import net.modfest.platform.repository.UserRepository;
import nl.theepicblock.dukerinth.ModrinthApi;
import nl.theepicblock.dukerinth.internal.GsonBodyHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.util.Collection;
import java.util.Set;

Expand Down Expand Up @@ -80,6 +87,7 @@ public String create(UserCreateData data) throws InvalidModrinthIdException, Pla
mrUser.avatarUrl,
Set.of(),
Set.of(),
Set.of(),
UserRole.NONE
));

Expand All @@ -99,6 +107,14 @@ private String generateUserId() {
}
}

public String getMinecraftId(String username) {
try(HttpClient client = HttpClient.newHttpClient()) {
return client.send(HttpRequest.newBuilder(URI.create("https://api.minecraftservices.com/minecraft/profile/lookup/name/%s".formatted(username))).build(), new GsonBodyHandler<>(MinecraftProfile.class, new Gson())).body().id();
} catch (IOException | InterruptedException e) {
return null;
}
}

public static class InvalidModrinthIdException extends Exception {

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static List<Object> testObjects() {
"https://i.imgflip.com/4awf2i.jpg",
Set.of("Badge"),
Set.of("bc2+5i"),
Set.of("abcd"),
UserRole.TEAM_MEMBER
),
new EventData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class UserDbTests {
"https://upload.wikimedia.org/wikipedia/commons/thumb/8/87/GOODSMILE_Racing_Komatti-Mirai_EV_TT_Zero.jpg/1920px-GOODSMILE_Racing_Komatti-Mirai_EV_TT_Zero.jpg",
Set.of(),
Set.of(),
Set.of(),
UserRole.TEAM_MEMBER
);

Expand Down