Skip to content

Commit

Permalink
Agora funciona sem acessar o disco salvando imagem no redis, corrigid…
Browse files Browse the repository at this point in the history
…o problema no delay, corrigido bug com sessão, adicionado workflows github actions
  • Loading branch information
erickweil committed Nov 10, 2024
1 parent 638f2bd commit ccd2d63
Show file tree
Hide file tree
Showing 17 changed files with 326 additions and 197 deletions.
89 changes: 89 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
name: CI CD Workflow

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'

- name: Run test script
run: |
echo "$FUNCIONA_VARIAVEIS"
ls -lhas
echo "Nome da imagem weilplace-api"
echo "$IMG_WEILPLACE_API"
echo "Nome da imagem weilplace-site"
echo "$IMG_WEILPLACE_SITE"
echo "REF: ${{ github.ref }}"
- name: Set up environment
run: mv ./backend/.env.example ./backend/.env

- name: Install and run tests
run: |
cd ./backend
npm ci
npm run test
# https://docs.github.com/en/actions/use-cases-and-examples/publishing-packages/publishing-docker-images
build:
runs-on: ubuntu-latest
permissions:
packages: write
contents: read
attestations: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20.x'

# https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Set up environment
run: mv ./backend/.env.example ./backend/.env

- name: Extract metadata (tags, labels) for Docker
id: apimeta
uses: docker/metadata-action@v5
with:
images: erickweil/weilplace-api

- name: Build and push (backend)
uses: docker/build-push-action@v5
with:
context: ./backend
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.apimeta.outputs.tags }}
labels: ${{ steps.apimeta.outputs.labels }}


- name: Extract metadata (tags, labels) for Docker
id: sitemeta
uses: docker/metadata-action@v5
with:
images: erickweil/weilplace-site

- name: Build and push (frontend)
uses: docker/build-push-action@v5
with:
context: ./frontend
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.sitemeta.outputs.tags }}
labels: ${{ steps.sitemeta.outputs.labels }}
3 changes: 3 additions & 0 deletions .vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.kompose/
*.secret*
.vercel
6 changes: 3 additions & 3 deletions backend/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ WEBSOCKET_ENABLED="true"

# Usado para deploy na Vercel
# Irá utilizar redis para guardar a imagem
DISABLE_FILESYSTEM="false"

# Se ativado, irá chamar o pixel-saver para salvar a imagem a cada X mudanças
# DISABLE_FILESYSTEM="false"
# Ao chamar get /picture, irá obter a imagem, aplicar todas as mudanças pendentes, e retornar a imagem
# Isto é para permitir que o backend não precise do serviço do pixel saver
# PIXEL_SAVER_CALL="true"
3 changes: 3 additions & 0 deletions backend/.vercelignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
public/*
*.secret*
node_modules/
9 changes: 5 additions & 4 deletions backend/service_pixelsaver.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { REDIS_ENABLED } from "./src/config/options.js";

import PixelSaver from "./src/service/pixelSaver.js";
import { PixelSaver } from "./src/service/pixelSaver.js";
import { connectToRedis } from "./src/config/redisConnection.js";
import PixelChanges from "./src/controller/pixelChanges.js";

Expand All @@ -12,8 +12,9 @@ if(!REDIS_ENABLED) {
let redisClient = await connectToRedis(PixelChanges.getLuaScriptsConfig());
await PixelChanges.init(redisClient);

const cronTask = await PixelSaver.init();
const saver = await PixelSaver.init();
const cronJob = saver.scheduleCron();

if(!cronTask) {
console.error("Erro ao inicial o Pixel Saver: Não iniciou a cron task");
if(!cronJob) {
throw new Error("Erro ao iniciar o cronJob");
}
16 changes: 10 additions & 6 deletions backend/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import session from "express-session";
import RedisStore from "connect-redis";
import http from "http";

import { SESSION_MAX_AGE, SESSION_SECRET, REDIS_ENABLED, WEBSOCKET_ENABLED } from "./config/options.js";
import { SESSION_MAX_AGE, SESSION_SECRET, REDIS_ENABLED, WEBSOCKET_ENABLED, PIXEL_SAVER_CALL } from "./config/options.js";

import routes from "./routes/index.js";
import { SessionManager } from "./middleware/sessionManager.js";
import PixelChanges from "./controller/pixelChanges.js";
import { connectToRedis } from "./config/redisConnection.js";
import PixelSaver from "./service/pixelSaver.js";
import { PixelSaver } from "./service/pixelSaver.js";
import initWebSocketServer from "./websocket/websocket.js";

let redisClient = REDIS_ENABLED ? await connectToRedis(PixelChanges.getLuaScriptsConfig()) : false;
Expand All @@ -19,8 +19,11 @@ await PixelChanges.init(redisClient);


if(!REDIS_ENABLED) {
console.log("Iniciando saver na mesma instância já que o redis está desativado...");
const cronTask = await PixelSaver.init();
if(!PIXEL_SAVER_CALL) {
console.log("Iniciando saver na mesma instância já que o redis está desativado...");
const saver = await PixelSaver.init();
const cronJob = saver.scheduleCron();
}
}

const app = express();
Expand Down Expand Up @@ -78,7 +81,7 @@ let sessionOptions = {
resave: true,
saveUninitialized: true,
cookie: {
sameSite: "strict",
sameSite: "none",
httpOnly: true,
secure: true,
maxAge: SESSION_MAX_AGE*1000, // em milissegundos
Expand All @@ -103,7 +106,8 @@ if(REDIS_ENABLED) {
const sessionParser = session(sessionOptions);
app.use(sessionParser);

app.use(SessionManager.initSession);
// Não. só vai ter sessão se estiver logado
// app.use(SessionManager.initSession);

// Websockets
if(WEBSOCKET_ENABLED) {
Expand Down
3 changes: 2 additions & 1 deletion backend/src/config/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,5 @@ export const REQUIRE_GOOGLE_LOGIN = envOrDefault("REQUIRE_GOOGLE_LOGIN","false")
export const OAUTH2_CLIENT_ID = envOrDefault("OAUTH2_CLIENT_ID","");
export const PALLETE = palleteJson.pallete.map(c => parseInt(c,16));

export const DISABLE_FILESYSTEM = envOrDefault("DISABLE_FILESYSTEM","false") === "true";
export const DISABLE_FILESYSTEM = envOrDefault("DISABLE_FILESYSTEM","false") === "true";
export const PIXEL_SAVER_CALL = envOrDefault("PIXEL_SAVER_CALL","false") === "true";
20 changes: 5 additions & 15 deletions backend/src/controller/pixelChanges.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { convertChangeToBase64 } from "../config/changesProtocol.js";
import RedisMock from "./redisMock.js";
import { defineScript, createClient, commandOptions } from "redis";
import { DISABLE_FILESYSTEM, IMAGE_HEIGHT, IMAGE_WIDTH, PALLETE } from "../config/options.js";
import { handleApplyChanges } from "../routes/privateRoutes.js";

let KEY_IDENTIFIER = "changesid";
let KEY_SAVEDINDEX = "savedindex";
Expand All @@ -12,6 +11,7 @@ let KEY_PICTURE = "picture"; // a imagem salva (se não usar filesystem)

let options = {
redis_prefix: "REDIS_PREFIX" in process.env ? process.env.REDIS_PREFIX : "",
// em milissegundos
place_delay: "PLACE_DELAY" in process.env ? parseInt(process.env.PLACE_DELAY) : 0,
/* O MAX_CHANGES_RESPONSE indica o tamanho de resposta máximo
que será dado ao cliente ao solicitar as modificações, mesmo que haja mais.
Expand All @@ -20,8 +20,7 @@ let options = {
Nada impede o cliente de solicitar a string inteira desde
o índice 0, para evitar DoS seria bom não deixar esse valor
muito alto*/
max_changes_response: "MAX_CHANGES_RESPONSE" in process.env ? parseInt(process.env.MAX_CHANGES_RESPONSE) : 8192,
pixel_saver_call: "PIXEL_SAVER_CALL" in process.env ? process.env.PIXEL_SAVER_CALL === "true" : false
max_changes_response: "MAX_CHANGES_RESPONSE" in process.env ? parseInt(process.env.MAX_CHANGES_RESPONSE) : 8192
};

/** @type {ReturnType<createClient>} */
Expand Down Expand Up @@ -72,9 +71,6 @@ class PixelChanges {
*/
static calledInit = false;
static async init(_redisClient, _options) {
if(PixelChanges.calledInit) {
throw new Error("PixelChanges já foi inicializado, deve chamá-lo apenas uma vez.");
}
PixelChanges.calledInit = true;

if(_options) {
Expand Down Expand Up @@ -143,6 +139,7 @@ class PixelChanges {
// Possível problema: Como primeiro pega o tempo e depois seta o pixel,
// se mandar requisições simultâneas pode acontecer de setar mais de um pixel devido
// à condição de corrida de verificarem ao mesmo tempo?
// A fazer: converter essa lógica para lua script, para garantir que seja atômico dentro do redis
if(options.place_delay > 0)
{
let timeLastPlaced = await redisClient.HGET(KEY_USERTIMES,username);
Expand Down Expand Up @@ -177,16 +174,9 @@ class PixelChanges {

await redisClient.APPEND(KEY_CHANGES, changes);

// 1 a cada 100 vezes chama o pixel saver
if(options.pixel_saver_call && Math.random() > 0.99) {
setTimeout(async () => {
console.log(await handleApplyChanges());
},0);
}

return {
message: "OK",
delay: options.place_delay
delay: options.place_delay > 0 ? Math.ceil(options.place_delay/1000) : 0
};
}

Expand All @@ -204,7 +194,7 @@ class PixelChanges {
return {
message: "OK",
identifier: ret[0],
i: ret[1]
i: parseInt(ret[1])
};
}

Expand Down
10 changes: 5 additions & 5 deletions backend/src/middleware/sessionManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export const haiku = () =>{

export class SessionManager {

/**
* @deprecated
* Descontinuado pois causa inúmeros nomes de usuário no redis quando o cookie não está configurado direito
*/
static initSession(req,res,next) {

if(!req.session.username) {
Expand Down Expand Up @@ -92,11 +96,7 @@ export class SessionManager {

return userinfo;
} else {
if(!hasSession) {
return undefined;
}

return userinfo;
return { username: "anonimo" };
}
}
}
2 changes: 1 addition & 1 deletion backend/src/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { LOG_ROUTES } from "../config/options.js";
export const logRoutes = (req,res,next) => {
const timestamp = new Date().toISOString();

const username = req.session?.username;
const username = req.session?.username || "anonimo";

let ip = req.headers["x-forwarded-for"] ||
req.socket.remoteAddress ||
Expand Down
47 changes: 44 additions & 3 deletions backend/src/routes/pixelsRoutes.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import express from "express";
import path from "path";
import PixelChanges from "../controller/pixelChanges.js";
import { DISABLE_FILESYSTEM, PATH_PICTURE } from "../config/options.js";
import { DISABLE_FILESYSTEM, PATH_PICTURE, PIXEL_SAVER_CALL, REDIS_ENABLED } from "../config/options.js";
import { genericRouteHandler } from "../middleware/routeHandler.js";
import { SessionManager } from "../middleware/sessionManager.js";
import { palleteJson } from "../config/pallete.js";
import { PixelSaver } from "../service/pixelSaver.js";

const router = express.Router();

Expand Down Expand Up @@ -320,11 +321,51 @@ router.get("/picture", async (req,res) => {
};

if(DISABLE_FILESYSTEM) {
let { body } = await handleGetPicureFromRedis();
let response = await handleGetPicureFromRedis();
let pngBuffer;
if(response.status === 200) {
pngBuffer = response.body;
}

// Isso causa aplicar todas as mudanças que ocorreram desde o último save, e fazer um novo save se necessário
// Tornando desnescessário o uso de cronjob para salvar a imagem, porém confiando que o cliente irá atualizar a página de vez em quando
if(PIXEL_SAVER_CALL) {
const { status, json } = await handleGetChanges({i:""+resp.i});
if(status !== 200) throw new Error("Não foi possível obter mudanças:"+json);

// Só vai aplicar a imagem se realmente houve mudanças
if(!pngBuffer || json.identifier !== resp.identifier || json.i !== resp.i) {
const saver = await PixelSaver.init(pngBuffer, resp.i, resp.identifier);

// Força execução de todas as mudanças
const result = await saver.cronTask(json);

// Se houve mudanças, a imagem foi salva, retornando o buffer png da nova imagem aqui para evitar acessar denovo
if(result && (result instanceof Buffer)) {
pngBuffer = result;
}

// pode acontecer quando é uma imagem nova, tenta pegar do redis denovo
if(!pngBuffer) {
response = await handleGetPicureFromRedis();
if(response.status === 200) {
pngBuffer = response.body;
}
}

headers["X-Changes-Offset"] = saver.last_i;
headers["X-Changes-Identifier"] = saver.last_identifier;
}
}

if(!pngBuffer) {
return res.status(500).json({message: "Erro ao acessar imagem"});
}

return res
.setHeaders(new Map(Object.entries(headers)))
.contentType("image/png")
.status(200).send(body);
.status(200).send(pngBuffer);
} else {
// 1. Se entre o getChanges acima e a leitura da imagem, for salva uma nova imagem
// não causa problemas, pois o cliente irá re-aplicar as mudanças, um re-trabalho
Expand Down
Loading

0 comments on commit ccd2d63

Please sign in to comment.