add swagger

This commit is contained in:
Lee 2024-04-10 09:51:31 +01:00
parent d959169f0b
commit 8990a6308a
16 changed files with 63 additions and 13 deletions

21
LICENSE Normal file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024, Fascinated
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.

@ -116,6 +116,14 @@
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
</dependency> </dependency>
<!-- SwaggerUI -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
<scope>compile</scope>
</dependency>
<!-- Unit Tests --> <!-- Unit Tests -->
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>

@ -1,6 +1,10 @@
package cc.fascinated; package cc.fascinated;
import com.google.gson.Gson; import com.google.gson.Gson;
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
import io.swagger.v3.oas.annotations.info.Contact;
import io.swagger.v3.oas.annotations.info.Info;
import io.swagger.v3.oas.annotations.info.License;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
@ -12,7 +16,15 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.Objects; import java.util.Objects;
@SpringBootApplication @Log4j2 @Log4j2
@SpringBootApplication
@OpenAPIDefinition(info = @Info(
title = "Minecraft Utilities API",
version = "1.0",
description = "Wrapper for the Minecraft APIs to make them easier to use.",
contact = @Contact(name = "Liam", email = "liam@fascinated.cc", url = "https://fascinated.cc"),
license = @License(name = "MIT License", url = "https://opensource.org/licenses/MIT")
))
public class Main { public class Main {
public static final Gson GSON = new Gson(); public static final Gson GSON = new Gson();

@ -19,6 +19,7 @@ public class HomeController {
public String home(Model model) { public String home(Model model) {
model.addAttribute("player_example_url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid); model.addAttribute("player_example_url", Config.INSTANCE.getWebPublicUrl() + "/player/" + exampleUuid);
model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/server/java/play.hypixel.net"); model.addAttribute("java_server_example_url", Config.INSTANCE.getWebPublicUrl() + "/server/java/play.hypixel.net");
model.addAttribute("swagger_url", Config.INSTANCE.getWebPublicUrl() + "/swagger-ui.html");
return "index"; return "index";
} }
} }

@ -4,6 +4,7 @@ import cc.fascinated.common.PlayerUtils;
import cc.fascinated.model.player.Player; import cc.fascinated.model.player.Player;
import cc.fascinated.model.player.Skin; import cc.fascinated.model.player.Skin;
import cc.fascinated.service.PlayerService; import cc.fascinated.service.PlayerService;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.CacheControl; import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -14,6 +15,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@RestController @RestController
@Tag(name = "Player Controller", description = "The Player Controller is used to get information about a player.")
@RequestMapping(value = "/player/") @RequestMapping(value = "/player/")
public class PlayerController { public class PlayerController {

@ -3,10 +3,8 @@ package cc.fascinated.controller;
import cc.fascinated.common.ServerUtils; import cc.fascinated.common.ServerUtils;
import cc.fascinated.common.Tuple; import cc.fascinated.common.Tuple;
import cc.fascinated.model.cache.CachedMinecraftServer; import cc.fascinated.model.cache.CachedMinecraftServer;
import cc.fascinated.model.server.JavaMinecraftServer;
import cc.fascinated.model.server.MinecraftServer;
import cc.fascinated.service.ServerService; import cc.fascinated.service.ServerService;
import cc.fascinated.service.pinger.impl.JavaMinecraftServerPinger; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType; import org.springframework.http.MediaType;
@ -14,6 +12,7 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@RestController @RestController
@Tag(name = "Server Controller", description = "The Server Controller is used to get information about a server.")
@RequestMapping(value = "/server/") @RequestMapping(value = "/server/")
public class ServerController { public class ServerController {

@ -1,6 +1,5 @@
package cc.fascinated.model.cache; package cc.fascinated.model.cache;
import cc.fascinated.model.mojang.MojangProfile;
import cc.fascinated.model.player.Cape; import cc.fascinated.model.player.Cape;
import cc.fascinated.model.player.Player; import cc.fascinated.model.player.Player;
import cc.fascinated.model.player.Skin; import cc.fascinated.model.player.Skin;

@ -3,7 +3,6 @@ package cc.fascinated.model.player;
import cc.fascinated.common.Tuple; import cc.fascinated.common.Tuple;
import cc.fascinated.common.UUIDUtils; import cc.fascinated.common.UUIDUtils;
import cc.fascinated.model.mojang.MojangProfile; import cc.fascinated.model.mojang.MojangProfile;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import org.springframework.data.annotation.Id; import org.springframework.data.annotation.Id;

@ -11,7 +11,6 @@ import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import java.awt.image.BufferedImage;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;

@ -1,11 +1,8 @@
package cc.fascinated.repository; package cc.fascinated.repository;
import cc.fascinated.model.cache.CachedPlayer;
import cc.fascinated.model.cache.CachedPlayerName; import cc.fascinated.model.cache.CachedPlayerName;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import java.util.UUID;
/** /**
* A cache repository for player usernames. * A cache repository for player usernames.
* <p> * <p>

@ -44,6 +44,7 @@ public class PlayerService {
*/ */
public CachedPlayer getPlayer(String id) { public CachedPlayer getPlayer(String id) {
id = id.toUpperCase(); // Convert the id to uppercase to prevent case sensitivity id = id.toUpperCase(); // Convert the id to uppercase to prevent case sensitivity
log.info("Getting player: {}", id);
UUID uuid = PlayerUtils.getUuidFromString(id); UUID uuid = PlayerUtils.getUuidFromString(id);
if (uuid == null) { // If the id is not a valid uuid, get the uuid from the username if (uuid == null) { // If the id is not a valid uuid, get the uuid from the username
uuid = usernameToUuid(id); uuid = usernameToUuid(id);
@ -51,11 +52,14 @@ public class PlayerService {
Optional<CachedPlayer> cachedPlayer = playerCacheRepository.findById(uuid); Optional<CachedPlayer> cachedPlayer = playerCacheRepository.findById(uuid);
if (cachedPlayer.isPresent()) { // Return the cached player if it exists if (cachedPlayer.isPresent()) { // Return the cached player if it exists
log.info("Player {} is cached", id);
return cachedPlayer.get(); return cachedPlayer.get();
} }
try { try {
log.info("Getting player profile from Mojang: {}", id);
MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); // Get the player profile from Mojang MojangProfile mojangProfile = mojangAPIService.getProfile(uuid.toString()); // Get the player profile from Mojang
log.info("Got player profile from Mojang: {}", id);
Tuple<Skin, Cape> skinAndCape = mojangProfile.getSkinAndCape(); Tuple<Skin, Cape> skinAndCape = mojangProfile.getSkinAndCape();
CachedPlayer player = new CachedPlayer( CachedPlayer player = new CachedPlayer(
uuid, // Player UUID uuid, // Player UUID
@ -80,6 +84,7 @@ public class PlayerService {
* @return the uuid of the player * @return the uuid of the player
*/ */
private UUID usernameToUuid(String username) { private UUID usernameToUuid(String username) {
log.info("Getting UUID from username: {}", username);
Optional<CachedPlayerName> cachedPlayerName = playerNameCacheRepository.findById(username); Optional<CachedPlayerName> cachedPlayerName = playerNameCacheRepository.findById(username);
if (cachedPlayerName.isPresent()) { if (cachedPlayerName.isPresent()) {
return cachedPlayerName.get().getUniqueId(); return cachedPlayerName.get().getUniqueId();
@ -87,10 +92,12 @@ public class PlayerService {
try { try {
MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username); MojangUsernameToUuid mojangUsernameToUuid = mojangAPIService.getUuidFromUsername(username);
if (mojangUsernameToUuid == null) { if (mojangUsernameToUuid == null) {
log.info("Player with username '{}' not found", username);
throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username)); throw new ResourceNotFoundException("Player with username '%s' not found".formatted(username));
} }
UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId()); UUID uuid = UUIDUtils.addDashes(mojangUsernameToUuid.getId());
playerNameCacheRepository.save(new CachedPlayerName(username, uuid)); playerNameCacheRepository.save(new CachedPlayerName(username, uuid));
log.info("Got UUID from username: {} -> {}", username, uuid);
return uuid; return uuid;
} catch (RateLimitException exception) { } catch (RateLimitException exception) {
throw new MojangAPIRateLimitException(); throw new MojangAPIRateLimitException();

@ -1,7 +1,7 @@
package cc.fascinated.service; package cc.fascinated.service;
import cc.fascinated.common.EnumUtils;
import cc.fascinated.common.DNSUtils; import cc.fascinated.common.DNSUtils;
import cc.fascinated.common.EnumUtils;
import cc.fascinated.exception.impl.BadRequestException; import cc.fascinated.exception.impl.BadRequestException;
import cc.fascinated.exception.impl.ResourceNotFoundException; import cc.fascinated.exception.impl.ResourceNotFoundException;
import cc.fascinated.model.cache.CachedMinecraftServer; import cc.fascinated.model.cache.CachedMinecraftServer;

@ -1,6 +1,8 @@
server: server:
address: 0.0.0.0 address: 0.0.0.0
port: 80 port: 80
servlet:
context-path: /
error: error:
whitelabel: whitelabel:
enabled: false enabled: false

@ -21,7 +21,8 @@
<div class="flex flex-col mt-3"> <div class="flex flex-col mt-3">
<p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${player_example_url}" th:text="${player_example_url}">???</a></p> <p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${player_example_url}" th:text="${player_example_url}">???</a></p>
<p>Java Server: <a class="text-blue-600" target=”_blank” th:href="${java_server_example_url}" th:text="${java_server_example_url}">???</a></p> <p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${java_server_example_url}" th:text="${java_server_example_url}">???</a></p>
<p>Swagger Docs: <a class="text-blue-600" target=”_blank” th:href="${swagger_url}" th:text="${swagger_url}">???</a></p>
</div> </div>
</body> </body>
</html> </html>

@ -1,6 +1,8 @@
server: server:
address: 0.0.0.0 address: 0.0.0.0
port: 80 port: 80
servlet:
context-path: /
error: error:
whitelabel: whitelabel:
enabled: false enabled: false

@ -21,7 +21,8 @@
<div class="flex flex-col mt-3"> <div class="flex flex-col mt-3">
<p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${player_example_url}" th:text="${player_example_url}">???</a></p> <p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${player_example_url}" th:text="${player_example_url}">???</a></p>
<p>Java Server: <a class="text-blue-600" target=”_blank” th:href="${java_server_example_url}" th:text="${java_server_example_url}">???</a></p> <p>Player Data: <a class="text-blue-600" target=”_blank” th:href="${java_server_example_url}" th:text="${java_server_example_url}">???</a></p>
<p>Swagger Docs: <a class="text-blue-600" target=”_blank” th:href="${swagger_url}" th:text="${swagger_url}">???</a></p>
</div> </div>
</body> </body>
</html> </html>