---
title: "Automatyzacja z GitHub Actions, AWS, Make i ChatGPT"
description: "Zobacz jak potencjał AI pomaga automatyzować generowanie certyfikatów dla uczestników kursu Opanuj Frontend"
publishDate: "2024-05-30T10:00:00.000Z"
image: "~/assets/images/blog/thumbnails/certificate-gha.png"
canonical: "https://opanuj.ai/blog/automatyzacja-github-actions-chat-gpt/"
excerpt: "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."
category: "Tutorial"
tags: ["openai","gpt-4","api","github","gha"]
author: "Przemek Smyrdek"
type: "post"
language: "pl"
---

## 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](https://opanujfrontend.pl).

![Przykładowy certyfikat](/assets/blog/pdf-cert-builder/cert-example.png)

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.

```javascript
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](https://www.npmjs.com/package/@pdf-lib/fontkit), a konkretnie jej fork zintegrowany z **pdf-lib**.

```javascript
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](https://maps.app.goo.gl/WUr7R7eg71VfEHYFA)), moim źródłem danych stał się plik JSON:

```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:

```javascript
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:

![Architektura rozwiązania](/assets/blog/pdf-cert-builder/plan.png)

Jak to działa?

1. Przy pomocy GitHub Actions uruchamiamy skrypt, który generuje certyfikat na podstawie szablonu i danych uczestnika
2. Certyfikat jest zapisywany w dedykowanym katalogu na AWS S3
3. Przygotowana aplikacja wysyła zapytanie HTTP do API make.com (webhook)
4. 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:

```yml
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:

![Interfejs GHA](/assets/blog/pdf-cert-builder/gha.png)

## 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:

1. Pobrania "state.json" z S3
2. Zapisania nowego "state.json" ze zaktualizowanym numerem certyfikatu
3. 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):

```javascript
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:

1. Odebrania żądania HTTP poprzez dedykowany webhook (publiczny endpoint inicjalizujący cały proces)
2. Pobranie właściwego certyfikatu z S3 (przy wykorzystaniu wcześniej skonfigurowanych danych dostępowych)
3. Wysłanie certyfikatu na wskazany adres email

Do zrealizowania całego procesu wystarczą trzy bloki:

![Scenariusz w Make.com](/assets/blog/pdf-cert-builder/make.png)

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:

![S3 w Make.com](/assets/blog/pdf-cert-builder/make-s3.png)

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:

![Email w Make.com](/assets/blog/pdf-cert-builder/smtp.png)

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](https://calendar.app.google/r3nSiZbQ1tenydbP8) - 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](https://www.opanujfrontend.pl/). To program, dzięki któremu poznasz praktyczne zastosowania sztucznej inteligencji, GitHub Actions oraz AWS w projektach frontendowych.