Add ticket branch discovery by ticket key #17

Merged
Twyco merged 3 commits from codex/0.2-gitea-integration/03-ticket-branch-discovery-by-ticket-key into main 2026-04-01 17:15:11 +00:00
46 changed files with 331485 additions and 93 deletions
Showing only changes of commit 517b2ccf70 - Show all commits
+49
View File
@@ -0,0 +1,49 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.gitattributes
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/docker-compose*
**/compose.y*ml
**/compose.*.y*ml
!**/composer.json
!**/composer.lock
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
vendor
**/.DS_Store
.editorconfig
.env.example
.env.mariadb
.env.mariadb.example
.env.production
LICENSE
README.md
# Ignore development-related files and directories
# For development environment, we will use mount bind to mount the source code to the container
# So we don't need to copy the source code to the container for development
tests/
phpunit.xml
phpunit.xml.dist
+19 -50
View File
@@ -1,65 +1,34 @@
APP_NAME=Laravel APP_NAME=kanbangit
APP_ENV=local APP_ENV=local
APP_KEY= APP_KEY=
APP_DEBUG=true APP_DEBUG=true
APP_TIMEZONE=Europe/Berlin
APP_URL=http://localhost APP_URL=http://localhost
API_KEY=
APP_LOCALE=en APP_LOCALE=en
APP_FALLBACK_LOCALE=en APP_FALLBACK_LOCALE=en
APP_FAKER_LOCALE=en_US APP_FAKER_LOCALE=en_US
APP_MAINTENANCE_DRIVER=file LOG_STACK=daily
# APP_MAINTENANCE_STORE=database
# PHP_CLI_SERVER_WORKERS=4 DB_CONNECTION=mariadb
DB_HOST=kanbangit-db
DB_PORT=3306
DB_DATABASE=kanbangit
DB_USERNAME=
DB_PASSWORD=
BCRYPT_ROUNDS=12 #Default
MAIL_MAILER=smtp
LOG_CHANNEL=stack MAIL_HOST=mail.example.de
LOG_STACK=single MAIL_PORT=587
LOG_DEPRECATIONS_CHANNEL=null MAIL_USERNAME=no-reply@example.com
LOG_LEVEL=debug MAIL_PASSWORD=
MAIL_ENCRYPTION=tls
DB_CONNECTION=sqlite MAIL_FROM_ADDRESS=no-reply@example.com
# DB_HOST=127.0.0.1
# DB_PORT=3306
# DB_DATABASE=laravel
# DB_USERNAME=root
# DB_PASSWORD=
SESSION_DRIVER=database
SESSION_LIFETIME=120
SESSION_ENCRYPT=false
SESSION_PATH=/
SESSION_DOMAIN=null
BROADCAST_CONNECTION=log
FILESYSTEM_DISK=local
QUEUE_CONNECTION=database
CACHE_STORE=database
# CACHE_PREFIX=
MEMCACHED_HOST=127.0.0.1
REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PASSWORD=null
REDIS_PORT=6379
MAIL_MAILER=log
MAIL_SCHEME=null
MAIL_HOST=127.0.0.1
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}" MAIL_FROM_NAME="${APP_NAME}"
AWS_ACCESS_KEY_ID= FILESYSTEM_DISK=public
AWS_SECRET_ACCESS_KEY=
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=
AWS_USE_PATH_STYLE_ENDPOINT=false
VITE_APP_NAME="${APP_NAME}" VITE_APP_NAME="${APP_NAME}"
+4
View File
@@ -0,0 +1,4 @@
MARIADB_DATABASE=kanbangit
MARIADB_USER=kanbangit
MARIADB_PASSWORD=
MARIADB_ROOT_PASSWORD=
+1
View File
@@ -13,6 +13,7 @@
.DS_Store .DS_Store
.env .env
.env.backup .env.backup
.env.mariadb
.env.production .env.production
.phpactor.json .phpactor.json
.phpunit.result.cache .phpunit.result.cache
@@ -9,6 +9,7 @@ use App\Models\BoardColumn;
use App\Models\Project; use App\Models\Project;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\DB;
use Inertia\Inertia; use Inertia\Inertia;
@@ -42,12 +43,13 @@ class ProjectBoardSettingsController extends Controller
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project), $this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
403, 403,
); );
$validated = $request->validated();
$desiredPosition = $request->position() ?? $project->boardColumns()->count() + 1; $desiredPosition = Arr::get($validated, 'position') ?? $project->boardColumns()->count() + 1;
$boardColumn = DB::transaction(function () use ($project, $request, $desiredPosition): BoardColumn { $boardColumn = DB::transaction(function () use ($project, $validated, $desiredPosition): BoardColumn {
$boardColumn = $project->boardColumns()->create([ $boardColumn = $project->boardColumns()->create([
'name' => $request->validated('name'), 'name' => Arr::get($validated, 'name'),
'position' => $project->boardColumns()->count() + 1, 'position' => $project->boardColumns()->count() + 1,
]); ]);
@@ -69,13 +71,14 @@ class ProjectBoardSettingsController extends Controller
$this->projectPermissionService->userCanManageProjectAccess($request->user(), $project), $this->projectPermissionService->userCanManageProjectAccess($request->user(), $project),
403, 403,
); );
$validated = $request->validated();
DB::transaction(function () use ($boardColumn, $project, $request): void { DB::transaction(function () use ($boardColumn, $project, $validated): void {
$boardColumn->update([ $boardColumn->update([
'name' => $request->validated('name'), 'name' => Arr::get($validated, 'name'),
]); ]);
$this->reorderColumns($project, $boardColumn, $request->position()); $this->reorderColumns($project, $boardColumn, Arr::get($validated, 'position'));
}); });
return to_route('projects.settings.board', $project) return to_route('projects.settings.board', $project)
@@ -59,6 +59,17 @@ class ProjectTicketLinkController extends Controller
], ],
); );
$this->createTicketWorklogEntry->handle(
$linkedTicket,
$request->user(),
'link_updated',
"Linked {$ticket->ticket_key} as {$ticketLink->ticketLinkType->label}.",
[
'linked_ticket_key' => $ticket->ticket_key,
'ticket_link_type' => $ticketLink->ticketLinkType->label,
],
);
return $ticketLink; return $ticketLink;
}); });
@@ -103,6 +114,17 @@ class ProjectTicketLinkController extends Controller
'ticket_link_type' => $label, 'ticket_link_type' => $label,
], ],
); );
$this->createTicketWorklogEntry->handle(
$linkedTicket,
$request->user(),
'link_removed',
"Removed {$label} link to {$ticket->ticket_key}.",
[
'linked_ticket_key' => $ticket->ticket_key,
'ticket_link_type' => $label,
],
);
}); });
return to_route('projects.tickets.show', [$project, $ticket]) return to_route('projects.tickets.show', [$project, $ticket])
@@ -12,7 +12,7 @@ class UpdateUserAdminStatusRequest extends FormRequest
*/ */
public function authorize(): bool public function authorize(): bool
{ {
return $this->user()?->is_admin === true; return $this->user()?->can('access-admin') ?? false;
} }
/** /**
@@ -27,15 +27,4 @@ class StoreBoardColumnRequest extends FormRequest
'position' => ['nullable', 'integer', 'min:1'], 'position' => ['nullable', 'integer', 'min:1'],
]; ];
} }
public function position(): ?int
{
$position = $this->validated('position');
if ($position === null) {
return null;
}
return (int) $position;
}
} }
@@ -27,9 +27,4 @@ class UpdateBoardColumnRequest extends FormRequest
'position' => ['required', 'integer', 'min:1'], 'position' => ['required', 'integer', 'min:1'],
]; ];
} }
public function position(): int
{
return (int) $this->validated('position');
}
} }
@@ -16,7 +16,7 @@ return new class extends Migration
$table->foreignId('ticket_id')->constrained()->cascadeOnDelete(); $table->foreignId('ticket_id')->constrained()->cascadeOnDelete();
$table->foreignId('ticket_field_definition_id')->constrained()->cascadeOnDelete(); $table->foreignId('ticket_field_definition_id')->constrained()->cascadeOnDelete();
$table->foreignId('value_user_id')->nullable()->constrained('users')->nullOnDelete(); $table->foreignId('value_user_id')->nullable()->constrained('users')->nullOnDelete();
$table->decimal('value_number', 10, 2)->nullable(); $table->decimal('value_number', 16, 4)->nullable();
$table->timestamps(); $table->timestamps();
$table->unique(['ticket_id', 'ticket_field_definition_id']); $table->unique(['ticket_id', 'ticket_field_definition_id']);
+69
View File
@@ -0,0 +1,69 @@
services:
web:
container_name: kanbangit-web
build:
context: .
dockerfile: ./docker/nginx/Dockerfile
restart: unless-stopped
ports:
- "8080:80"
volumes:
- kanbangit-storage:/var/www/storage:ro
- kanbangit-public-assets:/var/www/public/build:ro
env_file:
- .env.production
networks:
- kanbangit-web
depends_on:
php-fpm:
condition: service_healthy
php-fpm:
container_name: kanbangit-php-fpm
build:
context: .
dockerfile: ./docker/php-fpm/Dockerfile
target: production
restart: unless-stopped
volumes:
- kanbangit-storage:/var/www/storage
- kanbangit-public-assets:/var/www/public/build
env_file:
- .env.production
networks:
- kanbangit-web
- kanbangit-db
healthcheck:
test: ["CMD-SHELL", "php-fpm-healthcheck || exit 1"]
interval: 10s
timeout: 5s
retries: 3
depends_on:
mariadb:
condition: service_healthy
mariadb:
container_name: kanbangit-db
image: mariadb:11.4
restart: unless-stopped
env_file:
- .env.mariadb
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- mariadb-data-production:/var/lib/mysql
networks:
- kanbangit-db
healthcheck:
test: [ "CMD", "healthcheck.sh", "--connect", "--innodb_initialized" ]
start_period: 10s
interval: 10s
timeout: 5s
retries: 3
networks:
kanbangit-db:
kanbangit-web:
volumes:
mariadb-data-production:
kanbangit-storage:
kanbangit-public-assets:
+20
View File
@@ -0,0 +1,20 @@
FROM php:8.4-cli-bookworm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates git unzip \
&& rm -rf /var/lib/apt/lists/*
RUN curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get update && apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/*
RUN curl -sS https://getcomposer.org/installer | php -- \
--install-dir=/usr/local/bin --filename=composer
WORKDIR /var/www
COPY . /var/www
RUN composer install --no-dev --prefer-dist --no-interaction --no-progress --optimize-autoloader
RUN npm ci && npm run build
FROM nginx:alpine
COPY ./docker/nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /var/www/public /var/www/public
WORKDIR /var/www/public
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
+33
View File
@@ -0,0 +1,33 @@
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
client_max_body_size 100m;
server {
listen 80;
server_name localhost;
root /var/www/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ^~ /storage/ {
alias /var/www/storage/app/public/;
try_files $uri =404;
access_log off;
expires 7d;
add_header Cache-Control "public";
}
location ~ \.php$ {
fastcgi_pass php-fpm:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
}
+66
View File
@@ -0,0 +1,66 @@
FROM php:8.4-fpm AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
unzip \
libpq-dev \
libonig-dev \
libssl-dev \
libxml2-dev \
libcurl4-openssl-dev \
libicu-dev \
libzip-dev \
libmagickwand-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
&& docker-php-ext-install -j$(nproc) \
pdo_mysql \
pdo_pgsql \
pgsql \
opcache \
intl \
zip \
bcmath \
soap \
&& docker-php-ext-configure gd --with-jpeg --with-freetype \
&& docker-php-ext-install -j$(nproc) gd \
&& pecl install imagick \
&& docker-php-ext-enable imagick \
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
WORKDIR /var/www
COPY . /var/www
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
&& composer install --no-dev --optimize-autoloader --no-interaction --no-progress --prefer-dist
FROM php:8.4-fpm AS production
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq-dev \
libicu-dev \
libzip-dev \
libfcgi-bin \
procps \
libmagickwand-dev \
&& apt-get autoremove -y && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN curl -o /usr/local/bin/php-fpm-healthcheck \
https://raw.githubusercontent.com/renatomefi/php-fpm-healthcheck/master/php-fpm-healthcheck \
&& chmod +x /usr/local/bin/php-fpm-healthcheck
COPY ./docker/php-fpm/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
COPY ./storage /var/www/storage-init
COPY --from=builder /usr/local/lib/php/extensions/ /usr/local/lib/php/extensions/
COPY --from=builder /usr/local/etc/php/conf.d/ /usr/local/etc/php/conf.d/
COPY --from=builder /usr/local/bin/docker-php-ext-* /usr/local/bin/
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
RUN printf '%s\n' \
'[global]' \
'daemonize = no' \
'' \
'[www]' \
'pm.status_path = /status' \
> /usr/local/etc/php-fpm.d/zz-docker.conf
COPY --from=builder /var/www /var/www
WORKDIR /var/www
RUN chown -R www-data:www-data /var/www
USER www-data
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
EXPOSE 9000
CMD ["php-fpm"]
+11
View File
@@ -0,0 +1,11 @@
#!/bin/sh
set -e
if [ ! "$(ls -A /var/www/storage)" ]; then
echo "Initializing storage directory..."
cp -R /var/www/storage-init/. /var/www/storage
chown -R www-data:www-data /var/www/storage
fi
rm -rf /var/www/storage-init
php artisan config:cache
php artisan route:cache
exec "$@"
+3
View File
@@ -10,11 +10,14 @@ KanbanGit is intended to become a self-hostable project management application w
The product centers around projects. Each project owns its own board configuration, backlog, ticket metadata, ticket hierarchy, and repository connections. The architecture must support application-wide administration plus project-scoped permissions that can be assigned to users and teams. The product centers around projects. Each project owns its own board configuration, backlog, ticket metadata, ticket hierarchy, and repository connections. The architecture must support application-wide administration plus project-scoped permissions that can be assigned to users and teams.
First Mockup https://stitch.withgoogle.com/projects/353263382234239260
## Documentation map ## Documentation map
- [Product Spec](product-spec.md) describes expected product behavior, user-visible rules, and scope boundaries. - [Product Spec](product-spec.md) describes expected product behavior, user-visible rules, and scope boundaries.
- [Domain Model](domain-model.md) defines the canonical domain concepts, relationships, and invariants that future database tables, Eloquent models, policies, and frontend types should follow. - [Domain Model](domain-model.md) defines the canonical domain concepts, relationships, and invariants that future database tables, Eloquent models, policies, and frontend types should follow.
- [Architecture Roadmap](architecture-roadmap.md) captures the planned architecture direction, permission strategy, integration boundaries, and staged implementation intent. - [Architecture Roadmap](architecture-roadmap.md) captures the planned architecture direction, permission strategy, integration boundaries, and staged implementation intent.
- [GitHub Integration Plan](github-integration-plan.md) captures the first planned GitHub-specific integration strategy for release `0.3`.
- [Release Task Breakdown](releases/README.md) splits the planned work into small ordered implementation tickets grouped by release. - [Release Task Breakdown](releases/README.md) splits the planned work into small ordered implementation tickets grouped by release.
## Glossary ## Glossary
File diff suppressed because one or more lines are too long
+107
View File
@@ -0,0 +1,107 @@
# GitHub Integration Plan
## Purpose
This document describes the intended first GitHub integration approach for KanbanGit release `0.3`.
The goal is to add GitHub as the second supported Git provider without redesigning the provider-neutral repository, branch, and pull request flows already introduced for Gitea.
## Source Material
- Primary API source: `docs/gitApiDocs/api.github.com.json`
- Primary target API: GitHub REST v3 on `https://api.github.com`
## Release Direction
Release `0.3` should deliver GitHub Cloud support through the existing per-user Git connection model.
Key decisions:
- The first GitHub auth mode is a user-managed token connection.
- GitHub Cloud uses a fixed API base URL instead of a user-entered instance URL.
- Connection validation should confirm repository read access.
- Authenticated user metadata from `GET /user` is useful for display, but it should not be the only success criterion if repository access can already be verified.
- The provider abstraction must remain the boundary between ticket or project logic and GitHub-specific HTTP details.
## Required API Surface for v0.3
The release should be planned around these endpoints from `api.github.com`:
- `GET /user`
- `GET /user/repos`
- `GET /repos/{owner}/{repo}`
- `GET /repos/{owner}/{repo}/branches`
- `GET /repos/{owner}/{repo}/pulls`
These endpoints are sufficient for the current KanbanGit feature set:
- storing a user Git connection
- discovering repositories the user can access
- linking repositories to projects
- matching branches to tickets by ticket key
- matching pull requests to tickets by source branch name
## Provider-Neutral Data Mapping
The GitHub provider should map its responses into the existing provider-neutral contracts already used by Gitea integration.
### Authenticated user
- `id` -> `provider_user_id`
- `login` -> `account_login`
- `name` -> `account_name`
### Repository
- `id` -> `provider_repository_id`
- `owner.login` -> `owner_name`
- `name` -> `repository_name`
- `full_name` -> `repository_full_name`
- `html_url` -> `repository_url`
- `default_branch` -> default branch metadata when available
- `private` -> visibility label derivation
### Branch
- `name` -> branch name shown in ticket detail
### Pull request
- `number` -> provider identifier shown in ticket detail
- `title` -> pull request title
- `state` -> pull request status
- `head.ref` -> source branch used for ticket-key matching
- `html_url` -> outbound link target
## Product and UX Implications
- GitHub connections should appear beside Gitea connections in user settings.
- The GitHub connection form should not require a base URL for GitHub Cloud.
- Project repository settings should allow mixing Gitea and GitHub repositories in the same project when the provider abstraction already supports this.
- Ticket branch and pull request sections should continue to be grouped by repository connection so provider context stays obvious.
## Explicitly Deferred
The provided GitHub API definition also contains app and installation endpoints, but those should not be part of release `0.3`.
Deferred items:
- GitHub App authentication with `GET /app`
- installation discovery with `GET /app/installations`
- installation token generation with `POST /app/installations/{installation_id}/access_tokens`
- installation-scoped repository listing with `GET /installation/repositories`
- GitHub Enterprise Server support
- webhooks
- review, merge, and status-check actions
- GraphQL usage
- branch creation from inside KanbanGit
## Release Outcome
If release `0.3` is completed as planned, KanbanGit should support GitHub repository awareness with the same core workflow currently targeted for Gitea:
- connect a provider account
- expose accessible repositories
- link repositories to projects
- surface ticket-related branches
- surface ticket-related pull requests
+69
View File
@@ -0,0 +1,69 @@
## Missing features
### Ticket
- general:
- new skill, user resources
- ueberall $array['''] ersetzen durch Arr::get()
- (mehr über middlewares steuern)
- why uses everywhere forceFill?
- $request->validated('xxx') ersetzten durch:
- ``` $validated = $request->validate(); $xxx = Arr:get($validated, 'xxx'); ```
- oder: ```$request->position()```, methoden entfernen, wie:
```
public function position(): ?int {
position = this->validated('position');
if ($position === null) {
return null;
}
return (int) $position;
}
```
- app/Http/Requests/UpdateProjectTicketRequest.php and other ProjectTicket only to Ticket...
- mehrere actions/quereys in services auslagern
- 02:
- Delete Projekt as Admin
- 03
- permission models? eg.g syncUserPermissions uses only db table
- mit morph auf perm user (z.b. team oder user)
- Project Permissions Service
- syncTeamMembers(Team $team, array $userIds): void → ist wrong here
- gate statt has permission methods
- permission for creating teams without beeing admind -> teamowner
- team roles
- 05
- ticket type remove build in
- → add is parentBool to ticketType -> remove system_key (maybe add slug)
- auto generate one parent one task on new project as now
- disable delete last child ticket type
- initializeProjectTicketMetadata nur beim init benutzen
- every form request removed unnecessary methods: like position in StoreProjectNamedSettingRequest
- use Arr:get(\$validated, 'value'). instead of these messages or \$request->value
- Made ticket priority more static(change to has one) in ticket, but only show if there are more than one configured.
- made story points more static(change to table field in table), but only show if checked in project settings → new value on Project: hasStoryPoints
- more ticket field types (string, string array, boolean, precision (double))
- abortUnlessCanManage(xxx) entfernen, auch gate benutzen (ProjectTicketMetadataSettingsController)
- ticket assignee fest einbauen in ticket
- 06
- add Ticket Status
- ticketstatus can have one or more board columns
- one board cloumn cant be in two or more ticket statuses
- replace board_colum_id on ticket with ticket status id
- ticket status has an n:m relation in which relatin the ticket can switch
- (ui friendly example all following or select)
- change Project Backlog Controller → Project Controller
- remove store from project board controller and route projects/{project}/backlog, change to ticket controller, create
- 08
- worklog überarbeiten → type als worklog enum → json je nach enum type
- UpdateProjectTicket aufräumen
- 09
- app/Http/Controllers/ProjectTicketController.php parent ids anderes queren. nicht alle child ids berechnen und excluden -> immer parent vom parent bis null
- later:
- if deleting ticket type select a new ticket type which all existing ticket should get or select delete all tickets of that type
- repository_connections check base Url maybe unnessecary?
@@ -0,0 +1,32 @@
# 01 GitHub Provider Foundation and Auth Strategy
## Goal
Introduce GitHub as the second concrete Git provider behind the existing provider abstraction while keeping the first GitHub auth mode aligned with KanbanGit's current per-user connection model.
## Requirements
- Add GitHub as a supported provider key and provider client.
- Target GitHub Cloud on `https://api.github.com`.
- Keep project, ticket, and repository-connection core logic free from GitHub-specific assumptions wherever possible.
- The provider client must support the API surface needed for:
- authenticated user metadata
- repository listing
- repository lookup
- branch listing
- pull request listing
- The release must choose one initial GitHub auth strategy and document it clearly.
## Acceptance Criteria
- The codebase has a clear GitHub provider implementation behind the existing provider boundary.
- GitHub-specific HTTP details do not leak into ticket or project core logic.
- The selected GitHub auth strategy is compatible with the current `UserGitConnection` concept.
- The provider foundation is sufficient for later repository linking, branch discovery, and pull request visibility tickets in release `0.3`.
## Out of Scope
- GitHub App JWT handling
- GitHub installation tokens
- GitHub Enterprise Server support
- Webhook processing
@@ -0,0 +1,26 @@
# 02 User GitHub Connections and Repository Discovery
## Goal
Allow a user to connect GitHub in settings and expose the repositories that user can read through the chosen token.
## Requirements
- Add GitHub as an option in user Git connection settings.
- GitHub Cloud connections must use a fixed API base URL instead of asking the user for an instance URL.
- Connection verification must confirm repository read access.
- Authenticated user metadata from `GET /user` should be stored when available for display purposes.
- Accessible repositories must be loaded from the authenticated user's GitHub repository list.
## Acceptance Criteria
- A user can add, update, list, and remove a GitHub connection from settings.
- A token that can read repositories is accepted.
- A token that cannot read repositories is rejected with a clear validation error.
- Repositories available through the GitHub connection appear as selectable repository sources in project settings.
## Out of Scope
- OAuth browser login flows
- GitHub App installation authorization
- Background repository synchronization beyond on-demand fetches
@@ -0,0 +1,26 @@
# 03 Project GitHub Repository Connections
## Goal
Allow projects to link one or more GitHub repositories through the existing project repository settings workflow.
## Requirements
- Reuse the existing repository connection model and project settings flow for GitHub.
- A GitHub repository connection must store enough data to identify the provider and repository.
- Repository linking must remain protected by the appropriate project-level permissions.
- The repository list must make the provider source clear when a project uses more than one Git provider.
- The design should allow a project to hold both Gitea and GitHub repository connections when supported by the underlying provider abstraction.
## Acceptance Criteria
- An authorized user can add, edit, list, and remove GitHub repository connections for a project.
- A project can hold more than one GitHub repository connection.
- A project can display mixed-provider repository connections without ambiguity.
- The stored data remains compatible with the provider-neutral repository connection layer introduced in earlier releases.
## Out of Scope
- Cross-project shared repository connections
- Automatic background metadata refresh
- Organization-level permission management inside KanbanGit
@@ -0,0 +1,26 @@
# 04 GitHub Ticket Branch Discovery by Ticket Key
## Goal
Show GitHub branches related to a ticket by matching configured repository branches against the ticket key prefix.
## Requirements
- Add GitHub branch matches to the ticket detail view through the same provider-neutral branch section used for other providers.
- For each configured GitHub repository connection, identify branches whose names start with the ticket key.
- A matching branch must be shown on the ticket when its name starts with the exact ticket key, for example `AUTH-42-...`.
- Present matches in a way that makes the repository source and provider clear.
- The first implementation may use direct fetches or a simple cache, but it must not require a large background sync system.
## Acceptance Criteria
- A ticket with key `AUTH-42` shows GitHub branches such as `AUTH-42-login-form`.
- Branches that do not start with the ticket key are not shown.
- If multiple configured repositories contain matching branches, the ticket shows matches from each relevant repository.
- The branch section remains compatible with mixed-provider repository connections.
## Out of Scope
- Commit listing
- Branch creation from inside KanbanGit
- Cross-ticket branch disambiguation beyond exact ticket-key prefix matching
@@ -0,0 +1,29 @@
# 05 GitHub Ticket Pull Request Visibility
## Goal
Show GitHub pull requests related to a ticket alongside the matching branches in the ticket detail view.
## Requirements
- Add a GitHub pull requests section to the ticket detail view using the existing provider-neutral pull request presentation model.
- Pull requests must be associated to a ticket using the same ticket-key-based branch naming convention as branch discovery.
- Show enough pull request information to identify and open the matching item, at minimum:
- title
- provider identifier or number
- repository source
- status if available
- The implementation may derive pull request matches from source branch names.
## Acceptance Criteria
- A ticket shows GitHub pull requests whose source branch starts with the ticket key.
- Pull requests unrelated to the ticket key are not shown.
- The ticket detail view clearly separates branch matches from pull request matches.
- Pull request visibility remains compatible with mixed-provider repository connections.
## Out of Scope
- Pull request review actions inside KanbanGit
- Merge controls
- Status checks and CI reporting
@@ -0,0 +1,41 @@
# Release 0.3 - GitHub Integration
## Purpose
Add GitHub as the second supported Git provider with parity to the current Gitea-based repository, branch, and pull request visibility flows.
## API Basis
This release is planned from `docs/gitApiDocs/api.github.com.json`.
Primary endpoints for release `0.3`:
- `GET /user`
- `GET /user/repos`
- `GET /repos/{owner}/{repo}`
- `GET /repos/{owner}/{repo}/branches`
- `GET /repos/{owner}/{repo}/pulls`
## Release Decisions
- The first release targets GitHub Cloud on `https://api.github.com`.
- The first auth mode is a per-user token connection.
- Repository readability is the connection gate.
- Authenticated user profile data is an optional enrichment step for display metadata.
- GitHub App and installation flows are intentionally deferred.
## Tickets
- [01 GitHub Provider Foundation and Auth Strategy](01-github-provider-foundation-and-auth-strategy.md)
- [02 User GitHub Connections and Repository Discovery](02-user-github-connections-and-repository-discovery.md)
- [03 Project GitHub Repository Connections](03-project-github-repository-connections.md)
- [04 GitHub Ticket Branch Discovery by Ticket Key](04-github-ticket-branch-discovery-by-ticket-key.md)
- [05 GitHub Ticket Pull Request Visibility](05-github-ticket-pull-request-visibility.md)
## Deferred from This Release
- GitHub App authentication and installations
- GitHub Enterprise Server
- Webhooks
- Merge, review, and status-check actions
- Branch creation from ticket detail
+2
View File
@@ -6,9 +6,11 @@ This directory contains small, ordered implementation tickets grouped by release
- [Release 0.1 - Kanban, Ticketing, Users, and Permissions Foundation](0.1-kanban-foundation/01-admin-and-user-management-foundation.md) - [Release 0.1 - Kanban, Ticketing, Users, and Permissions Foundation](0.1-kanban-foundation/01-admin-and-user-management-foundation.md)
- [Release 0.2 - Gitea Integration](0.2-gitea-integration/01-git-provider-abstraction-and-gitea-foundation.md) - [Release 0.2 - Gitea Integration](0.2-gitea-integration/01-git-provider-abstraction-and-gitea-foundation.md)
- [Release 0.3 - GitHub Integration](0.3-github-integration/README.md)
## Scope notes ## Scope notes
- Release `0.1` covers the core Kanban, ticket, user, team, and permission foundation. - Release `0.1` covers the core Kanban, ticket, user, team, and permission foundation.
- Release `0.2` covers the first Git provider integration with Gitea. - Release `0.2` covers the first Git provider integration with Gitea.
- Release `0.3` covers GitHub Cloud as the second Git provider using the existing provider abstraction and user connection model.
- Roadmap and other currently out-of-scope items are intentionally not broken into tickets here. - Roadmap and other currently out-of-scope items are intentionally not broken into tickets here.
+1 -1
View File
@@ -22,10 +22,10 @@ import {
SidebarMenuButton, SidebarMenuButton,
SidebarMenuItem, SidebarMenuItem,
} from '@/components/ui/sidebar'; } from '@/components/ui/sidebar';
import { dashboard } from '@/routes';
import { index as adminTeams } from '@/routes/admin/teams'; import { index as adminTeams } from '@/routes/admin/teams';
import { index as adminUsers } from '@/routes/admin/users'; import { index as adminUsers } from '@/routes/admin/users';
import { index as projectsIndex } from '@/routes/projects'; import { index as projectsIndex } from '@/routes/projects';
import { dashboard } from '@/routes';
import type { NavItem } from '@/types'; import type { NavItem } from '@/types';
const page = usePage(); const page = usePage();
@@ -98,6 +98,7 @@ watch(
async (isOpen) => { async (isOpen) => {
if (!isOpen) { if (!isOpen) {
resetModalState(); resetModalState();
return; return;
} }
+1 -1
View File
@@ -1,6 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { Form, Head } from '@inertiajs/vue3'; import { Form, Head } from '@inertiajs/vue3';
import { Shield, Users } from 'lucide-vue-next'; import { Shield } from 'lucide-vue-next';
import TeamController from '@/actions/App/Http/Controllers/TeamController'; import TeamController from '@/actions/App/Http/Controllers/TeamController';
import Heading from '@/components/Heading.vue'; import Heading from '@/components/Heading.vue';
import InputError from '@/components/InputError.vue'; import InputError from '@/components/InputError.vue';
-1
View File
@@ -19,7 +19,6 @@ declare module '@inertiajs/core' {
name: string; name: string;
auth: Auth; auth: Auth;
sidebarOpen: boolean; sidebarOpen: boolean;
[key: string]: unknown;
}; };
} }
} }
@@ -9,5 +9,4 @@ export type AvailableGitRepositorySourceModel = {
account_name: string | null; account_name: string | null;
error: string | null; error: string | null;
repositories: GitRepositoryOptionModel[]; repositories: GitRepositoryOptionModel[];
[key: string]: unknown;
}; };
@@ -8,5 +8,4 @@ export type BoardColumnModel = {
tickets?: TicketModel[]; tickets?: TicketModel[];
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -5,5 +5,4 @@ export type GitRepositoryOptionModel = {
full_name: string; full_name: string;
repository_url: string; repository_url: string;
visibility_label: string; visibility_label: string;
[key: string]: unknown;
}; };
@@ -4,5 +4,4 @@ export type ProjectModel = {
key: string; key: string;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -12,5 +12,4 @@ export type RepositoryConnectionModel = {
has_personal_access: boolean; has_personal_access: boolean;
created_at: string | null; created_at: string | null;
updated_at: string | null; updated_at: string | null;
[key: string]: unknown;
}; };
-1
View File
@@ -7,5 +7,4 @@ export type TeamModel = {
users: UserModel[]; users: UserModel[];
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -6,5 +6,4 @@ export type TicketFieldDefinitionModel = {
project_id: number; project_id: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -11,5 +11,4 @@ export type TicketFieldValueModel = {
value_user_id: number | null; value_user_id: number | null;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -10,5 +10,4 @@ export type TicketLinkModel = {
ticket_link_type_id: number; ticket_link_type_id: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -5,5 +5,4 @@ export type TicketLinkTypeModel = {
project_id: number; project_id: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -23,5 +23,4 @@ export type TicketModel = {
worklog_entries?: TicketWorklogEntryModel[]; worklog_entries?: TicketWorklogEntryModel[];
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -5,5 +5,4 @@ export type TicketPriorityModel = {
project_id: number; project_id: number;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -7,5 +7,4 @@ export type TicketTypeModel = {
system_key: string | null; system_key: string | null;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -10,5 +10,4 @@ export type TicketWorklogEntryModel = {
summary: string; summary: string;
ticket_id: number; ticket_id: number;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };
@@ -9,5 +9,4 @@ export type UserGitConnectionModel = {
account_name: string | null; account_name: string | null;
created_at: string | null; created_at: string | null;
updated_at: string | null; updated_at: string | null;
[key: string]: unknown;
}; };
-1
View File
@@ -7,5 +7,4 @@ export type UserModel = {
is_admin: boolean; is_admin: boolean;
created_at: string; created_at: string;
updated_at: string; updated_at: string;
[key: string]: unknown;
}; };