Skip to content

Commit 90528df

Browse files
committed
Start adding frontend (auth flow, server setup console page)
1 parent ee25322 commit 90528df

41 files changed

Lines changed: 4309 additions & 47 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/build.gradle.kts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ dependencies {
2323
implementation("io.micronaut.data:micronaut-data-jdbc")
2424
implementation("io.micronaut.flyway:micronaut-flyway")
2525
implementation("io.micronaut.security:micronaut-security-oauth2")
26+
implementation("io.micronaut.security:micronaut-security-jwt")
27+
implementation("io.micronaut.security:micronaut-security")
2628
implementation("io.micronaut.serde:micronaut-serde-jackson")
2729
implementation("io.micronaut.sql:micronaut-jdbc-hikari")
2830
implementation("io.micronaut.reactor:micronaut-reactor")
2931

32+
implementation("com.github.docker-java:docker-java:3.7.1")
33+
implementation("com.github.docker-java:docker-java-transport-httpclient5:3.7.1")
34+
3035
// discord
3136
implementation("io.github.kaktushose:jda-commands:5.0.0") {
3237
exclude("org.slf4j", "slf4j-simple")
@@ -89,8 +94,6 @@ micronaut {
8994
deduceEnvironment = true
9095
optimizeNetty = true
9196
replaceLogbackXml = true
92-
configurationProperties.put("micronaut.security.jwks.enabled", "false")
93-
configurationProperties.put("micronaut.security.openid-configuration.enabled", "false")
9497
}
9598

9699
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package de.chojo.pluginjam.bot.config;
2+
3+
import io.micronaut.context.annotation.ConfigurationProperties;
4+
5+
@ConfigurationProperties("docker")
6+
public class DockerConfig {
7+
private String host = "unix:///var/run/docker.sock";
8+
private String certPath = "/home/user/.docker";
9+
private boolean tlsVerify = false;
10+
private String registryUsername;
11+
private String registryPassword;
12+
private String registryEmail;
13+
private String registryUrl;
14+
private String networkName = "plugin-jam-network";
15+
private String teamServerImage = "plugin-jam-mc-server:latest";
16+
17+
public String getHost() {
18+
return host;
19+
}
20+
21+
public String getCertPath() {
22+
return certPath;
23+
}
24+
25+
public boolean isTlsVerify() {
26+
return tlsVerify;
27+
}
28+
29+
public String getRegistryUsername() {
30+
return registryUsername;
31+
}
32+
33+
public String getRegistryPassword() {
34+
return registryPassword;
35+
}
36+
37+
public String getRegistryEmail() {
38+
return registryEmail;
39+
}
40+
41+
public String getRegistryUrl() {
42+
return registryUrl;
43+
}
44+
45+
public String getNetworkName() {
46+
return networkName;
47+
}
48+
49+
public String getTeamServerImage() {
50+
return teamServerImage;
51+
}
52+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/*
2+
* SPDX-License-Identifier: AGPL-3.0-only
3+
*
4+
* Copyright (C) 2022 DevCord Team and Contributor
5+
*/
6+
7+
package de.chojo.pluginjam.bot.config;
8+
9+
import io.micronaut.context.annotation.ConfigurationProperties;
10+
11+
import java.util.List;
12+
13+
@ConfigurationProperties("plugins")
14+
public class PluginsConfig {
15+
private List<String> defaultPlugins = List.of();
16+
17+
public List<String> getDefaultPlugins() {
18+
return defaultPlugins;
19+
}
20+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package de.chojo.pluginjam.controller;
2+
3+
import de.chojo.pluginjam.database.entity.team.Team;
4+
import de.chojo.pluginjam.service.ServerService;
5+
import de.chojo.pluginjam.service.TeamService;
6+
import io.micronaut.http.annotation.Header;
7+
import io.micronaut.security.annotation.Secured;
8+
import io.micronaut.security.authentication.Authentication;
9+
import io.micronaut.security.rules.SecurityRule;
10+
import io.micronaut.websocket.WebSocketSession;
11+
import io.micronaut.websocket.annotation.OnClose;
12+
import io.micronaut.websocket.annotation.OnOpen;
13+
import io.micronaut.websocket.annotation.ServerWebSocket;
14+
import org.slf4j.Logger;
15+
import reactor.core.Disposable;
16+
17+
import java.util.Map;
18+
import java.util.Optional;
19+
import java.util.concurrent.ConcurrentHashMap;
20+
21+
import static org.slf4j.LoggerFactory.getLogger;
22+
23+
@ServerWebSocket("/api/server/logs/ws")
24+
@Secured(SecurityRule.IS_AUTHENTICATED)
25+
public class LogWebSocket {
26+
private static final Logger log = getLogger(LogWebSocket.class);
27+
private final ServerService serverService;
28+
private final TeamService teamService;
29+
private final Map<String, Disposable> subscriptions = new ConcurrentHashMap<>();
30+
31+
public LogWebSocket(ServerService serverService, TeamService teamService) {
32+
this.serverService = serverService;
33+
this.teamService = teamService;
34+
}
35+
36+
@OnOpen
37+
public void onOpen(WebSocketSession session, Authentication authentication) {
38+
log.info("[DEBUG_LOG] WebSocket onOpen triggered for session: {}", session.getId());
39+
if (authentication == null) {
40+
log.warn("[DEBUG_LOG] No authentication found for session: {}", session.getId());
41+
session.close();
42+
return;
43+
}
44+
Optional<Team> team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
45+
if (team.isEmpty()) {
46+
log.warn("[DEBUG_LOG] No team found for user: {}", authentication.getName());
47+
session.close();
48+
return;
49+
}
50+
51+
log.info("Opening log websocket for team {} (user {})", team.get().id(), authentication.getName());
52+
53+
Disposable subscription = serverService.dockerService().streamLogs(team.get().id(), 100)
54+
.repeat()
55+
.retryWhen(reactor.util.retry.Retry.fixedDelay(Long.MAX_VALUE, java.time.Duration.ofSeconds(5)))
56+
.subscribe(event -> {
57+
if (session.isOpen()) {
58+
session.sendSync(event.getData());
59+
}
60+
}, throwable -> {
61+
log.error("[DEBUG_LOG] Permanent error in log stream for team {}", team.get().id(), throwable);
62+
session.close();
63+
});
64+
65+
subscriptions.put(session.getId(), subscription);
66+
}
67+
68+
@io.micronaut.websocket.annotation.OnMessage
69+
public void onMessage(String message, WebSocketSession session) {
70+
// Not used
71+
}
72+
73+
@OnClose
74+
public void onClose(WebSocketSession session) {
75+
log.info("Closing log websocket for session {}", session.getId());
76+
Disposable subscription = subscriptions.remove(session.getId());
77+
if (subscription != null && !subscription.isDisposed()) {
78+
subscription.dispose();
79+
}
80+
}
81+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package de.chojo.pluginjam.controller;
2+
3+
import de.chojo.pluginjam.model.PowerSignalDTO;
4+
import de.chojo.pluginjam.model.ServerStatus;
5+
import de.chojo.pluginjam.service.ServerService;
6+
import de.chojo.pluginjam.service.TeamService;
7+
import io.micronaut.http.HttpResponse;
8+
import io.micronaut.http.annotation.Body;
9+
import io.micronaut.http.annotation.Controller;
10+
import io.micronaut.http.annotation.Get;
11+
import io.micronaut.http.annotation.Post;
12+
import io.micronaut.security.annotation.Secured;
13+
import io.micronaut.security.authentication.Authentication;
14+
import io.micronaut.security.rules.SecurityRule;
15+
16+
@Controller("api/server")
17+
@Secured(SecurityRule.IS_AUTHENTICATED)
18+
public class ServerController {
19+
private final ServerService serverService;
20+
private final TeamService teamService;
21+
22+
public ServerController(ServerService serverService, TeamService teamService) {
23+
this.serverService = serverService;
24+
this.teamService = teamService;
25+
}
26+
27+
@Get("/status/")
28+
public HttpResponse<ServerStatus> getStatus(Authentication authentication) {
29+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
30+
if (team.isEmpty()) {
31+
return HttpResponse.notAllowed();
32+
}
33+
34+
return HttpResponse.ok(serverService.dockerService().serverStatus(team.get().id()));
35+
}
36+
37+
@Get("/exists")
38+
public HttpResponse<java.util.Map<String, Object>> exists(Authentication authentication) {
39+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
40+
if (team.isEmpty()) {
41+
return HttpResponse.notAllowed();
42+
}
43+
44+
boolean exists = serverService.dockerService().exists(team.get().id());
45+
boolean provisioning = serverService.isProvisioning(team.get().id());
46+
47+
return HttpResponse.ok(java.util.Map.of(
48+
"exists", exists,
49+
"provisioning", provisioning
50+
));
51+
}
52+
53+
@Post("/provision")
54+
public HttpResponse<Void> provision(Authentication authentication) {
55+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
56+
if (team.isEmpty()) {
57+
return HttpResponse.notAllowed();
58+
}
59+
serverService.provisionServer(team.get().id());
60+
return HttpResponse.accepted();
61+
}
62+
63+
@Post("/reinstall")
64+
public HttpResponse<Void> reinstall(Authentication authentication) {
65+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
66+
if (team.isEmpty()) {
67+
return HttpResponse.notAllowed();
68+
}
69+
serverService.reinstallServer(team.get().id());
70+
return HttpResponse.accepted();
71+
}
72+
73+
@Post("/power")
74+
public HttpResponse<Void> powerSignal(Authentication authentication, @Body PowerSignalDTO powerSignal) {
75+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
76+
if (team.isEmpty()) {
77+
return HttpResponse.notAllowed();
78+
}
79+
System.out.println("[DEBUG_LOG] Received power signal: " + powerSignal.signal() + " for team: " + team.get().id());
80+
serverService.handlePowerSignal(powerSignal, team.get().id());
81+
return HttpResponse.ok();
82+
}
83+
84+
@Post("/command")
85+
public HttpResponse<Void> sendCommand(Authentication authentication, @Body java.util.Map<String, String> body) {
86+
var team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
87+
if (team.isEmpty()) {
88+
return HttpResponse.notAllowed();
89+
}
90+
String command = body.get("command");
91+
if (command == null || command.isBlank()) {
92+
return HttpResponse.badRequest();
93+
}
94+
serverService.dockerService().sendCommand(team.get().id(), command);
95+
return HttpResponse.ok();
96+
}
97+
98+
}
Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
11
package de.chojo.pluginjam.controller;
22

3+
import de.chojo.pluginjam.database.entity.team.Team;
4+
import de.chojo.pluginjam.service.TeamService;
5+
import io.micronaut.http.HttpResponse;
36
import io.micronaut.http.annotation.Controller;
7+
import io.micronaut.http.annotation.Get;
8+
import io.micronaut.security.annotation.Secured;
9+
import io.micronaut.security.authentication.Authentication;
10+
import io.micronaut.security.rules.SecurityRule;
411

5-
@Controller("api/v1/teams")
12+
import java.util.Optional;
13+
14+
@Controller("api/teams")
15+
@Secured(SecurityRule.IS_AUTHENTICATED)
616
public class TeamController {
17+
private final TeamService teamService;
18+
19+
public TeamController(TeamService teamService) {
20+
this.teamService = teamService;
21+
}
722

23+
@Get("/my-team")
24+
public HttpResponse<Team> getMyTeam(Authentication authentication) {
25+
Optional<Team> team = teamService.getUserTeam(Long.parseLong(authentication.getName()));
26+
return team.map(HttpResponse::ok).orElseGet(HttpResponse::notFound);
27+
}
828
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package de.chojo.pluginjam.controller;
2+
3+
import io.micronaut.http.annotation.Controller;
4+
import io.micronaut.http.annotation.Get;
5+
import io.micronaut.security.annotation.Secured;
6+
import io.micronaut.security.authentication.Authentication;
7+
import io.micronaut.security.rules.SecurityRule;
8+
9+
import java.util.Map;
10+
11+
@Controller("/api/user")
12+
public class UserController {
13+
14+
@Get("/me")
15+
@Secured(SecurityRule.IS_AUTHENTICATED)
16+
public Map<String, Object> getCurrentUser(Authentication authentication) {
17+
return Map.of(
18+
"name", authentication.getAttributes().getOrDefault("name", "Unknown"),
19+
"picture", authentication.getAttributes().getOrDefault("picture", ""),
20+
"id", authentication.getName()
21+
);
22+
}
23+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package de.chojo.pluginjam.model;
2+
3+
import io.micronaut.serde.annotation.Serdeable;
4+
5+
@Serdeable
6+
public class PowerSignalDTO {
7+
public enum Signal {
8+
START, STOP, RESTART
9+
}
10+
11+
private Signal signal;
12+
13+
public Signal signal() {
14+
return signal;
15+
}
16+
17+
public void setSignal(Signal signal) {
18+
this.signal = signal;
19+
}
20+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package de.chojo.pluginjam.model;
2+
3+
public class Server {
4+
private int id;
5+
private String containerName;
6+
private ServerStatus status;
7+
8+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* SPDX-License-Identifier: AGPL-3.0-only
3+
*
4+
* Copyright (C) 2022 DevCord Team and Contributor
5+
*/
6+
7+
package de.chojo.pluginjam.model;
8+
9+
public enum ServerStatus {
10+
STARTING_STOPPING("⏳"),
11+
RUNNING("🟢"),
12+
STOPPED("🔴"),
13+
VOID("❌");
14+
15+
private final String emoji;
16+
ServerStatus(String emoji) {
17+
this.emoji = emoji;
18+
}
19+
20+
public String emoji() {
21+
return emoji;
22+
}
23+
}

0 commit comments

Comments
 (0)