Skip to content
Merged
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
6 changes: 5 additions & 1 deletion .github/ISSUE_TEMPLATE/nowa.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ body:
id: kod_pocztowy
attributes:
label: Kod Pocztowy
description: Wprowadź kod pocztowy w formacie `00-000`
placeholder: 00-000
validations:
required: true
- type: input
Expand All @@ -97,5 +99,7 @@ body:
id: telefon
attributes:
label: Telefon kontaktowy dla kuriera
description: Wprowadź numer telefonu w formacie `000 000 000`
placeholder: 000 000 000
validations:
required: true
required: true
168 changes: 168 additions & 0 deletions .github/scripts/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock

# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock

# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
.pdm.toml
.pdm-python
.pdm-build/

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
.idea/
18 changes: 18 additions & 0 deletions .github/scripts/consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import enum


class OrgSchemaIds(enum.StrEnum):
name = "nazwa"
www = "www"
krs = "krs"
slug = "nazwa_strony"
street = "ulica"
postal_code = "kod_pocztowy"
city = "miasto"
phone_number = "telefon"

NEW_ORG_ISSUE_DEFAULT_TITLE = "[Nowa Organizacja]"
NEW_ORG_SCHEMA_FILENAME = "nowa.yaml"


ORG_SCHEMA_SLUG_FIELD = "adres"
19 changes: 19 additions & 0 deletions .github/scripts/labels.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import enum

from consts import OrgSchemaIds


class Label(enum.StrEnum):
INVALID_KRS = "niepoprawny KRS"
INVALID_POSTAL_CODE = "niepoprawny kod pocztowy"
INVALID_PHONE = "niepoprawny numer telefonu"
INVALID_SLUG = "niepoprawna nazwa strony"
AUTO_VERIFIED = "zweryfikowana automatycznie"


INVALID_FIELD_TO_LABEL = {
OrgSchemaIds.krs: Label.INVALID_KRS,
OrgSchemaIds.postal_code: Label.INVALID_POSTAL_CODE,
OrgSchemaIds.phone_number: Label.INVALID_PHONE,
OrgSchemaIds.slug: Label.INVALID_SLUG,
}
26 changes: 26 additions & 0 deletions .github/scripts/parsers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from typing import Any

import yaml


class FormDataParser:

def __init__(self, form_data: dict[str, Any], form_schema_filename: str):
self.form_data = form_data
self.form_schema_filename = form_schema_filename
self.form_schema = self.get_form_schema(form_schema_filename)

@staticmethod
def get_form_schema(template_filename):
with open(f"../ISSUE_TEMPLATE/{template_filename}") as f:
return yaml.safe_load(f)

def get_label(self, identifier: str) -> str | None:
for field in self.form_schema.get("body", []):
if "id" in field and field["id"] == identifier:
return field["attributes"]["label"]
return None

def get(self, identifier: str) -> str | None:
label = self.get_label(identifier)
return self.form_data.get(label, "")
64 changes: 64 additions & 0 deletions .github/scripts/process.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import json
import logging
import os

from github import Auth, Github, Issue

from consts import OrgSchemaIds, NEW_ORG_ISSUE_DEFAULT_TITLE, NEW_ORG_SCHEMA_FILENAME
from labels import Label
from parsers import FormDataParser
from pullers import OrgDataPuller
from utils import has_label
from validators import OrgValidator

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__file__)

GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
GITHUB_REPOSITORY = os.getenv("GITHUB_REPOSITORY")

auth = Auth.Token(GITHUB_TOKEN)
g = Github(auth=auth)
repo = g.get_repo(GITHUB_REPOSITORY)


def process_new_org_issue(issue: Issue, data: FormDataParser):
if has_label(issue, Label.AUTO_VERIFIED):
issue.remove_from_labels(Label.AUTO_VERIFIED)

validator = OrgValidator(data, issue)
if not validator.validate():
logger.error("Validation failed")
return

if not (org := OrgDataPuller.get_org_by_krs(issue, data.get(OrgSchemaIds.krs))):
logger.error("KRS db validation failed")
return

# Update issue title
if issue.title == NEW_ORG_ISSUE_DEFAULT_TITLE:
logger.info("Updating issue title")
issue.edit(title=f"{NEW_ORG_ISSUE_DEFAULT_TITLE} {org.name or data.get(OrgSchemaIds.name)}")

if not has_label(issue, Label.AUTO_VERIFIED):
logger.info("Adding auto-verified label")
issue.create_comment(f"@{issue.user.login}, dziękujemy za podanie informacji. "
f"Przyjęliśmy zgłoszenie dodania nowej organizacji. "
f"Wkrótce skontaktujemy się celem weryfikacji zgłoszenia.")
issue.add_to_labels(Label.AUTO_VERIFIED)


def main():
github_form_json = os.getenv("GITHUB_FORM_JSON")
github_issue_number = int(os.getenv("GITHUB_ISSUE_NUMBER"))

issue = repo.get_issue(github_issue_number)
data = FormDataParser(
json.loads(github_form_json),
NEW_ORG_SCHEMA_FILENAME
)
process_new_org_issue(issue, data)


if __name__ == "__main__":
main()
43 changes: 43 additions & 0 deletions .github/scripts/pullers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import re
from typing import Optional

import requests
from github import Issue

from utils import has_label
from labels import Label


class OrgDataPuller:

def __init__(self, krs: str):
self.krs = krs
self.data = self.pull_data()

def pull_data(self) -> dict | None:
response = requests.get(f"https://api-krs.ms.gov.pl/api/krs/OdpisAktualny/{self.krs}?rejestr=S&format=json")
if response.status_code == 200:
return response.json()
else:
raise requests.HTTPError(f"Failed to fetch data for KRS {self.krs}")

@property
def name(self):
return self.data.get("odpis", {}).get("dane", {}).get("dzial1", {}).get("danePodmiotu", {}).get("nazwa")

@staticmethod
def get_org_by_krs(issue: Issue, krs: str) -> Optional["OrgDataPuller"]:

# Downloading official org data
try:
org = OrgDataPuller(krs)
except requests.HTTPError as e:
issue.create_comment(f"Nie udało się pobrać danych o organizacji z KRS. "
f"Proszę sprawdzić, czy podany numer jest poprawny")
issue.add_to_labels(Label.INVALID_KRS)
return

if has_label(issue, Label.INVALID_KRS):
issue.remove_from_labels(Label.INVALID_KRS)

return org
3 changes: 3 additions & 0 deletions .github/scripts/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PyGithub==2.5.0
PyYAML==6.0.2
requests==2.32.3
5 changes: 5 additions & 0 deletions .github/scripts/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from labels import Label


def has_label(issue, label: Label):
return any([l for l in issue.labels if l.name == label])
Loading