Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
splinker/
3 changes: 2 additions & 1 deletion eslint.config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default defineConfig([
'mysql/',
'public/',
'coverage/',
'node_modules/'
'node_modules/',
'splinker/'
]
},
{
Expand Down
24 changes: 20 additions & 4 deletions splinker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,42 @@
FROM eclipse-temurin:21-jre

ARG SPLINKER_VERSION=7.2.7
ARG NODE_VERSION=22.20.0

WORKDIR /app

RUN \
apt-get update && \
# Install minimal packages, download splinker.jar and Node.js binary
RUN apt-get update && \
apt-get install -y \
cron \
tzdata \
curl && \
curl \
xz-utils \
ca-certificates && \
curl -fsSL https://github.com/cria/splinker-javafx/releases/download/v${SPLINKER_VERSION}/splinker.jar \
-o /app/splinker.jar && \
apt-get remove -y curl && \
curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz -o /tmp/node.tar.xz && \
tar -xJf /tmp/node.tar.xz -C /usr/local --strip-components=1 && \
rm -f /tmp/node.tar.xz && \
corepack enable && \
apt-get remove -y curl xz-utils && \
apt autoremove -y && \
rm -rf /var/lib/apt/lists/*

# Copy package.json and yarn.lock from splinker/ and install dependencies
COPY package.json yarn.lock /app/
RUN yarn install --production && \
yarn cache clean --force

ENV \
TZ=America/Sao_Paulo \
CRON_SCHEDULE="0 0 * * *"

RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

COPY save_logs.mjs run_splinker.sh /app/
RUN chmod +x /app/run_splinker.sh

COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh

Expand Down
2 changes: 2 additions & 0 deletions splinker/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@ services:
container_name: splinker
build: .
env_file: .env
extra_hosts:
- "host.docker.internal:host-gateway"
13 changes: 12 additions & 1 deletion splinker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,17 @@
#!/bin/sh

# Export container environment variables to /etc/environment so cron can read them
printenv | grep -v "^\(HOME\|USER\|LOGNAME\|SHELL\|PATH\)=" >> /etc/environment

echo "Generating config from environment variables..."

# Expect DATABASE_* variables to be provided by the environment/container
: "${DATABASE_HOST:?DATABASE_HOST is required}"
: "${DATABASE_PORT:?DATABASE_PORT is required}"
: "${DATABASE_NAME:?DATABASE_NAME is required}"
: "${DATABASE_USERNAME:?DATABASE_USERNAME is required}"
: "${DATABASE_PASSWORD:?DATABASE_PASSWORD is required}"

cat > /app/splinker.conf <<EOF
[dataset]
token=${SPLINKER_TOKEN}
Expand All @@ -13,7 +24,7 @@ EOF

echo "Configuring cron job with schedule: $CRON_SCHEDULE ($TZ)"

echo "$CRON_SCHEDULE $(which java) -jar /app/splinker.jar /app/splinker.conf 1> /proc/1/fd/1 2> /proc/1/fd/2" | crontab -
echo "$CRON_SCHEDULE /app/run_splinker.sh 1> /proc/1/fd/1 2> /proc/1/fd/2" | crontab -

if ! crontab -l >/dev/null 2>&1; then
echo "Failed to configure cron job"
Expand Down
8 changes: 8 additions & 0 deletions splinker/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "splinker-helper",
"version": "0.1.0",
"private": true,
"dependencies": {
"pg": "^8.16.3"
}
}
6 changes: 6 additions & 0 deletions splinker/run_splinker.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/sh

# Roda o jar do splinker, duplica o output para os logs do Docker e envia para o script de log
# Usamos tee para que a saída também seja registrada nos logs do container
# Caminhos absolutos são necessários pois o cron tem um PATH mínimo
/opt/java/openjdk/bin/java -jar /app/splinker.jar /app/splinker.conf 2>&1 | tee /proc/1/fd/1 | /usr/local/bin/node /app/save_logs.mjs
81 changes: 81 additions & 0 deletions splinker/save_logs.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Client } from 'pg'

const DATABASE_HOST = process.env.DATABASE_HOST
const DATABASE_PORT = process.env.DATABASE_PORT
const DATABASE_NAME = process.env.DATABASE_NAME
const DATABASE_USERNAME = process.env.DATABASE_USERNAME
const DATABASE_PASSWORD = process.env.DATABASE_PASSWORD

async function readStdin() {
let result = ''
process.stdin.setEncoding('utf8')

for await (const chunk of process.stdin) {
result += chunk
}

return result
}

function parseSuccess(logData) {
return (
logData.includes('Transmission completed successfully')
|| /Success:\s*[1-9]/.test(logData)
)
}

function createClient() {
if (!DATABASE_NAME || !DATABASE_USERNAME || !DATABASE_PASSWORD) {
throw new Error('Database connection variables are not fully provided.')
}

return new Client({
host: DATABASE_HOST,
port: parseInt(DATABASE_PORT, 10),
database: DATABASE_NAME,
user: DATABASE_USERNAME,
password: DATABASE_PASSWORD
})
}

async function main() {
const logData = await readStdin()
const sucesso = parseSuccess(logData)
const client = createClient()

try {
await client.connect()
await client.query("SET timezone TO 'America/Sao_Paulo'")

const res = await client.query(
'SELECT MAX("CatalogNumber") as ultimo_tombo_hcf FROM vw_splinker'
)
const ultimoTomboHcf = res.rows[0]?.ultimo_tombo_hcf ?? null

const insertQuery = `
INSERT INTO splinker_execucoes (ultimo_tombo_hcf, sucesso, log_saida)
VALUES ($1, $2, $3)
`

await client.query(
insertQuery,
[
ultimoTomboHcf,
sucesso,
logData
]
)

console.log('Execução do Splinker registrada em splinker_execucoes.')
} catch (error) {
console.error('Erro ao salvar os logs:', error instanceof Error ? error.message : error)
process.exit(1)
} finally {
await client.end()
}
}

main().catch((error) => {
console.error(error instanceof Error ? error.message : error)
process.exit(1)
})
91 changes: 91 additions & 0 deletions splinker/yarn.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


pg-cloudflare@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz#4b4c20e6d8ae531d400730f4804571a8d62f1497"
integrity sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==

pg-connection-string@^2.13.0:
version "2.13.0"
resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.13.0.tgz#8678113465a5af3cc977dcb51eadc847b27aa2de"
integrity sha512-EMnU9E2fSULdsbErBbMaXJvFeD9B4+nPcM3f+4lsiCR0BHLPrLVjv3DbyM2hgQQviKJaTWIRRTjKjWlHg3p2ig==

pg-int8@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c"
integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==

pg-pool@^3.14.0:
version "3.14.0"
resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.14.0.tgz#f35ae4eb846780cad71af24099b3edfa9781ad90"
integrity sha512-gKtPkFdQPU3DksooVLi9LsjZxrsBUZIpa+7aVx+LV5pNh0KzP4Zleud2po+ConrxbuXGBJ6Hfer6hdgpIBpBaw==

pg-protocol@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.14.0.tgz#c1f045b74274b007078c687147141f785f59b8de"
integrity sha512-n5taZ1kO3s9ngDTVxsEznOqCyToTgz0FLuPq0B33COy5pPpuWJpY3/2oRBVETuOgzdqRXfWpM9HIhp2LBBT1BA==

pg-types@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3"
integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==
dependencies:
pg-int8 "1.0.1"
postgres-array "~2.0.0"
postgres-bytea "~1.0.0"
postgres-date "~1.0.4"
postgres-interval "^1.1.0"

pg@^8.16.3:
version "8.21.0"
resolved "https://registry.yarnpkg.com/pg/-/pg-8.21.0.tgz#d7fa2118d960cec5cc7d2b24525f9850dd5932b0"
integrity sha512-AUP1EYJuHraQGsVoCQVIcM7TEJVGtDzxWtGFZd8rds9d+CCXlU5Js1rYgfLNvxy9iJrpHjGrRjoi/3BT9fRyiA==
dependencies:
pg-connection-string "^2.13.0"
pg-pool "^3.14.0"
pg-protocol "^1.14.0"
pg-types "2.2.0"
pgpass "1.0.5"
optionalDependencies:
pg-cloudflare "^1.4.0"

pgpass@1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d"
integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==
dependencies:
split2 "^4.1.0"

postgres-array@~2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e"
integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==

postgres-bytea@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.1.tgz#c40b3da0222c500ff1e51c5d7014b60b79697c7a"
integrity sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==

postgres-date@~1.0.4:
version "1.0.7"
resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8"
integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==

postgres-interval@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695"
integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==
dependencies:
xtend "^4.0.0"

split2@^4.1.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4"
integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==

xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
14 changes: 14 additions & 0 deletions src/database/migration/20260530_cria_splinker_execucoes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Knex } from 'knex'

export async function run(knex: Knex): Promise<void> {
const hasTable = await knex.schema.hasTable('splinker_execucoes')
if (!hasTable) {
await knex.schema.createTable('splinker_execucoes', table => {
table.increments('id').primary()
table.timestamp('data_hora').defaultTo(knex.fn.now())
table.integer('ultimo_tombo_hcf')
table.boolean('sucesso')
table.text('log_saida')
})
}
}
Loading