Skip to content
Draft
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
191 changes: 191 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
# github2stackfield

**github2stackfield** is an x0 web application that bridges **GitHub Issues** with **Stackfield Tasks**.
It lets you search GitHub issues, inspect their details, and create corresponding Stackfield tasks — all from a clean, Bootstrap-styled browser frontend.

---

## Architecture

| Layer | Technology |
|---|---|
| Browser frontend | [x0 JavaScript framework](https://github.com/WEBcodeX1/x0) with Bootstrap default theme |
| Backend services | Python WSGI scripts via [python-micro-esb](https://github.com/clauspruefer/python-micro-esb) |
| Database | PostgreSQL (shared x0 instance) |
| Deployment | Apache2 + mod_wsgi (Docker or bare-metal) |

---

## Screens

### Screen 1 — User Credentials
Configure and verify API access for both platforms.

- **GitHub API Credentials** — enter your GitHub username and Personal Access Token.
Click *Verify GitHub Credentials* to validate and store them.
- **Stackfield API Credentials** — enter your Stackfield e-mail and API token.
Click *Verify Stackfield Credentials* to validate and store them.

### Screen 2 — Issue / Task Mapping
Search GitHub issues and select one to map to Stackfield.

1. Enter the target repository (`owner/repository`) and an optional search term.
2. Click **Search Issues** — results populate the issue list below.
3. Right-click any row and select **Connect Stackfield Task** to navigate to Screen 3.

### Screen 3 — Connect Stackfield Task
Review the selected GitHub issue and create a Stackfield task.

- **GitHub Issue Properties** — read-only fields: issue number, state, title, URL.
- **Stackfield Task Mapping** — editable fields pre-populated from the issue:
- *Stackfield Room ID* — the target Stackfield room / channel identifier.
- *Task Title* — editable, defaults to the GitHub issue title.
- *Description* — editable, defaults to the GitHub issue body.
- *Priority* — Low / Medium / High / Urgent.
- Click **Create New Stackfield Task** — a new task is created in Stackfield via the REST API.

---

## Prerequisites

| Requirement | Notes |
|---|---|
| x0 framework | Follow [x0 INSTALL.md](https://github.com/WEBcodeX1/x0/blob/main/INSTALL.md) |
| PostgreSQL ≥ 14 | Shared with x0 |
| Python ≥ 3.10 | `requests`, `psycopg2`, `pgdbpool`, `python-micro-esb` |
| GitHub Personal Access Token | Needs `repo` scope for private repos, `public_repo` for public |
| Stackfield API token | See Stackfield workspace settings → Integrations → API |

---

## Installation

### 1. Set up x0

Follow the official x0 installation guide to get the base framework running with PostgreSQL.

### 2. Install Python dependencies

```bash
pip install requests psycopg2-binary pgdbpool
pip install git+https://github.com/clauspruefer/python-micro-esb.git
```

### 3. Run database setup scripts

Connect to your x0 PostgreSQL database and execute the scripts in order:

```bash
psql -U postgres -d x0 -f database/01-create-schema.sql
psql -U postgres -d x0 -f database/02-insert-config.sql
psql -U postgres -d x0 -f database/03-insert-text.sql
```

### 4. Deploy static files

Copy the `static/` directory so it is served at `/static/github2sf/`:

```bash
cp -r static/ /var/www/x0/static/github2sf/
```

### 5. Deploy Python backend

Copy the `python/` directory into the x0 Python directory:

```bash
cp python/*.py /var/www/x0/python/github2sf/
```

### 6. Configure Apache2

Add the WSGI aliases from `docker/apache2.conf` to your Apache virtual host, then reload:

```bash
apache2ctl graceful
```

### 7. Open the application

Navigate to `http://your-server/?appid=github2sf` in your browser.

---

## Docker (quick start)

```bash
cd docker
docker compose up --build
```

Then open [http://localhost:8080/?appid=github2sf](http://localhost:8080/?appid=github2sf).

> **Note:** The Docker image fetches x0 and python-micro-esb from GitHub at build time.
> You still need to run the database SQL scripts against the PostgreSQL container:
>
> ```bash
> docker exec -i github2sf-db psql -U postgres -d x0 < database/01-create-schema.sql
> docker exec -i github2sf-db psql -U postgres -d x0 < database/02-insert-config.sql
> docker exec -i github2sf-db psql -U postgres -d x0 < database/03-insert-text.sql
> ```

---

## Project structure

```
github2stackfield/
├── static/
│ ├── menu.json # x0 navigation menu definition
│ ├── object.json # x0 UI objects (formfields, lists, buttons …)
│ └── skeleton.json # x0 screen layout
├── python/
│ ├── service_implementation.py # GitHubService + StackfieldService ClassHandlers
│ ├── user_routing.py # python-micro-esb ServiceRouter routing functions
│ ├── VerifyGitHubCredentials.py # WSGI – verify GitHub credentials
│ ├── VerifyStackfieldCredentials.py # WSGI – verify Stackfield credentials
│ ├── SearchGitHubIssues.py # WSGI – search issues, populate list
│ ├── GetGitHubIssueDetails.py # WSGI – fetch issue details for Screen 3
│ ├── CreateStackfieldTask.py # WSGI – create Stackfield task
│ ├── POSTData.py # x0 POST body reader helper
│ └── StdoutLogger.py # logging helper
├── database/
│ ├── 01-create-schema.sql # github2sf schema + tables
│ ├── 02-insert-config.sql # x0 app configuration rows
│ └── 03-insert-text.sql # UI text / i18n entries
├── docker/
│ ├── Dockerfile
│ ├── docker-compose.yml
│ └── apache2.conf
└── README.md
```

---

## python-micro-esb integration

The backend services are built on the [python-micro-esb](https://github.com/clauspruefer/python-micro-esb) framework:

- **`service_implementation.py`** — contains `GitHubService` and `StackfieldService`, both subclassing `microesb.ClassHandler`. Each class exposes service methods (`verify`, `search_issues`, `get_issue_details`, `create_task`).
- **`user_routing.py`** — routing functions consumed by `ServiceRouter.send()`. Each function instantiates the appropriate service class, calls the relevant method, and returns the result.
- **WSGI scripts** — thin wrappers that read the x0 POST payload, call `ServiceRouter.send()`, and return JSON to the x0 frontend.

---

## Stackfield API notes

Stackfield's REST API is available at `https://www.stackfield.com/api/v1/`.
Key endpoints used:

| Endpoint | Purpose |
|---|---|
| `GET /v1/user` | Verify credentials |
| `POST /v1/rooms/{room_id}/tasks` | Create a new task |

The **Room ID** can be found in Stackfield under *Room settings → General → Room ID* or via the URL slug.

---

## License

See [LICENSE](LICENSE).
59 changes: 59 additions & 0 deletions database/01-create-schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
-- ]*[ ------------------------------------------------------------------ ]*[
-- . github2stackfield - Database Schema .
-- ]*[ ------------------------------------------------------------------ ]*[
-- . .
-- . Run against an existing x0 PostgreSQL database. .
-- . The x0 framework must be set up first (see x0 repository). .
-- . .
-- ]*[ ------------------------------------------------------------------ ]*[

-- Application schema
CREATE SCHEMA IF NOT EXISTS github2sf;

-- ---------------------------------------------------------------------------
-- API Credentials storage
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS github2sf.credentials (
credential_type VARCHAR(20) NOT NULL,
username_or_email TEXT NOT NULL,
api_token TEXT NOT NULL,
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT pk_credentials PRIMARY KEY (credential_type)
);

COMMENT ON TABLE github2sf.credentials IS
'Stores GitHub and Stackfield API credentials (one row per credential_type).';

-- ---------------------------------------------------------------------------
-- Application configuration key-value store
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS github2sf.app_config (
config_key VARCHAR(100) NOT NULL,
value TEXT,
updated_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT pk_app_config PRIMARY KEY (config_key)
);

COMMENT ON TABLE github2sf.app_config IS
'Key-value store for github2stackfield application runtime configuration.';

-- ---------------------------------------------------------------------------
-- Issue / Task mapping log
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS github2sf.issue_task_mapping (
id SERIAL NOT NULL,
github_repo TEXT NOT NULL,
github_issue_number INTEGER NOT NULL,
github_issue_title TEXT,
stackfield_room_id TEXT NOT NULL,
stackfield_task_id TEXT,
stackfield_task_url TEXT,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
CONSTRAINT pk_issue_task_mapping PRIMARY KEY (id)
);

CREATE INDEX IF NOT EXISTS idx_issue_task_mapping_repo_issue
ON github2sf.issue_task_mapping (github_repo, github_issue_number);

COMMENT ON TABLE github2sf.issue_task_mapping IS
'Audit log of all GitHub issue → Stackfield task mappings created by the app.';
22 changes: 22 additions & 0 deletions database/02-insert-config.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- ]*[ ------------------------------------------------------------------ ]*[
-- . github2stackfield - x0 Application Configuration .
-- ]*[ ------------------------------------------------------------------ ]*[
-- . .
-- . Insert the x0 system.config rows for the github2stackfield app. .
-- . Adjust app_id value if your x0 setup uses a different identifier. .
-- . .
-- ]*[ ------------------------------------------------------------------ ]*[

-- Remove any previously inserted config for this app
DELETE FROM system.config WHERE app_id = 'github2sf';

INSERT INTO system.config (app_id, config_group, "value") VALUES
('github2sf', 'index_title', 'GitHub ↔ Stackfield Connector'),
('github2sf', 'debug_level', '0'),
('github2sf', 'display_language', 'en'),
('github2sf', 'default_screen', 'Screen1'),
('github2sf', 'parent_window_url', 'null'),
('github2sf', 'subdir', '/static/github2sf'),
('github2sf', 'config_file_menu', 'menu.json'),
('github2sf', 'config_file_object', 'object.json'),
('github2sf', 'config_file_skeleton','skeleton.json');
116 changes: 116 additions & 0 deletions database/03-insert-text.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
-- ]*[ ------------------------------------------------------------------ ]*[
-- . github2stackfield - UI Text / Localisation .
-- ]*[ ------------------------------------------------------------------ ]*[
-- . .
-- . Insert into the x0 webui.text table. .
-- . Both English and German translations are provided. .
-- . .
-- ]*[ ------------------------------------------------------------------ ]*[

-- Navigation / Menu
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.MENU.SCREEN1', 'menu', 'API-Zugangsdaten', 'User Credentials');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.MENU.SCREEN2', 'menu', 'Issue / Aufgaben-Mapping', 'Issue / Task Mapping');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.MENU.SCREEN3', 'menu', 'Stackfield-Aufgabe verbinden', 'Connect Stackfield Task');

-- Screen 1 – GitHub Credentials
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.SECTION.HEADER', 'screen1', 'GitHub API-Zugangsdaten', 'GitHub API Credentials');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.SECTION.SUBHEADER', 'screen1', 'Benutzername und Personal Access Token eingeben', 'Enter your GitHub username and Personal Access Token');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.USER.LABEL', 'screen1', 'GitHub Benutzername', 'GitHub Username');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.TOKEN.LABEL', 'screen1', 'GitHub Personal Access Token', 'GitHub Personal Access Token');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.VERIFY.BUTTON', 'screen1', 'GitHub Zugangsdaten prüfen', 'Verify GitHub Credentials');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.GITHUB.VERIFY.NOTIFY', 'screen1', 'GitHub Authentifizierung', 'GitHub Authentication');

-- Screen 1 – Stackfield Credentials
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.SECTION.HEADER', 'screen1', 'Stackfield API-Zugangsdaten', 'Stackfield API Credentials');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.SECTION.SUBHEADER', 'screen1', 'E-Mail-Adresse und API-Token eingeben', 'Enter your Stackfield email and API token');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.EMAIL.LABEL', 'screen1', 'Stackfield E-Mail', 'Stackfield Email');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.TOKEN.LABEL', 'screen1', 'Stackfield API-Token', 'Stackfield API Token');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.VERIFY.BUTTON', 'screen1', 'Stackfield Zugangsdaten prüfen', 'Verify Stackfield Credentials');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN1.STACKFIELD.VERIFY.NOTIFY', 'screen1', 'Stackfield Authentifizierung', 'Stackfield Authentication');

-- Screen 2 – Search
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.SEARCH.SECTION.HEADER', 'screen2', 'GitHub Issues suchen', 'Search GitHub Issues');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.SEARCH.SECTION.SUBHEADER', 'screen2', 'Repository und Suchbegriff eingeben', 'Enter the repository and an optional search term');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.SEARCH.QUERY.LABEL', 'screen2', 'Suchbegriff (optional)', 'Search query (optional)');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.SEARCH.REPO.LABEL', 'screen2', 'Repository (owner/repo)', 'Repository (owner/repo)');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.SEARCH.BUTTON', 'screen2', 'Issues suchen', 'Search Issues');

-- Screen 2 – Issue List columns
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.LIST.COL.NUMBER', 'screen2', 'Nr.', '#');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.LIST.COL.TITLE', 'screen2', 'Titel', 'Title');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.LIST.COL.STATE', 'screen2', 'Status', 'State');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.LIST.COL.CREATED', 'screen2', 'Erstellt', 'Created');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.LIST.COL.ASSIGNEE', 'screen2', 'Zugewiesen', 'Assignee');

-- Screen 2 – Context menu
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN2.CONTEXTMENU.CONNECT', 'screen2', 'Stackfield-Aufgabe verbinden', 'Connect Stackfield Task');

-- Screen 3 – GitHub Issue Details
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.SECTION.HEADER', 'screen3', 'GitHub Issue Eigenschaften', 'GitHub Issue Properties');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.SECTION.SUBHEADER', 'screen3', 'Daten aus der GitHub API', 'Data from the GitHub API');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.NUMBER.LABEL', 'screen3', 'Issue-Nummer', 'Issue Number');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.STATE.LABEL', 'screen3', 'Status', 'State');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.TITLE.LABEL', 'screen3', 'Titel', 'Title');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.ISSUE.URL.LABEL', 'screen3', 'GitHub URL', 'GitHub URL');

-- Screen 3 – Stackfield Mapping
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.STACKFIELD.SECTION.HEADER', 'screen3', 'Stackfield Aufgaben-Zuordnung', 'Stackfield Task Mapping');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.STACKFIELD.SECTION.SUBHEADER', 'screen3', 'Ziel-Raum und Aufgaben-Details', 'Target room and task details');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.STACKFIELD.ROOM.LABEL', 'screen3', 'Stackfield Raum-ID', 'Stackfield Room ID');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.TASK.TITLE.LABEL', 'screen3', 'Aufgaben-Titel', 'Task Title');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.TASK.DESC.LABEL', 'screen3', 'Beschreibung', 'Description');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.TASK.PRIORITY.LABEL', 'screen3', 'Priorität', 'Priority');

-- Screen 3 – Priority options
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.PRIORITY.LOW', 'screen3', 'Niedrig', 'Low');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.PRIORITY.MEDIUM', 'screen3', 'Mittel', 'Medium');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.PRIORITY.HIGH', 'screen3', 'Hoch', 'High');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.PRIORITY.URGENT', 'screen3', 'Dringend', 'Urgent');

-- Screen 3 – Create button
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.CREATE.BUTTON', 'screen3', 'Neue Stackfield-Aufgabe erstellen', 'Create New Stackfield Task');
INSERT INTO webui.text (id, "group", value_de, value_en) VALUES
('TXT.SCREEN3.CREATE.NOTIFY', 'screen3', 'Stackfield Aufgabe erstellen', 'Create Stackfield Task');
Loading