diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..cffc198
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1 @@
+* @MartaSien
\ No newline at end of file
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..3401052
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
+
+version: 2
+updates:
+ - package-ecosystem: "pip"
+ directory: "/"
+ schedule:
+ interval: "weekly"
diff --git a/.github/linters/.mega-linter.yml b/.github/linters/.mega-linter.yml
new file mode 100644
index 0000000..c45a956
--- /dev/null
+++ b/.github/linters/.mega-linter.yml
@@ -0,0 +1,16 @@
+# Configuration file for MegaLinter
+# See all available variables at https://megalinter.io/configuration/ and in linters documentation
+# ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY
+ENABLE_LINTERS:
+ - PYTHON_BLACK
+ - PYTHON_RUFF
+ - CSS_STYLELINT
+ - HTML_HTMLHINT
+ - MARKDOWN_MARKDOWNLINT
+ - YAML_PRETTIER
+MARKDOWN_FILTER_REGEX_EXCLUDE: '(LICENSE\.md|docs/NEWS\.md)'
+MARKDOWN_MARKDOWN_LINK_CHECK_DISABLE_ERRORS: true
+PRE_COMMANDS:
+ - command: R --slave -e 'install.packages(c("lintr"))'
+ cwd: "root"
+PRINT_ALL_FILES: false
diff --git a/.github/workflows/megalinter.yml b/.github/workflows/megalinter.yml
new file mode 100644
index 0000000..4dbceef
--- /dev/null
+++ b/.github/workflows/megalinter.yml
@@ -0,0 +1,37 @@
+# MegaLinter GitHub Action configuration file
+# More info at https://megalinter.io
+name: MegaLinter
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+ branches: [master]
+
+env:
+ MEGALINTER_CONFIG: .github/linters/.mega-linter.yml
+
+concurrency:
+ group: ${{ github.ref }}-${{ github.workflow }}
+ cancel-in-progress: true
+
+jobs:
+ megalinter:
+ name: MegaLinter
+ runs-on: ubuntu-latest
+ steps:
+ # Git Checkout
+ - name: Checkout Code
+ uses: actions/checkout@v4
+ with:
+ token: ${{ secrets.PAT }}
+
+ - name: MegaLinter
+ id: ml
+ # You can override MegaLinter flavor used to have faster performances
+ # More info at https://megalinter.io/flavors/
+ uses: oxsecurity/megalinter/flavors/python@v8.0.0
+ env:
+ VALIDATE_ALL_CODEBASE: true
+ GITHUB_TOKEN: ${{ secrets.PAT }}
+ # DISABLE: COPYPASTE,SPELL # Uncomment to disable copy-paste and spell checks
diff --git a/.github/workflows/update-website.yml b/.github/workflows/update-website.yml
new file mode 100644
index 0000000..8d11de4
--- /dev/null
+++ b/.github/workflows/update-website.yml
@@ -0,0 +1,55 @@
+# Simple workflow for deploying static content to GitHub Pages
+name: Update website
+
+on:
+ # Runs on pushes targeting the default branch
+ pull_request:
+ branches: ["master"]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
+# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
+concurrency:
+ group: "update"
+ cancel-in-progress: false
+
+jobs:
+ # Run Python script to fetch REST API info
+ update:
+ runs-on: ubuntu-latest
+ permissions:
+ # Give the default GITHUB_TOKEN write permission to commit and push the
+ # added or changed files to the repository.
+ contents: write
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ with:
+ # Push to protected branch
+ token: ${{ secrets.PAT }}
+ # Fetch all commits instead of a single, top one
+ fetch-depth: 0
+ - name: Set up Python
+ uses: actions/setup-python@v5
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install -r requirements.txt
+ - name: Generate files
+ run: python main.py
+ - name: Prepare deployment branch
+ run: |
+ git stash
+ git checkout gh-pages
+ git reset --hard origin/master
+ git stash pop
+ - name: Commit and push
+ uses: stefanzweifel/git-auto-commit-action@v7.1.0
+ with:
+ commit_message: Update website
+ push_options: --force
+ branch: gh-pages
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..f5855d3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+env
+__pycache__
+.env
\ No newline at end of file
diff --git a/ORIG_README.md b/ORIG_README.md
new file mode 100644
index 0000000..2e5ba86
--- /dev/null
+++ b/ORIG_README.md
@@ -0,0 +1,45 @@
+# HS3 Baza zasobów Dashboard
+
+Skrypt, który generuje podsumowanie [Bazy Wiedzy zasobów Hackerspace Trójmiasto](https://kb.hs3.pl/docs) w formie statycznej strony internetowej.
+
+## Sposób działania
+
+1. Baza Wiedzy znajduje się na Discourse Hackerspace Trójmiasto i jest dostępna publicznie. Projekt wykorzystuje Discourse REST API do pobrania listy zasobów.
+1. Lista zasobów zapisana jest w pliku csv `zasoby.csv`.
+1. Skrypt tworzy statyczną stronę internetową na podstawie pliku `.csv`.
+1. Strona jest hostowana przy pomocy GitHub Pages.
+
+## Możliwości generatora bazy zasobów csv
+
+- pobieranie listy wszystkich zasobów z wybranej kategorii
+- pobieranie ID, tagów i treści posta każdego zasobu
+- wyłuskanie z treści posta informacji:
+ - nazwa przedmiotu
+ - miejsce zamieszkania
+ - ilość
+ - opiekunowie
+- oto jak powinien wyglądać ostateczny wpis:
+
+```
+ID, nazwa, miejsce, ilość, opiekunowie, tagi
+```
+
+## Automatyczna aktualizacja
+
+Żeby działały automatyczne aktualizacje przy użyciu GitHub Actions należy w sekretach dodać sekret o nazwie `PAT` którego wartością jest Personal Access Token z uprawnieniami do modyfikowania repozytorium.
+
+## Możliwości dashboard'u
+
+- filtrowanie bazy zasobów po tagach
+- sortowanie alfabetyczne bazy zasobów po dowolnej kolumnie
+- linki do zasobu na Discourse w ID zasobu
+- łatwa zmiana ilości kolumn dashboardu
+
+## Co chcę dodać w przyszłości
+
+- wizualizacja statystyk z bazy zasobów
+- generowanie drugiego pliku csv służącego do wygenerowania naklejek z kodem QR
+
+## Dokumentacja
+
+- [Discourse REST API](https://docs.discourse.org/)
diff --git a/discourse.py b/discourse.py
new file mode 100644
index 0000000..dc31edc
--- /dev/null
+++ b/discourse.py
@@ -0,0 +1,116 @@
+'''
+Class to generate a csv file based on data fetched via Discourse REST API
+'''
+import os
+import csv
+import json
+import requests
+from dotenv import load_dotenv
+
+DISCOURSE_URL = "https://kb.hs3.pl" # Database is hosted here
+CATEGORY_ID = 9 # Database category ID
+PLACES = [
+ "cow-work",
+ "garage",
+ "lab",
+ "audiolab",
+ "server-room"
+]
+class DiscourseDatabase():
+ def __init__(self):
+ data = self.get_category_data()
+ self.category_topics_csv(data)
+ load_dotenv()
+
+ def get_headers(self, auth=False):
+ """Get request headers, optionally with auth data."""
+ headers = {
+ "content-type": "application/json",
+ }
+ if auth:
+ headers["Api-Key"] = os.getenv("DISCOURSE_PAT")
+ headers["Api-Username"] = os.getenv("DISCOURSE_USERNAME")
+ return headers
+
+ def get_category_data(self) -> dict:
+ """Get all topics from a Discourse category with pagination"""
+ url = f"{DISCOURSE_URL}/c/{CATEGORY_ID}.json"
+ print(f"Fetching data from {url}")
+ all_topics = []
+ page = 0
+ while True:
+ params = {"per_page": 100, "page": page}
+ res = requests.get(url, headers=self.get_headers(), params=params)
+ res.raise_for_status()
+ res_json = res.json()
+ topics = res_json["topic_list"]["topics"]
+ if not topics:
+ break
+ for topic in topics:
+ if topic["category_id"] == CATEGORY_ID:
+ all_topics.append(topic)
+ print(f"Fetched page {page}: {len(topics)} topics, {len(all_topics)} total in category")
+ page += 1
+ return {"topic_list": {"topics": all_topics}}
+
+ def get_topic_content(self, topic_id: str):
+ """Get a single topic's content"""
+ get_url = f"{DISCOURSE_URL}/posts/{topic_id}.json"
+ res = requests.get(get_url, headers=self.get_headers(auth=True))
+ res.raise_for_status()
+ return res.json()
+
+ def category_topics_csv(self, category_data) -> None:
+ """Save category topics to a csv file"""
+ columns = ["id", "title", "place", "tags"]
+ records = category_data["topic_list"]["topics"]
+ with open('zasoby.csv', 'w', encoding='UTF8') as f:
+ write = csv.writer(f)
+ write.writerow(columns)
+ for topic in records:
+ html_url = f'{topic["title"]}'
+ place = self.get_place(topic)
+ write.writerow([topic["id"], html_url, place, topic["tags"]])
+ print(f"New zasoby.csv generated with {len(records)} records")
+
+ def get_place(self, topic):
+ """Get place of a topic"""
+ for place in PLACES:
+ if place in topic["tags"]:
+ return f'{place}'
+ return "unknown"
+
+ def replace_string_in_post(self, topic_id: str, old_string: str, new_string: str) -> dict:
+ """Replace a selected string within a topic's first post using Discourse REST API"""
+ # Fetch the topic to get the first post ID
+ topic_url = f"{DISCOURSE_URL}/t/{topic_id}.json"
+ topic_res = requests.get(topic_url, headers=self.get_headers(auth=True))
+ topic_res.raise_for_status()
+ topic_data = topic_res.json()
+
+ # Get the first post ID from the topic
+ first_post_id = topic_data["post_stream"]["posts"][0]["id"]
+
+ # Fetch the post content
+ post_url = f"{DISCOURSE_URL}/posts/{first_post_id}.json"
+ post_res = requests.get(post_url, headers=self.get_headers(auth=True))
+ post_res.raise_for_status()
+ post_data = post_res.json()
+
+ # Replace the string
+ updated_raw = post_data["raw"].replace(old_string, new_string)
+
+ # Update the post
+ payload = {"post": {"raw": updated_raw}}
+ res = requests.put(post_url, json=payload, headers=self.get_headers(auth=True))
+ res.raise_for_status()
+ return res.json()
+
+if __name__ == "__main__":
+ disc = DiscourseDatabase()
+ category = disc.get_category_data()
+ records = category["topic_list"]["topics"]
+ for topic in records:
+ if "lab" in topic["tags"]:
+ disc.replace_string_in_post(topic["id"], "[Workshop](https://kb.s.hs3.pl/tag/workshop)", "[Lab](https://kb.s.hs3.pl/tag/lab)")
+
diff --git a/docs/index.html b/docs/index.html
new file mode 100644
index 0000000..54ab1de
--- /dev/null
+++ b/docs/index.html
@@ -0,0 +1,6503 @@
+
+
+