Initial Commit
This commit is contained in:
commit
1de8a8df8c
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.ctxt
|
||||||
|
.mtj.tmp/
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.nar
|
||||||
|
*.ear
|
||||||
|
*.zip
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
.idea
|
||||||
|
cmake-build-*/
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
build/
|
||||||
|
work/
|
||||||
|
target/
|
||||||
|
.idea_modules/
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
git.properties
|
||||||
|
pom.xml.versionsBackup
|
26
Dockerfile
Normal file
26
Dockerfile
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Stage 1: Build the application
|
||||||
|
FROM maven:3.9.9-eclipse-temurin-17-alpine AS builder
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /home/container
|
||||||
|
|
||||||
|
# Copy the current directory contents into the container at /home/container
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Build the jar
|
||||||
|
RUN mvn package -T2C -q -Dmaven.test.skip -DskipTests
|
||||||
|
|
||||||
|
# Stage 2: Create the final lightweight image
|
||||||
|
FROM eclipse-temurin:17.0.12_7-jre-focal
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /home/container
|
||||||
|
|
||||||
|
# Copy the built jar file from the builder stage
|
||||||
|
COPY --from=builder /home/container/target/API.jar .
|
||||||
|
|
||||||
|
# We're running in production
|
||||||
|
ENV APP_ENV "production"
|
||||||
|
|
||||||
|
# Run the jar file
|
||||||
|
CMD java -jar API.jar -Djava.awt.headless=true
|
81
pom.xml
Normal file
81
pom.xml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-parent</artifactId>
|
||||||
|
<version>3.3.3</version>
|
||||||
|
<relativePath/>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<!-- Project Details -->
|
||||||
|
<groupId>me.braydon</groupId>
|
||||||
|
<artifactId>API</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
|
||||||
|
<!-- Properties -->
|
||||||
|
<properties>
|
||||||
|
<java.version>17</java.version>
|
||||||
|
<maven.compiler.source>${java.version}</maven.compiler.source>
|
||||||
|
<maven.compiler.target>${java.version}</maven.compiler.target>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<!-- Build Config -->
|
||||||
|
<build>
|
||||||
|
<finalName>${project.artifactId}</finalName>
|
||||||
|
<plugins>
|
||||||
|
<!-- Spring -->
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>build-info</id>
|
||||||
|
<goals>
|
||||||
|
<goal>build-info</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<additionalProperties>
|
||||||
|
<description>${project.description}</description>
|
||||||
|
</additionalProperties>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<!-- Dependencies -->
|
||||||
|
<dependencies>
|
||||||
|
<!-- Spring -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Libraries -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>1.18.34</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Error Reporting & Metrics -->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>io.sentry</groupId>-->
|
||||||
|
<!-- <artifactId>sentry-spring-boot-starter-jakarta</artifactId>-->
|
||||||
|
<!-- <version>8.0.0-alpha.4</version>-->
|
||||||
|
<!-- <scope>compile</scope>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.questdb</groupId>
|
||||||
|
<artifactId>questdb</artifactId>
|
||||||
|
<version>8.1.1</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
34
src/main/java/cc/pulseapp/api/PulseAPI.java
Normal file
34
src/main/java/cc/pulseapp/api/PulseAPI.java
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package cc.pulseapp.api;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.SneakyThrows;
|
||||||
|
import lombok.extern.log4j.Log4j2;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.StandardCopyOption;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@SpringBootApplication
|
||||||
|
@Log4j2(topic = "PulseApp")
|
||||||
|
public class PulseAPI {
|
||||||
|
@SneakyThrows
|
||||||
|
public static void main(@NonNull String[] args) {
|
||||||
|
// Load the application.yml configuration file
|
||||||
|
File config = new File("application.yml");
|
||||||
|
if (!config.exists()) { // Saving the default config if it doesn't exist locally
|
||||||
|
Files.copy(Objects.requireNonNull(PulseAPI.class.getResourceAsStream("/application.yml")), config.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||||
|
log.info("Saved the default configuration to '{}', please re-launch the application",
|
||||||
|
config.getAbsolutePath()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Found configuration at '{}'", config.getAbsolutePath());
|
||||||
|
SpringApplication.run(PulseAPI.class, args); // Start the app
|
||||||
|
}
|
||||||
|
}
|
20
src/main/java/cc/pulseapp/api/common/EnvironmentUtils.java
Normal file
20
src/main/java/cc/pulseapp/api/common/EnvironmentUtils.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package cc.pulseapp.api.common;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class EnvironmentUtils {
|
||||||
|
/**
|
||||||
|
* Is the app running in a production environment?
|
||||||
|
*/
|
||||||
|
@Getter private static final boolean production;
|
||||||
|
|
||||||
|
static {
|
||||||
|
String appEnv = System.getenv("APP_ENV");
|
||||||
|
production = appEnv != null && (appEnv.equals("production"));
|
||||||
|
}
|
||||||
|
}
|
44
src/main/java/cc/pulseapp/api/common/IPUtils.java
Normal file
44
src/main/java/cc/pulseapp/api/common/IPUtils.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package cc.pulseapp.api.common;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.experimental.UtilityClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@UtilityClass
|
||||||
|
public final class IPUtils {
|
||||||
|
private static final String[] IP_HEADERS = new String[] {
|
||||||
|
"CF-Connecting-IP",
|
||||||
|
"X-Forwarded-For"
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the real IP from the given request.
|
||||||
|
*
|
||||||
|
* @param request the request
|
||||||
|
* @return the real IP
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static String getRealIp(@NonNull HttpServletRequest request) {
|
||||||
|
String ip = request.getRemoteAddr();
|
||||||
|
for (String headerName : IP_HEADERS) {
|
||||||
|
String header = request.getHeader(headerName);
|
||||||
|
if (header == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!header.contains(",")) { // Handle single IP
|
||||||
|
ip = header;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Handle multiple IPs
|
||||||
|
String[] ips = header.split(",");
|
||||||
|
for (String ipHeader : ips) {
|
||||||
|
ip = ipHeader;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package cc.pulseapp.api.exception;
|
||||||
|
|
||||||
|
import cc.pulseapp.api.model.ErrorResponse;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.boot.autoconfigure.web.servlet.error.AbstractErrorController;
|
||||||
|
import org.springframework.boot.web.error.ErrorAttributeOptions;
|
||||||
|
import org.springframework.boot.web.servlet.error.ErrorAttributes;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The route to handle errors for this app.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(value = "/error", produces = MediaType.APPLICATION_JSON_VALUE)
|
||||||
|
public final class ExceptionController extends AbstractErrorController {
|
||||||
|
public ExceptionController(@NonNull ErrorAttributes errorAttributes) {
|
||||||
|
super(errorAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping @ResponseBody @NonNull
|
||||||
|
public ResponseEntity<ErrorResponse> onError(@NonNull HttpServletRequest request) {
|
||||||
|
Map<String, Object> error = getErrorAttributes(request, ErrorAttributeOptions.of(
|
||||||
|
ErrorAttributeOptions.Include.MESSAGE
|
||||||
|
));
|
||||||
|
HttpStatus status = getStatus(request); // The status code
|
||||||
|
return new ResponseEntity<>(new ErrorResponse(status, (String) error.get("message")), status);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package cc.pulseapp.api.exception.impl;
|
||||||
|
|
||||||
|
import lombok.NonNull;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This exception is raised
|
||||||
|
* when a bad request is made.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@ResponseStatus(HttpStatus.BAD_REQUEST)
|
||||||
|
public final class BadRequestException extends RuntimeException {
|
||||||
|
public BadRequestException(@NonNull String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
47
src/main/java/cc/pulseapp/api/log/RequestLogger.java
Normal file
47
src/main/java/cc/pulseapp/api/log/RequestLogger.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package cc.pulseapp.api.log;
|
||||||
|
|
||||||
|
import cc.pulseapp.api.common.IPUtils;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.converter.HttpMessageConverter;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.http.server.ServletServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServletServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Responsible for logging request and
|
||||||
|
* response transactions to the terminal.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@ControllerAdvice
|
||||||
|
@Slf4j(topic = "Req/Res Transaction")
|
||||||
|
public class RequestLogger implements ResponseBodyAdvice<Object> {
|
||||||
|
@Override
|
||||||
|
public boolean supports(@NonNull MethodParameter returnType, @NonNull Class<? extends HttpMessageConverter<?>> converterType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, @NonNull MethodParameter returnType, @NonNull MediaType selectedContentType,
|
||||||
|
@NonNull Class<? extends HttpMessageConverter<?>> selectedConverterType,
|
||||||
|
@NonNull ServerHttpRequest rawRequest, @NonNull ServerHttpResponse rawResponse) {
|
||||||
|
HttpServletRequest request = ((ServletServerHttpRequest) rawRequest).getServletRequest();
|
||||||
|
HttpServletResponse response = ((ServletServerHttpResponse) rawResponse).getServletResponse();
|
||||||
|
|
||||||
|
// Get the request ip ip
|
||||||
|
String ip = IPUtils.getRealIp(request);
|
||||||
|
|
||||||
|
log.info("%s | %s %s %s %s".formatted(
|
||||||
|
ip, request.getMethod(), request.getRequestURI(), request.getProtocol(), response.getStatus()
|
||||||
|
));
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
}
|
43
src/main/java/cc/pulseapp/api/model/ErrorResponse.java
Normal file
43
src/main/java/cc/pulseapp/api/model/ErrorResponse.java
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
package cc.pulseapp.api.model;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.ToString;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A basic response model.
|
||||||
|
*
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@Getter @ToString
|
||||||
|
public class ErrorResponse {
|
||||||
|
/**
|
||||||
|
* The status code of this error.
|
||||||
|
*/
|
||||||
|
@NonNull private final HttpStatus status;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The HTTP code of this error.
|
||||||
|
*/
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The message of this error.
|
||||||
|
*/
|
||||||
|
@NonNull private final String message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp this error occurred.
|
||||||
|
*/
|
||||||
|
@NonNull private final Date timestamp;
|
||||||
|
|
||||||
|
public ErrorResponse(@NonNull HttpStatus status, @NonNull String message) {
|
||||||
|
this.status = status;
|
||||||
|
this.message = message;
|
||||||
|
this.code = status.value();
|
||||||
|
timestamp = new Date();
|
||||||
|
}
|
||||||
|
}
|
36
src/main/java/cc/pulseapp/api/model/User.java
Normal file
36
src/main/java/cc/pulseapp/api/model/User.java
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
package cc.pulseapp.api.model;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Braydon
|
||||||
|
*/
|
||||||
|
@AllArgsConstructor @Getter @EqualsAndHashCode(onlyExplicitlyIncluded = true) @ToString
|
||||||
|
public final class User {
|
||||||
|
/**
|
||||||
|
* The snowflake id of this user.
|
||||||
|
*/
|
||||||
|
@EqualsAndHashCode.Include private final long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This user's username.
|
||||||
|
*/
|
||||||
|
@NonNull private final String username;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The password for this user.
|
||||||
|
*/
|
||||||
|
@NonNull private final String password;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The salt for this user's password.
|
||||||
|
*/
|
||||||
|
@NonNull private final String passwordSalt;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The date this user last logged in.
|
||||||
|
*/
|
||||||
|
@NonNull private final Date lastLogin;
|
||||||
|
}
|
26
src/main/resources/application.yml
Normal file
26
src/main/resources/application.yml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
# Server Configuration
|
||||||
|
server:
|
||||||
|
address: 0.0.0.0
|
||||||
|
port: 7500
|
||||||
|
|
||||||
|
# Log Configuration
|
||||||
|
logging:
|
||||||
|
file:
|
||||||
|
path: "./logs"
|
||||||
|
|
||||||
|
# Sentry Configuration
|
||||||
|
sentry:
|
||||||
|
dsn: "CHANGE_ME"
|
||||||
|
tracesSampleRate: 1.0
|
||||||
|
environment: "development"
|
||||||
|
|
||||||
|
# QuestDB Configuration (Metrics)
|
||||||
|
questdb:
|
||||||
|
enabled: false
|
||||||
|
uri: "http::addr=localhost:9000;username=tether;password=p4$$w0rd;auto_flush_interval=5000;"
|
||||||
|
|
||||||
|
# Spring Configuration
|
||||||
|
spring:
|
||||||
|
# Ignore
|
||||||
|
banner:
|
||||||
|
location: "classpath:banner.txt"
|
12
src/main/resources/banner.txt
Normal file
12
src/main/resources/banner.txt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
_____ _ _____ _____
|
||||||
|
| __ \ | | /\ /\ | __ \_ _|
|
||||||
|
| |__) | _| |___ ___ / \ _ __ _ __ / \ | |__) || |
|
||||||
|
| ___/ | | | / __|/ _ \ / /\ \ | '_ \| '_ \ / /\ \ | ___/ | |
|
||||||
|
| | | |_| | \__ \ __// ____ \| |_) | |_) | / ____ \| | _| |_
|
||||||
|
|_| \__,_|_|___/\___/_/ \_\ .__/| .__/ /_/ \_\_| |_____|
|
||||||
|
| | | |
|
||||||
|
|_| |_|
|
||||||
|
|
||||||
|
| API Version - v${application.version}
|
||||||
|
| Spring Version - ${spring-boot.formatted-version}
|
||||||
|
______________________________________________________________________
|
Loading…
x
Reference in New Issue
Block a user