Skip to content

Commit

Permalink
Merge pull request #40 from BlazingTwist/18-support-docker-containers
Browse files Browse the repository at this point in the history
18 support docker containers
  • Loading branch information
BlazingTwist authored Jan 11, 2024
2 parents cbf787b + b054402 commit 4e7526b
Show file tree
Hide file tree
Showing 16 changed files with 157 additions and 50 deletions.
11 changes: 11 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
FROM maven:3-amazoncorretto-21 as build
COPY src/main/java src/main/java
COPY src/main/resources src/main/resources
COPY src/test src/test
COPY pom.xml pom.xml
RUN mvn package -f pom.xml

FROM amazoncorretto:21
COPY --from=build /target/jchess.jar /app/jchess.jar
COPY --from=build /target/lib /app/lib
CMD ["java", "-jar", "/app/jchess.jar"]
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
Hier wird das Projekt allgemein und das Backend beschrieben.
Siehe auch: [Frontend Dokumentation](./src/main/jchess-web/README.md)

### Server start/stop
```bash
# starts both the Backend and Frontend.
# Will be available at 'localhost:3000'
docker-compose up -d

# stops the servers
docker-compose down
```

---

## Entwicklerhilfen
Expand Down
17 changes: 17 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
services:
backend:
build: .
ports:
- "8880:8880"
expose:
- "8880"
environment:
- IS_DOCKER=true
frontend:
build:
context: ./src/main/
dockerfile: ./jchess-web/Dockerfile
args:
backend_port: 8880
ports:
- "3000:3000"
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
<mainClass>jchess.server.WipExampleServer</mainClass>
<mainClass>jchess.server.JChessServer</mainClass>
</manifest>
</archive>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,38 @@
import jchess.server.api.servlet.GameModesServlet;
import jchess.server.api.servlet.ThemesServlet;
import jchess.server.api.socket.BoardUpdateWebsocket;
import jchess.server.api.socket.PieceSelectionWebsocket;
import jchess.server.api.socket.ChatWebsocket;
import jchess.server.api.socket.PieceSelectionWebsocket;
import jchess.server.session.SessionManager;
import jchess.server.session.SessionMgrController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;

public class WipExampleServer {
private static final Logger logger = LoggerFactory.getLogger(WipExampleServer.class);
public class JChessServer {
private static final Logger logger = LoggerFactory.getLogger(JChessServer.class);

public static final String resourcePrefix = "resources";

public static BoardUpdateWebsocket boardUpdateWebsocket;
public static PieceSelectionWebsocket pieceSelectionWebsocket;

public static void main(String[] args) throws ServletException, URISyntaxException {
public static void main(String[] args) throws ServletException, URISyntaxException, UnknownHostException {
boardUpdateWebsocket = new BoardUpdateWebsocket();
pieceSelectionWebsocket = new PieceSelectionWebsocket();
ChatWebsocket chatWebsocket = new ChatWebsocket();

SessionMgrController.registerSessionManager(GameSessionData.class, 10, TimeUnit.MINUTES);
SessionMgrController.startHeartbeat(1, TimeUnit.MINUTES);

ClassPathResourceManager resourceManager = new ClassPathResourceManager(WipExampleServer.class.getClassLoader());
ClassPathResourceManager resourceManager = new ClassPathResourceManager(JChessServer.class.getClassLoader());

DeploymentInfo deployment = Servlets.deployment()
.setClassLoader(WipExampleServer.class.getClassLoader())
.setClassLoader(JChessServer.class.getClassLoader())
.setContextPath("")
.setDeploymentName("WipChessServer")
.addServlet(Servlets.servlet("GameCreate", GameCreateServlet.class).addMapping("/api/game/create"))
Expand All @@ -66,8 +68,12 @@ public static void main(String[] args) throws ServletException, URISyntaxExcepti
.addPrefixPath("/api/pieceSelection", Handlers.websocket(pieceSelectionWebsocket))
.addPrefixPath("/api/chat", Handlers.websocket(chatWebsocket));

final String isDocker = System.getenv("IS_DOCKER");
final String address = (isDocker == null || isDocker.isEmpty()) ? "localhost" : InetAddress.getLocalHost().getHostAddress();
final int port = 8880;
logger.info("Listening on {}:{}", address, port);
Undertow server = Undertow.builder()
.addHttpListener(8880, "127.0.0.1")
.addHttpListener(port, address)
.setHandler(pathHandler)
.build();
server.start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jchess.gamemode.GameMode;
import jchess.server.WipExampleServer;
import jchess.server.JChessServer;
import jchess.server.util.HttpUtils;
import jchess.server.util.JsonUtils;
import org.slf4j.Logger;
Expand All @@ -32,7 +32,7 @@ protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws I

final String sessionId;
try {
sessionId = WipExampleServer.startNewGame(gameMode);
sessionId = JChessServer.startNewGame(gameMode);
} catch (Exception e) {
logger.warn("Failed to start new game.", e);
HttpUtils.error(resp, StatusCodes.INTERNAL_SERVER_ERROR, "Failed to start new game. Exception: " + e.getMessage());
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/jchess/server/api/servlet/ThemesServlet.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jchess.server.WipExampleServer;
import jchess.server.JChessServer;
import jchess.server.adapter.Vector2IAdapter;
import jchess.server.util.JsonUtils;
import org.slf4j.Logger;
Expand All @@ -26,7 +26,7 @@ public class ThemesServlet extends HttpServlet {
private static final Logger logger = LoggerFactory.getLogger(ThemesServlet.class);
private static final Map<String, Map<String, String>> themeMap = new HashMap<>();
private static final Map<String, TileInfo> themeTileInfoMap = new HashMap<>();
private static final String resourcePrefix = WipExampleServer.resourcePrefix;
private static final String resourcePrefix = JChessServer.resourcePrefix;

public ThemesServlet() {
try {
Expand Down
3 changes: 1 addition & 2 deletions src/main/jchess-web/.env.example
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
NEXT_PUBLIC_JCHESS_UNDERTOW_SERVER_URI=http://127.0.0.1:8880
NEXT_PUBLIC_JCHESS_SOCKET_URI=ws://localhost:8880
NEXT_PUBLIC_CLIENT_URI=http://localhost:3000
NEXT_PUBLIC_BOARD_WITH_COORDINATES=false
NEXT_PUBLIC_LOCAL_STORAGE=false
NEXT_PUBLIC_LOCAL_STORAGE=false
80 changes: 80 additions & 0 deletions src/main/jchess-web/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
ARG backend_port

# https://github.com/vercel/next.js/blob/canary/examples/with-docker/Dockerfile
FROM node:18-alpine AS base


# Rebuild the source code only when needed
FROM base AS builder
ARG backend_port
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app

COPY jchess-web/package.json jchess-web/package-lock.json* ./
COPY ./jchess-web/app app
COPY ./jchess-web/components components
COPY ./jchess-web/pages pages
COPY ./jchess-web/public public
COPY ./jchess-web/services services
COPY ./jchess-web/utils utils
COPY ./jchess-web/.eslintrc.json .eslintrc.json
COPY ./jchess-web/components.json components.json
COPY ./jchess-web/next.config.js next.config.js
COPY ./jchess-web/postcss.config.js postcss.config.js
COPY ./jchess-web/tailwind.config.ts tailwind.config.ts
COPY ./jchess-web/tsconfig.json tsconfig.json
COPY ./resources/dx ./resources/dx

RUN npm i
RUN npm install -g json-schema-to-typescript
RUN json2ts -i ./resources/dx/schema/ -o ./models --cwd ./resources/dx/schema/types

# Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1

# see .env.example
ENV NEXT_PUBLIC_JCHESS_UNDERTOW_SERVER_URI "http://backend:$backend_port"
# verwirrend, aber: http Requests werden auf dem Server aufgelöst, Sockets auf dem Client
# -> PROBLEM: dann stimmt die Addresse hier natürlich nur, wenn man den Server selbst hostet... (TODO erja)
ENV NEXT_PUBLIC_JCHESS_SOCKET_URI "ws://localhost:$backend_port"
ENV NEXT_PUBLIC_BOARD_WITH_COORDINATES "false"
ENV NEXT_PUBLIC_LOCAL_STORAGE "false"

RUN npm run build

# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app

ENV NODE_ENV production
# Uncomment the following line in case you want to disable telemetry during runtime.
# ENV NEXT_TELEMETRY_DISABLED 1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public

# Set the correct permission for prerender cache
RUN mkdir .next
RUN chown nextjs:nodejs .next

# Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000

ENV PORT 3000
# set hostname to localhost
ENV HOSTNAME "0.0.0.0"

# server.js is created by next build from the standalone output
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
CMD ["node", "server.js"]
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export default function GameComponment({ sessionId }: { sessionId: string }) {
translationString: string
) {
const canvas: JSX.Element[] = [];
const serverUri = Config.clientUri + "/api/";
const offsetWidthFromCanvasRef = canvasRef.current?.offsetWidth || 1;
const rawBoardWidth = theme!.tileAspectRatio!.x + theme!.tileStride!.x * (maxTilePos[0] - minTilePos[0]);
let scaleFactor = offsetWidthFromCanvasRef / rawBoardWidth;
Expand All @@ -142,7 +141,7 @@ export default function GameComponment({ sessionId }: { sessionId: string }) {
>
<img
className="absolute w-full h-full"
src={serverUri + iconPath}
src={"api/" + iconPath}
onClick={() => {
console.log("Clicked: " + tileKey);

Expand Down Expand Up @@ -186,7 +185,7 @@ export default function GameComponment({ sessionId }: { sessionId: string }) {
transform: translationString,
pointerEvents: "none",
}}
src={serverUri + iconPath}
src={"api/" + iconPath}
/>
</div>
);
Expand All @@ -213,7 +212,7 @@ export default function GameComponment({ sessionId }: { sessionId: string }) {
height: theme!.tileAspectRatio!.y * scaleFactor,
pointerEvents: "none",
}}
src={serverUri + iconPath}
src={"api/" + iconPath}
></img>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export default function PieceSelectionComponent({
sessionId: string;
iconMap: { [key: string]: string };
}) {
const serverUri = Config.clientUri + "/api/";

const [pieceSelectionOffer, setPieceSelectionOffer] = useState<OfferPieceSelection | undefined>(undefined);

// Websocket
Expand Down Expand Up @@ -84,7 +82,7 @@ export default function PieceSelectionComponent({
}
}}
>
<img src={serverUri + iconPath} />
<img src={"api/" + iconPath} />
</div>
</div>
</CarouselItem>
Expand Down
11 changes: 6 additions & 5 deletions src/main/jchess-web/components/ui/carousel.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import * as React from "react";
import useEmblaCarousel, {
type EmblaCarouselType as CarouselApi,
type EmblaOptionsType as CarouselOptions,
type EmblaPluginType as CarouselPlugin,
} from "embla-carousel-react";
import { ArrowLeft, ArrowRight } from "lucide-react";

import { cn } from "@/utils/tailwindMergeUtils";
import { Button } from "@/components/ui/button";
import {
type EmblaOptionsType as CarouselOptions,
type EmblaCarouselType as CarouselApi,
type EmblaPluginType as CarouselPlugin,
} from "embla-carousel";
import useEmblaCarousel from "embla-carousel-react";

type CarouselProps = {
opts?: CarouselOptions;
Expand Down
1 change: 1 addition & 0 deletions src/main/jchess-web/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: 'standalone',
};

module.exports = nextConfig;
Expand Down
1 change: 1 addition & 0 deletions src/main/jchess-web/public/.keep
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
// Ordner ist aktuell leer, aber die Dockerfile schlägt fehl, wenn er fehlt.....
23 changes: 8 additions & 15 deletions src/main/jchess-web/services/rest_api_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,27 @@ import { GameClicked } from "@/models/message/GameClicked.schema";
import { GameCreate } from "@/models/message/GameCreate.schema";
import { GameModes } from "@/models/message/GameModes.schema";
import { Themes } from "@/models/message/Themes.schema";
import { Theme } from "@/models/types/Theme.schema";
import Config from "@/utils/config";

/**
* @function postCreateGame
* @description Creates a new game on the server.
* @returns A promise that resolves to the session id of the new game.
*/
export async function postCreateGame(gameCreateBody: GameCreate): Promise<string> {
const serverUri = Config.clientUri;
const response = await fetch(`${serverUri}/api/game/create`, {
const response = await fetch(`api/game/create`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(gameCreateBody),
});
if (!response.ok) {
throw new Error(`Error fetching ${serverUri}/api/game/create: ${response.status} ${response.statusText}`);
throw new Error(`Error fetching api/game/create: ${response.status} ${response.statusText}`);
}
try {
const data = await response.json();
return data;
return await response.json();
} catch (error) {
console.error(`Error fetching ${serverUri}/api/game/create:`, error);
console.error(`Error fetching api/game/create:`, error);
throw error;
}
}
Expand All @@ -37,16 +33,15 @@ export async function postCreateGame(gameCreateBody: GameCreate): Promise<string
* @param body - The click to send.
*/
export async function postClick(body: GameClicked): Promise<void> {
const serverUri = Config.clientUri;
const response = await fetch(`${serverUri}/api/game/clicked`, {
const response = await fetch(`api/game/clicked`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new Error(`Error fetching ${serverUri}/api/game/create: ${response.status} ${response.statusText}`);
throw new Error(`Error fetching api/game/create: ${response.status} ${response.statusText}`);
}
}

Expand Down Expand Up @@ -74,14 +69,12 @@ export async function fetchGameModes(): Promise<GameModes> {
* @throws An error if the fetch request fails or if there is an error parsing the response.
*/
export async function fetchData<T>(endpoint: string): Promise<T> {
const serverUri = Config.clientUri;
const response = await fetch(`${serverUri}/api/${endpoint}`);
const response = await fetch(`api/${endpoint}`);
if (!response.ok) {
throw new Error(`Error fetching ${endpoint}: ${response.status} ${response.statusText}`);
}
try {
const data = await response.json();
return data;
return await response.json();
} catch (error) {
console.error(`Error fetching ${endpoint}:`, error);
throw error;
Expand Down
Loading

0 comments on commit 4e7526b

Please sign in to comment.