· tutorial · Przeczytasz w 12 min
Automatyzacja z GitHub Actions, AWS, Make i ChatGPT
TL;DR
ChatGPT sprawia, że jako programiści unikamy odkrywania koła na nowo. Dla określonego problemu otrzymujemy zestaw najpopularniejszych narzędzi, które następnie możemy integrować w naszych projektach.
Wprowadzenie
W trakcie majowego wystąpienia na InfoShare zachęcaliśmy frontend developerów do aktywnego eksplorowania narzędzi opartych o generatywną sztuczną inteligencję. Nasze wystąpienie nie było motywowane trendami czy chęcią zdobycia tanich punktów od publiczności, a raczej tym, jak AI zmienia naszą Przeprogramowaną codzienność.
W tym tekście postanowiłem opisać jedną z niedawnych inicjatyw, która w kontekście rozwijanych przez nas szkoleń pozwoli oszczędzić cenny czas i automatyzować powtarzalne procesy. Pokażę ci, jak przy pomocy GitHub Actions oraz ChataGPT zautomatyzowaliśmy proces wystawiania certyfikatów dla uczestników naszego szkolenia Opanuj Frontend: AI Edition.
Praca z certyfikatami to absolutny fundament działalności szkoleniowej, więc inwestycja w automatyzację procesu powinna się zwrócić w krótkim czasie. Do dzieła!
Określenie wymagań
W pierwotnej formie, praca z certyfikatami dla absolwentów naszych szkoleń opierała się o zestaw manualnych kroków. Proces rozpoczynał się w momencie, kiedy użytkownik zgłosił fakt ukończenia szkolenia poprzez formularz Google Forms. Jego dane były zapisywane do Google Sheets, my przenosiliśmy je na szablon w Figmie i ręcznie generowaliśmy certyfikaty w formacie PDF, na końcu wysyłając je na wskazany adres email.
Wraz z tym, jak liczba absolwentów zaczęła rosnąć, postanowiliśmy zainwestować pewną ilość czasu w automatyzację i przygotowanie modułu, który całą operację przeprowadzi w naszym imieniu. W projekcie pojawiły się następujące wymagania funkcjonalne:
- tworzenie certyfikatów na podstawie określonego szablonu (w formacie PDF)
- nakładanie danych uczestnika na szablon (imię i nazwisko)
- przechowywanie i aktualizowanie numerów certyfikatów
- możliwość wysyłania certyfikatów na wskazany adres email
Intuicja podpowiadała, że każdy z tych elementów można w ten czy inny sposób zrealizować w środowisku Node, a następnie uruchamiać przygotowany moduł na żądanie, tworząc określony certyfikat. Postanowiłem rozpocząć od pierwszego punktu i zaimplementować funkcję do generowania certyfikatów na podstawie szablonu - jeśli ten fragment zadziała, reszta powinna być już tylko formalnością.
Procesowanie PDF
Pierwszym momentem, w którym potwierdziła się użyteczność AI, było naszkicowanie funkcji do procesowania plików PDF. Zdawałem sobie sprawę, że w środowisku Node’a jest to jak najbardziej możliwe, ale konkretne rozwiązania wymagały ode mnie przeszukiwania internetu. Ale od czego jest pair programmer?
I would like to build node-based solution that takes PDF template and adds my text into predefined area. How to build that? If you need more context, ask me extra questions.
ChatGPT oszczędził mój czas proponując wykorzystanie biblioteki pdf-lib
, z której - wg danych na npm - na codzień korzystają setki tysięcy modułów takich jak nasz.
To build a Node.js-based solution that takes a PDF template and adds your text into predefined areas, you can use the pdf-lib library, which is a popular library for working with PDFs in Node.js. Here’s a step-by-step guide on how to build this...
Wspomniana biblioteka jest na tyle popularna, że ChatGPT bez problemu poradził sobie z pierwszą wersją funkcji do wczytywania szablonów i nanoszenia danych kandydata. Doprowadzenie funkcji do finalnego kształtu wymagało drobnych korekt, takich jak pozycja elementów dopasowana do naszego kursowego szablonu, ale na tym etapie była to drobnostka.
const fs = require('fs');
const { PDFDocument, rgb } = require('pdf-lib');
// Load PDF template
async function loadPdfTemplate(filePath) {
const existingPdfBytes = fs.readFileSync(filePath);
const pdfDoc = await PDFDocument.load(existingPdfBytes);
return pdfDoc;
}
// Fill PDF template
async function fillPdfTemplate(pdfDoc, textData) {
const pages = pdfDoc.getPages();
const firstPage = pages[0];
// Define where to place the text (x, y coordinates)
// You need to determine these coordinates based on your PDF template
const positions = {
placeholder1: { x: 100, y: 700 },
placeholder2: { x: 100, y: 650 },
};
// Add text to predefined areas
for (const [placeholder, position] of Object.entries(positions)) {
if (textData[placeholder]) {
firstPage.drawText(textData[placeholder], {
x: position.x,
y: position.y,
size: 12,
color: rgb(0, 0, 0),
});
}
}
// Save the filled PDF
const pdfBytes = await pdfDoc.save();
fs.writeFileSync('filled_template.pdf', pdfBytes);
}
// Main function
async function main() {
const pdfTemplatePath = 'template.pdf'; // Path to your PDF template
const textData = {
placeholder1: 'Hello, World!',
placeholder2: 'This is a test.',
};
const pdfDoc = await loadPdfTemplate(pdfTemplatePath);
await fillPdfTemplate(pdfDoc, textData);
console.log('PDF template filled and saved as filled_template.pdf');
}
main();
Wygenerowany kod spełniał najważniejsze wymagania, ale przy pierwszym uruchomieniu pojawił się problem:
Error: WinAnsi cannot encode "ż" (0x017c)
Pytając o niego ChatGPT, uzyskałem satysfakcjonującą odpowiedź:
The issue you're encountering is due to the font you're using not supporting certain characters, such as "ż". To handle this, you need to use a font that supports the required characters, including special or accented characters.
Here’s how you can update the script to use a font that supports more characters, such as Google's Roboto or any other font that you can embed in the PDF:
1. Download a Font File
First, download a font file that supports a wide range of characters. For this example, let's use Roboto-Regular.ttf. You can download it from Google Fonts.
2. Update the Script to Use the New Font
Modify your index.js file to embed and use the new font: <snippet>
Faktycznie, dodając do projektu właściwe fonty oraz modyfikując funkcję fillPdfTemplate
o dodatkowe parametry, udało mi się rozwiązać problem z kodowaniem znaków. Przy okazji tej zmiany projekt wzbogacił się o bibliotekę fontkit, a konkretnie jej fork zintegrowany z pdf-lib.
import { PDFDocument } from 'pdf-lib';
import fontkit from '@pdf-lib/fontkit';
const template = readFileSync(join(__dirname, '../templates', 'opanuj-frontend.pdf'));
const pdfDoc = await PDFDocument.load(template);
pdfDoc.registerFontkit(fontkit);
const fontPrimary = await loadFont(pdfDoc, 'Cocogoose.ttf');
const fontSecondary = await loadFont(pdfDoc, 'Cocogoose-Pro-Light.ttf');
Dodatkowe korekty ograniczyły się do przejścia na format ES Modules oraz umieszczania tekstu na środku szablonu. Co najważniejsze, w szybki sposób uzyskałem odpowiedź na najważniejsze pytanie, a mianowicie czy jestem w stanie zaimplementować funkcję do generowania certyfikatów w Node.js.
Obsługa stanu
Drugi etap okazał się zdecydowanie łatwiejszy. Obsługa stanu, wynikająca z wystawiania certyfikatów z unikalnymi numerami, wymagała przechowywania par course -> lastCertNumber
w jednym z dostępnych źródeł danych.
Zgodnie z nazwą ulicy, na której mieściło się oryginalne biuro Facebooka (1 Hacker Way), moim źródłem danych stał się plik JSON:
{
"OPANUJ_FRONTEND": 15
}
Tak złożona i zaawansowana struktura danych pozwala na zapamiętywanie numeru ostatnio wystawionego certyfikatu, a dodatkowo pozwala rozszerzać cały projekt o kolejne szkolenia. Do tego jest możliwa do wersjonowania przez GITa, jest przenaszalna i praktycznie niemożliwa do zepsucia. Jeśli już, to narzędzia do wczytywania plików i parsowania stringów poinformują nas o niewłaściwym formacie.
Procesowanie mojego stanu umieściłem w dedykowanym module. W nim zaimplementowałem dwie najważniejsze funkcje - jedną do odczytu stanu z pliku, a drugą do zapisu zmian. Oto jak wygląda kod:
import { promises as fsPromises } from 'fs';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const { readFile, writeFile } = fsPromises;
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const STATE_FILE_PATH = join(__dirname, '../data/state.json');
async function parseJSONState() {
const stateData = await readFile(STATE_FILE_PATH);
return JSON.parse(stateData);
}
export async function getNextCertificateNumber(key) {
const state = await parseJSONState();
return state[key] + 1;
}
export async function incrementCertificateNumber(key) {
const state = await parseJSONState();
state[key] += 1;
await writeFile(STATE_FILE_PATH, JSON.stringify(state, null, 2));
}
Keep it simple - bazy danych mogą poczekać. W przypadku kolejnych usprawnień warto byłoby rozważyć obsługę błędów oraz bardziej optymalne posługiwanie się plikami, ale na tym etapie projektu (kiedy w pełni kontrolujemy tworzenie certfikatów) uznałem to za opcjonalne.
PDF to nie wszystko
Wygenerowany certyfikat w formacie PDF to jedynie połowa sukcesu. Do zbudowania pozostał moduł do przechowywania plików, mailing, a także infrastruktura, na której całość będzie uruchamiana. W tym momencie miałem już w głowie obraz całej aplikacji i przepływu danych, który powinien działać w następujący sposób:
Jak to działa?
- Przy pomocy GitHub Actions uruchamiamy skrypt, który generuje certyfikat na podstawie szablonu i danych uczestnika
- Certyfikat jest zapisywany w dedykowanym katalogu na AWS S3
- Przygotowana aplikacja wysyła zapytanie HTTP do API make.com (webhook)
- Z pomocą API make.com pobieramy certyfikat z S3 i wysyłamy go na wskazany adres email
Skąd takie decyzje? GitHub Actions to nie tylko procesy CI/CD, ale również świetne narzędzie do automatyzacji i uruchamiania bardziej złożonych modułów w Node. AWS S3 to sprawdzone miejsce do przechowywania plików, które wielokrotnie udowodniło swoją wartość przy jednocześnie niskim narzucie na konfigurację i utrzymanie. API make.com pozwala na wysyłanie maili z załącznikami, a także jest łatwe w integracji z innymi narzędziami - w tym przypadku z AWS S3.
Integrowanie wszystkich narzędzi postanowiłem rozpocząć od przygotowania środowiska do automatyzacji. ChatGPT pomógł mi utworzyć scenariusz, który klonuje repozytorium, instaluje Node.js, konfiguruje AWS CLI i uruchamia wskazany skrypt:
Create a workflow that will be manually dispatched via GitHub Actions. I would like to clone my repository, setup node.js, configure AWS credentials and execute one of the files. Also, introduce three inputs - student name as text, student email as text and gender (male/female) as select - these values should be available to the Node script as env variables. Make sure you are using the newest actions that are available in GitHub marketplace.
Rezultat wygląda w sposób następujący:
name: Custom Workflow
on:
workflow_dispatch:
inputs:
student_name:
description: 'Student Name'
required: true
default: ''
student_email:
description: 'Student Email'
required: true
default: ''
gender:
description: 'Gender'
required: true
default: 'male'
options:
- male
- female
jobs:
setup_and_run:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v3
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: 'us-west-2' # Change this to your preferred AWS region
- name: Install dependencies
run: npm install
- name: Run Node.js script
env:
STUDENT_NAME: ${{ github.event.inputs.student_name }}
STUDENT_EMAIL: ${{ github.event.inputs.student_email }}
GENDER: ${{ github.event.inputs.gender }}
run: node path/to/your/script.js # Change this to the path of your script
Korekty? Aktualizacja akcji (v3 -> v4), pobieranie wersji Node’a z pliku zamiast wersji ustalonej na sztywno (with: node-version-file: '.npmrc'
), a do tego zamiana npm install
na npm ci
dla bardziej przewidywalnego procesu budowania aplikacji.
Podobnie jak w przypadku pracy z PDFami, odpowiedź z ChataGPT nie była idealna, ale z łatwością dostosowałem ją do swoich potrzeb. W ten sposób zyskałem gotowy do użycia szablon, który pozwala na uruchomienie skryptu z dowolnymi parametrami. Całość została umieszczona w folderze .github/workflows
, dzięki czemu mogłem uruchamiać dedykowaną akcję poprzez zakładkę Actions w moim repozytorium:
Przechowywanie certyfikatów
Przedostatnim etapem całego procesu było zapisywanie certyfikatów w AWS S3 . W tym przypadku ChatGPT podpowiedział mi jak wykorzystać AWS SDK w środowisku Node’a - była to wiedza, którą zdobyłem wcześniej, ale dzięki pomocy wirtualnego asystenta mogłem szybciej przypomnieć sobie detale implementacji.
How to upload and download files to AWS S3 in Node environment? Show me basic examples using the newest version of AWS SDK.
Odpowiedź była zaskakująco prosta:
To upload and download files to AWS S3 in a Node.js environment, you can use the AWS SDK for JavaScript (v3). Here are the basic examples:
1. First, install the AWS SDK for JavaScript v3 using npm:
npm install @aws-sdk/client-s3
2. Set up your AWS credentials and region. You can either use the shared credentials file (~/.aws/credentials) or environment variables.
...
Finalnie otrzymałem fragmenty kodu, które wykorzystałem do zrealizowania trzech operacji:
- Pobrania “state.json” z S3
- Zapisania nowego “state.json” ze zaktualizowanym numerem certyfikatu
- Wysłania certyfikatu na S3
Przykładowo, pierwsze zadanie zostało zrealizowane w ten sposób (niektóre stałe pominąłem dla lepszej czytelności):
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
export async function downloadStateFile() {
const s3 = new S3Client({ region: AWS_REGION });
const command = new GetObjectCommand({
Bucket: S3_BUCKET,
Key: 'state.json',
});
const response = await s3.send(command);
const fileStream = createWriteStream(STATE_FILE_PATH);
return new Promise((resolve, reject) => {
response.Body.pipe(fileStream)
.on('error', (err) => {
console.error('State file downloading error:', err);
reject(err);
})
.on('close', () => {
console.log(`State file downloaded successfully to ${STATE_FILE_PATH}`);
resolve();
});
});
}
W ten sposób zrealizowałem wszystkie operacje na plikach w AWS S3. W przypadku większych projektów warto byłoby zaimplementować obsługę pewnego rodzaju transakcyjności (np. żeby uniknąć aktualizacji stanu przy niepoprawnej kreacji nowego certyfikatu), a także zabezpieczyć dostęp do plików za pomocą odpowiednich uprawnień. Na to przyjdzie jednak pora w momencie, kiedy skala projektu będzie tego wymagała.
Wysyłanie certyfikatów
Na tym etapie certyfikaty uczestników były już możliwe do zapisania na S3, w dedykowanym folderze poświęconym konkretnemu szkoleniu.
Ostatnim krokiem było udostępnienie właściwego dokumentu uczestnikowi naszego szkolenia. Aby oszczędzić sobie zbędnej konfiguracji, postawiłem na popularne rozwiązanie no-code, czyli Make. Make pozwala na tworzenie prostych aplikacji, które mogą być wykorzystane do wysyłania maili, generowania dokumentów, a także integracji z innymi narzędziami.
Cały scenariusz sprowadza się do trzech kroków:
- Odebrania żądania HTTP poprzez dedykowany webhook (publiczny endpoint inicjalizujący cały proces)
- Pobranie właściwego certyfikatu z S3 (przy wykorzystaniu wcześniej skonfigurowanych danych dostępowych)
- Wysłanie certyfikatu na wskazany adres email
Do zrealizowania całego procesu wystarczą trzy bloki:
Zaletą Make jest kontekstowa konfiguracja każdego bloku, dzięki czemu mogę w prosty sposób dostosować parametry wejściowe i wyjściowe. W przypadku integracji z AWS S3, mogę zdefiniować ścieżkę do pliku, a także dane dostępowe do mojego konta AWS:
W przypadku wysyłki maili, mogę zdefiniować treść wiadomości, temat, a także załącznik (certyfikat w formacie PDF). Same maile wysyłam poprzez istniejącą skrzynkę pocztową i autentykację SMTP, co pozwala na zachowanie spójności w komunikacji z uczestnikami:
Po zapisaniu scenariusza mogę go testować na kilka sposobów - od ręcznego wywołania webhooka, aż po uruchomienie go w odpowiedzi na zdarzenie (np. nowa odpowiedź w zgłoszeniu ukończenia szkolenia). W ten sposób zrealizowałem cały proces generowania i wysyłania certyfikatów dla uczestników naszego szkolenia - na teraz mówimy o ponad dwustu pasjonatach frontendu!
Nowe możliwości dzięki ChatGPT i AI
Realizacja całego projektu jest dla mnie kolejnym dowodem na to, jak wielki potencjał drzemie w narzędziach opartych o sztuczną inteligencję. ChatGPT sprawia, że jako programiści unikamy odkrywania koła na nowo. Dla określonego problemu otrzymujemy zestaw najpopularniejszych narzędzi, które następnie możemy integrować w naszych projektach.
W przypadku bardziej złożonych problemów, pracę możemy rozpocząć od analizy projektu, dekompozycji na mniejsze składowe, a następnie zapytać ChatGPT o konkretne rozwiązania. W ten sposób możemy porównać nasze przyzwyczajenia z propozycjami wirtualnego asystenta, a także zyskać nowe perspektywy na rozwijanie naszych projektów.
Im dłużej pracuję z tego typu rozwiązaniami, tym bardziej przekonuję się do stwierdzenia, że pracy nie zabierze ci AI. Pracę może ci zabrać osoba, która aktywnie eksploruje nowe technologie, uczy się na błędach i potrafi dostosować swoje podejście do zmieniających się warunków. AI jest narzędziem, które pozwala na szybsze i bardziej efektywne rozwiązywanie problemów, a nie zastępuje nas w procesie tworzenia oprogramowania - przynajmniej na razie.
Szukasz okazji na poznanie pełnego potencjału AI? Umów się na darmowe konsultacje - chętnie porozmawiamy o tym, jak sztuczna inteligencja może odmienić realizację projektów w twojej firmie. A jeśli jesteś programistą, to zapraszamy na Opanuj Frontend: AI Edition. To program, dzięki któremu poznasz praktyczne zastosowania sztucznej inteligencji, GitHub Actions oraz AWS w projektach frontendowych.