diff --git a/.env.example b/.env.example index b86b39a..2f46d8c 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,8 @@ -VITE_PORT=8081 \ No newline at end of file +VITE_PORT=8081 + +# Server database environment variables +DB_NAME=area +DB_PASSWORD=change-me +DB_HOST=mariadb +DB_PORT=3306 +DB_USER=root \ No newline at end of file diff --git a/.gitignore b/.gitignore index e4c9141..93ed00d 100644 --- a/.gitignore +++ b/.gitignore @@ -136,3 +136,5 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +ssl/ diff --git a/Makefile b/Makefile index 35c5e34..f9aec34 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ TARGET_MAX_CHAR_NUM=20 .PHONY: start build stop restart reset logs clean help -PROJECT_IMAGES = area-client-web area-client-mobile +PROJECT_IMAGES = area-client-web area-client-mobile area-server mariadb rabbitmq ## Show help help: @@ -56,4 +56,16 @@ logs: ## Clean up containers, images, volumes and orphans clean: - docker compose down --rmi local -v --remove-orphans \ No newline at end of file + docker compose down --rmi local -v --remove-orphans + +# Flutter mobile client commands +flutter-build: + docker build -t flutter-app ./client_mobile + +flutter-run: + docker run -it \ + --network host \ + -v $(PWD)/client_mobile:/app \ + -v /tmp/.X11-unix:/tmp/.X11-unix \ + -e DISPLAY=${DISPLAY} \ + flutter-app \ No newline at end of file diff --git a/README.md b/README.md index fc47963..2560090 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,11 @@ With AREA, you can create automated workflows that integrate various services an ## Table of Contents - [Getting Started](#getting-started) - - [Prerequisites](#prerequisites) - - [Installation & Usage](#installation--usage) + - [Prerequisites](#prerequisites) + - [Installation & Usage](#installation--usage) - [Documentation](#documentation) - - [Requirements](#requirements) - - [Usage](#usage) + - [Requirements](#requirements) + - [Usage](#usage) - [Tests](#tests) - [License](#license) - [Contributors](#contributors) @@ -19,9 +19,8 @@ With AREA, you can create automated workflows that integrate various services an ### Prerequisites -- vite js -- go - docker +- make ### Installation & Usage @@ -29,40 +28,30 @@ With AREA, you can create automated workflows that integrate various services an Click to expand 1. Clone the repo + ```sh git clone git@github.com:ASM-Studios/AREA.git ``` 2. Create .env files + - Run the following command to create private env files + ```sh cp .env.example .env +cp server/.env.server.example server/.env.server cp client_web/.env.local.example .env.local cp client_mobile/.env.mobile.example .env.mobile ``` + - Fill the .env, .env.web and .env.mobile files -3. Install NPM packages -```sh -cd AREA/client-web -npm install -``` +4. Run the project -3. Install Go packages ```sh -cd AREA/server - +make start ``` -4. Run the project -```sh -cd AREA/client-web -npm run start -``` -```sh -cd AREA/server -go run ./... -``` ### Documentation @@ -87,6 +76,7 @@ The documentation is automatically built and deployed to GitHub Pages when a pus You can consult the documentation online at [AREA Documentation](https://asm-studios.github.io/AREA/). You can build the documentation locally by running the following command: + ```sh cd AREA/docs make docs diff --git a/client_mobile/.env.mobile.example b/client_mobile/.env.mobile.example index 965d1d9..e538184 100644 --- a/client_mobile/.env.mobile.example +++ b/client_mobile/.env.mobile.example @@ -1,17 +1,3 @@ -VITE_PORT=8081 -VITE_ENDPOINT=http://localhost:8080 - -VITE_GOOGLE_CLIENT_ID= -VITE_GOOGLE_CLIENT_SECRET= - -VITE_MICROSOFT_CLIENT_ID= - -VITE_LINKEDIN_CLIENT_ID= -VITE_LINKEDIN_CLIENT_SECRET= - -VITE_SPOTIFY_CLIENT_ID= -VITE_SPOTIFY_CLIENT_SECRET= - # Server URLs API_URL=http://localhost:8080 WEB_CLIENT_URL=http://localhost:8081 diff --git a/client_mobile/Dockerfile b/client_mobile/Dockerfile index 778bcff..824acdd 100644 --- a/client_mobile/Dockerfile +++ b/client_mobile/Dockerfile @@ -1,26 +1,22 @@ -FROM ghcr.io/cirruslabs/flutter:stable +FROM ghcr.io/cirruslabs/flutter:3.24.5 AS builder WORKDIR /app -ARG VITE_PORT -ARG VITE_ENDPOINT -ARG VITE_GOOGLE_CLIENT_ID -ARG VITE_GOOGLE_CLIENT_SECRET -ARG VITE_MICROSOFT_CLIENT_ID -ARG VITE_LINKEDIN_CLIENT_ID -ARG VITE_LINKEDIN_CLIENT_SECRET -ARG VITE_SPOTIFY_CLIENT_ID -ARG VITE_SPOTIFY_CLIENT_SECRET +RUN git config --system --add safe.directory /sdks/flutter && \ + git config --system --add safe.directory /app && \ + chmod -R 777 /sdks/flutter + +COPY pubspec.* ./ + +RUN flutter pub get + +COPY . . + ARG API_URL ARG WEB_CLIENT_URL ARG MOBILE_CLIENT_URL ARG GITHUB_CLIENT_ID ARG GITHUB_CLIENT_SECRET -COPY . . - -RUN flutter pub get RUN flutter build apk --release - -RUN mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/client.apk -RUN chmod -R 755 build/app/outputs/flutter-apk/ \ No newline at end of file +RUN mv build/app/outputs/flutter-apk/app-release.apk build/app/outputs/flutter-apk/client.apk \ No newline at end of file diff --git a/client_mobile/pubspec.yaml b/client_mobile/pubspec.yaml index a5640fa..248ced5 100644 --- a/client_mobile/pubspec.yaml +++ b/client_mobile/pubspec.yaml @@ -19,7 +19,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: '>=3.4.1 <4.0.0' + sdk: '^3.5.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/client_web/Dockerfile b/client_web/Dockerfile index 2d06b21..cbc4058 100644 --- a/client_web/Dockerfile +++ b/client_web/Dockerfile @@ -1,25 +1,26 @@ ###----------------------- Certificate generation stage -----------------------### -FROM alpine:3.19 AS cert-builder +FROM alpine:3.20.3 AS cert-builder -# Install mkcert dependencies -RUN apk add --no-cache \ - curl \ - nss \ - nss-tools - -# Install mkcert -RUN curl -JLO "https://dl.filippo.io/mkcert/latest?for=linux/amd64" \ - && chmod +x mkcert-v*-linux-amd64 \ - && mv mkcert-v*-linux-amd64 /usr/local/bin/mkcert \ - && mkcert -install \ - && mkcert localhost +RUN apk add --no-cache openssl +RUN mkdir -p /etc/nginx/ssl && \ + openssl req -x509 -nodes -days 365 -newkey rsa:2048 \ + -keyout /etc/nginx/ssl/private.key \ + -out /etc/nginx/ssl/certificate.crt \ + -subj "/C=FR/ST=Paris/L=Paris/O=Area/OU=IT/CN=localhost" \ + -addext "subjectAltName = DNS:localhost" && \ + chmod 644 /etc/nginx/ssl/certificate.crt /etc/nginx/ssl/private.key ###----------------------- Build stage for Node.js application -----------------------### -FROM node:latest AS builder +FROM node:20-alpine AS builder WORKDIR /app +COPY package*.json ./ +RUN npm ci + +COPY . . + ARG VITE_PORT ARG VITE_ENDPOINT ARG VITE_GOOGLE_CLIENT_ID @@ -35,20 +36,33 @@ ARG MOBILE_CLIENT_URL ARG GITHUB_CLIENT_ID ARG GITHUB_CLIENT_SECRET -COPY ./package*.json ./ -RUN npm install -COPY . . - RUN npm run build ###----------------------- Production stage -----------------------### -FROM nginx:alpine AS production +FROM nginx:1.25.3-alpine -COPY --from=builder /app/dist /usr/share/nginx/html -COPY ./nginx.conf /etc/nginx/conf.d/default.conf -RUN mkdir -p /usr/share/nginx/html/mobile_builds +ARG VITE_PORT + +RUN adduser -D nginxuser && \ + mkdir -p /usr/share/nginx/html/mobile_builds && \ + mkdir -p /etc/nginx/ssl && \ + chown -R nginxuser:nginxuser /usr/share/nginx/html && \ + chown -R nginxuser:nginxuser /etc/nginx/ssl && \ + chown -R nginxuser:nginxuser /var/cache/nginx && \ + chown -R nginxuser:nginxuser /var/log/nginx && \ + touch /var/run/nginx.pid && \ + chown -R nginxuser:nginxuser /var/run/nginx.pid + +COPY --from=cert-builder --chown=nginxuser:nginxuser /etc/nginx/ssl /etc/nginx/ssl +COPY --from=builder --chown=nginxuser:nginxuser /app/dist /usr/share/nginx/html +COPY --chown=nginxuser:nginxuser ./nginx.conf /etc/nginx/conf.d/default.conf + +USER nginxuser EXPOSE ${VITE_PORT} +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:${VITE_PORT}/health || exit 1 + CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/client_web/nginx.conf b/client_web/nginx.conf index 036ee9a..32e8d6e 100644 --- a/client_web/nginx.conf +++ b/client_web/nginx.conf @@ -1,7 +1,12 @@ server { - listen 8081; + listen 8081 ssl; server_name localhost; + ssl_certificate /etc/nginx/ssl/certificate.crt; + ssl_certificate_key /etc/nginx/ssl/private.key; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + location / { root /usr/share/nginx/html; index index.html index.htm; diff --git a/client_web/vite.config.ts b/client_web/vite.config.ts index 1371208..42b73cd 100644 --- a/client_web/vite.config.ts +++ b/client_web/vite.config.ts @@ -1,6 +1,5 @@ import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' -import fs from 'fs' import path from 'path' export default defineConfig(({ mode }) => { @@ -10,10 +9,6 @@ export default defineConfig(({ mode }) => { plugins: [react()], server: { port: parseInt(env.VITE_PORT) || 8081, - https: { - key: fs.readFileSync(path.resolve(__dirname, 'localhost-key.pem')), - cert: fs.readFileSync(path.resolve(__dirname, 'localhost.pem')), - }, }, resolve: { alias: { diff --git a/docker-compose.yml b/docker-compose.yml index f8dde58..7fa6411 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,27 +1,81 @@ services: + rabbitmq: + image: rabbitmq:4.0.4-management-alpine + ports: + - "8082:15672" + - "5000:5673" + networks: + - area_network + volumes: + - ./rabbit-mq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf:ro + healthcheck: + test: [ "CMD", "rabbitmqctl", "status" ] + interval: 5s + timeout: 15s + retries: 5 + restart: unless-stopped + + mariadb: + image: mariadb:11.4.4 + ports: + - "3306:3306" + environment: + MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} + MYSQL_DATABASE: ${DB_NAME} + MYSQL_ROOT_HOST: "%" + volumes: + - mariadb_data:/var/lib/mysql + - ./server/init.sql:/docker-entrypoint-initdb.d/init.sql:ro + networks: + - area_network + healthcheck: + test: [ "CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD}" ] + interval: 10s + timeout: 5s + retries: 5 + env_file: + - .env + restart: unless-stopped + + area-server: + build: + context: ./server + dockerfile: Dockerfile + ports: + - "8080:8080" + volumes: + - ./ssl:/app/ssl:ro + environment: + DB_HOST: ${DB_HOST} + DB_PORT: ${DB_PORT} + DB_USER: ${DB_USER} + DB_NAME: ${DB_NAME} + DB_PASSWORD: ${DB_PASSWORD} + depends_on: + mariadb: + condition: service_healthy + rabbitmq: + condition: service_healthy + networks: + - area_network + env_file: + - server/.env.server + restart: unless-stopped + area-client-mobile: build: context: ./client_mobile dockerfile: Dockerfile args: - - VITE_PORT - - VITE_ENDPOINT - - VITE_GOOGLE_CLIENT_ID - - VITE_GOOGLE_CLIENT_SECRET - - VITE_MICROSOFT_CLIENT_ID - - VITE_LINKEDIN_CLIENT_ID - - VITE_LINKEDIN_CLIENT_SECRET - - VITE_SPOTIFY_CLIENT_ID - - VITE_SPOTIFY_CLIENT_SECRET - API_URL - WEB_CLIENT_URL - MOBILE_CLIENT_URL - GITHUB_CLIENT_ID - GITHUB_CLIENT_SECRET volumes: - - area-client-data:/app/build/app/outputs/flutter-apk + - area_client_data:/app/build/app/outputs/flutter-apk networks: - - area-network + - area_network env_file: - ./client_mobile/.env.mobile @@ -47,17 +101,21 @@ services: ports: - "${VITE_PORT}:${VITE_PORT}" volumes: - - area-client-data:/usr/share/nginx/html/mobile_builds + - area_client_data:/usr/share/nginx/html/mobile_builds:ro depends_on: - area-client-mobile + - area-server networks: - - area-network + - area_network env_file: - ./client_web/.env.local + restart: unless-stopped volumes: - area-client-data: + area_client_data: + mariadb_data: networks: - area-network: + area_network: + driver: bridge diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..a268295 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,5 @@ +dependencies: + flutter: + sdk: flutter + flutter_dotenv: ^5.2.1 + go_router: ^14.6.1 \ No newline at end of file diff --git a/server/.env.example b/server/.env.example deleted file mode 100644 index 510adf3..0000000 --- a/server/.env.example +++ /dev/null @@ -1,7 +0,0 @@ -SECRET_KEY= -DB_HOST= -DB_PORT= -DB_NAME= -DB_USER= -DB_PASSWORD= -RMQ_URL= diff --git a/server/.env.server.example b/server/.env.server.example new file mode 100644 index 0000000..d544f4a --- /dev/null +++ b/server/.env.server.example @@ -0,0 +1,7 @@ +SECRET_KEY="change-this-secret-key" +DB_HOST=localhost +DB_PORT=3306 +DB_NAME=area +DB_USER=root +DB_PASSWORD=change-me +RMQ_URL="amqp://root:root@localhost:5672/" \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..796fa7f --- /dev/null +++ b/server/.gitignore @@ -0,0 +1,2 @@ +.env +.env.server diff --git a/server/Dockerfile b/server/Dockerfile index cc75960..dbe5521 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -1,22 +1,36 @@ -FROM golang:1.23.3-alpine as builder +FROM golang:1.23.4-alpine3.20 AS builder + +RUN apk add --no-cache gcc musl-dev + WORKDIR /app -COPY . . -RUN go mod tidy +COPY go.mod go.sum ./ + RUN go mod download -RUN go get -u github.com/swaggo/swag -RUN go get -u github.com/gin-gonic/gin -RUN go build -o main . +COPY . . + +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . -FROM debian:bullseye-slim +FROM alpine:3.20.3 + +RUN apk add --no-cache ca-certificates && \ + adduser -D appuser WORKDIR /app + COPY --from=builder /app/main . -COPY --from=builder /app/.env . COPY --from=builder /app/config.json . +COPY .env.server .env + +RUN chown -R appuser:appuser /app && \ + chmod +x /app/main -RUN chmod +x /app/main +USER appuser EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ + CMD wget --no-verbose --tries=1 --spider http://localhost:8080/health || exit 1 + CMD ["./main"] diff --git a/server/build/docker-compose.yml b/server/build/docker-compose.yml deleted file mode 100644 index a3aeff1..0000000 --- a/server/build/docker-compose.yml +++ /dev/null @@ -1,60 +0,0 @@ -services: - rabbitmq: - image: rabbitmq:3-management - ports: - - "8082:15672" - - "5000:5673" - networks: - - app_network - volumes: - - ./rabbit-mq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf - healthcheck: - test: [ "CMD", "rabbitmqctl", "status" ] - interval: 5s - timeout: 15s - retries: 5 - - mariadb: - image: mariadb:10.4 - ports: - - "3306:3306" - environment: - MYSQL_ROOT_PASSWORD: ${DB_PASSWORD} - MYSQL_DATABASE: ${DB_NAME} - volumes: - - mariadb_data:/var/lib/mysql - networks: - - app_network - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-h", "localhost" ] - interval: 10s - timeout: 5s - retries: 5 - - go-app: - build: - context: ../. - dockerfile: Dockerfile - ports: - - "8080:8080" - environment: - DB_HOST: mariadb - DB_PORT: 3306 - DB_NAME: ${DB_NAME} - DB_USER: root - DB_PASSWORD: ${DB_PASSWORD} - SECRET_KEY: ${SECRET_KEY} - depends_on: - mariadb: - condition: service_healthy - rabbitmq: - condition: service_healthy - networks: - - app_network - -volumes: - mariadb_data: - -networks: - app_network: - driver: bridge \ No newline at end of file diff --git a/server/init.sql b/server/init.sql new file mode 100644 index 0000000..ea08740 --- /dev/null +++ b/server/init.sql @@ -0,0 +1,6 @@ +CREATE DATABASE IF NOT EXISTS area; +USE area; + +-- Grant all privileges to root user from any host +GRANT ALL PRIVILEGES ON area.* TO 'root'@'%' IDENTIFIED BY 'root'; +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/server/main.go b/server/main.go index bfb0bef..cd03209 100644 --- a/server/main.go +++ b/server/main.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/gin-gonic/gin" "log" + "net/http" "strconv" ) @@ -36,7 +37,13 @@ func main() { router := routers.SetupRouter() port := strconv.Itoa(config.AppConfig.Port) log.Printf("Starting %s on port %s in %s mode", config.AppConfig.AppName, port, config.AppConfig.GinMode) - if err := router.Run(fmt.Sprintf(":%s", port)); err != nil { - log.Fatalf("Failed to start server: %v", err) + + server := &http.Server{ + Addr: fmt.Sprintf(":%s", port), + Handler: router, + } + + if err := server.ListenAndServe(); err != nil { + log.Printf("Server error: %v", err) } } diff --git a/server/start_server.sh b/server/start_server.sh deleted file mode 100755 index 257b7b0..0000000 --- a/server/start_server.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -# Start the server -COMPOSE_FILE=build/docker-compose.yml -ENV_FILE=.env - -if command -v docker-compose &> /dev/null -then - docker-compose --env-file $ENV_FILE -f $COMPOSE_FILE -p server up --build -d -else - docker compose --env-file $ENV_FILE -f $COMPOSE_FILE -p server up --build -d -fi diff --git a/server/stop_server.sh b/server/stop_server.sh deleted file mode 100755 index f703b8f..0000000 --- a/server/stop_server.sh +++ /dev/null @@ -1,12 +0,0 @@ -#! /bin/bash - -# Stop the server -COMPOSE_FILE=build/docker-compose.yml -ENV_FILE=.env - -if command -v docker-compose &> /dev/null -then - docker-compose --env-file $ENV_FILE -f $COMPOSE_FILE down --remove-orphans -else - docker compose --env-file $ENV_FILE -f $COMPOSE_FILE down --remove-orphans -fi