feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
/ * *
* Shinkan Jinkendo API Client
*
* Zentrale API - Kommunikation mit automatischer Token - Injektion
* /
2026-04-27 14:48:46 +02:00
import { stripHtmlToText } from './htmlUtils'
2026-05-13 08:58:41 +02:00
import { normalizeAdvanceMode , parseComboRepSeriesCountUi } from './combinationMethodProfileUi'
2026-05-14 21:31:04 +02:00
import { request , ACTIVE _CLUB _STORAGE _KEY } from '../api/client.js'
2026-04-27 14:48:46 +02:00
2026-05-14 21:31:04 +02:00
export { ACTIVE _CLUB _STORAGE _KEY }
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Auth
// ============================================================================
export async function login ( email , password ) {
return request ( '/api/auth/login' , {
method : 'POST' ,
body : JSON . stringify ( { email , password } )
} )
}
2026-05-05 16:40:49 +02:00
export async function register ( email , password , name , extra = { } ) {
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
return request ( '/api/auth/register' , {
method : 'POST' ,
2026-05-05 16:40:49 +02:00
body : JSON . stringify ( { email , password , name , ... extra } ) ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
} )
}
export async function logout ( ) {
return request ( '/api/auth/logout' , { method : 'POST' } )
}
export async function getCurrentProfile ( ) {
return request ( '/api/profiles/me' )
}
2026-05-05 16:12:17 +02:00
/** Liste aller Profile – nur für Plattform-Admins (Vereinsanlage). */
export async function listProfiles ( ) {
return request ( '/api/profiles' )
}
2026-05-05 21:05:52 +02:00
/** Alle Nutzer inkl. Vereinsmitgliedschaften — nur Portal-Admin (UI: Admin → Nutzer). */
export async function listAdminUsers ( ) {
return request ( '/api/admin/users' )
}
2026-05-07 12:36:46 +02:00
/** Medien-Speicher (MEDIA_ROOT + relativer Unterordner) — GET: admin/superadmin, PUT: nur superadmin. */
export async function getPlatformMediaStorage ( ) {
return request ( '/api/admin/platform-media-storage' )
}
export async function putPlatformMediaStorage ( payload ) {
return request ( '/api/admin/platform-media-storage' , {
method : 'PUT' ,
body : JSON . stringify ( payload ) ,
} )
}
2026-04-29 11:19:05 +02:00
export async function updateProfile ( profileId , data ) {
return request ( ` /api/profiles/ ${ profileId } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
2026-05-09 10:32:33 +02:00
/ * *
* Passwort anderer Konten : Standard leerer Body → E - Mail mit Reset - Link ( wie Passwort vergessen ) .
* Nur Super - Admins dürfen ` newPassword ` setzen ( direktes Überschreiben des Passwort - Hashes ) .
* /
export async function managementPasswordReset ( profileId , newPassword = null ) {
const body = { }
if ( newPassword != null && String ( newPassword ) . trim ( ) !== '' ) {
body . new _password = newPassword
}
2026-05-09 10:15:16 +02:00
return request ( ` /api/profiles/ ${ profileId } /management-password-reset ` , {
method : 'POST' ,
2026-05-09 10:32:33 +02:00
body : JSON . stringify ( body ) ,
2026-05-09 10:15:16 +02:00
} )
}
2026-04-29 11:19:05 +02:00
export async function changePassword ( newPassword ) {
return request ( '/api/auth/pin' , {
method : 'PUT' ,
body : JSON . stringify ( { pin : newPassword } ) ,
} )
}
2026-04-29 11:37:54 +02:00
/** GET /api/auth/verify/{token} — keine Auth nötig; Token gehört zur URL des Bestätigungslinks */
export async function verifyEmail ( token ) {
const t = encodeURIComponent ( token )
return request ( ` /api/auth/verify/ ${ t } ` , {
method : 'GET' ,
} )
}
export async function resendVerification ( email ) {
return request ( '/api/auth/resend-verification' , {
method : 'POST' ,
body : JSON . stringify ( { email } ) ,
} )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Clubs & Groups
// ============================================================================
export async function listClubs ( ) {
return request ( '/api/clubs' )
}
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
export async function getClub ( id ) {
return request ( ` /api/clubs/ ${ id } ` )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
export async function createClub ( data ) {
return request ( '/api/clubs' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
export async function updateClub ( id , data ) {
return request ( ` /api/clubs/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteClub ( id ) {
return request ( ` /api/clubs/ ${ id } ` , { method : 'DELETE' } )
}
2026-05-05 16:29:11 +02:00
/** Vereinsmitglieder (API für Admin ohne eigene UI) */
export async function listClubMembers ( clubId , { includeInactive = false } = { } ) {
const q = includeInactive ? '?include_inactive=true' : ''
return request ( ` /api/clubs/ ${ clubId } /members ${ q } ` )
}
export async function getClubMember ( clubId , profileId ) {
return request ( ` /api/clubs/ ${ clubId } /members/ ${ profileId } ` )
}
export async function addClubMember ( clubId , payload ) {
return request ( ` /api/clubs/ ${ clubId } /members ` , {
method : 'POST' ,
body : JSON . stringify ( payload ) ,
} )
}
export async function updateClubMember ( clubId , profileId , payload ) {
return request ( ` /api/clubs/ ${ clubId } /members/ ${ profileId } ` , {
method : 'PUT' ,
body : JSON . stringify ( payload ) ,
} )
}
export async function removeClubMember ( clubId , profileId ) {
return request ( ` /api/clubs/ ${ clubId } /members/ ${ profileId } ` , { method : 'DELETE' } )
}
2026-05-05 16:40:49 +02:00
/** Aktive Vereine (öffentlich, für Registrierungswahl). */
export async function listPublicClubsDirectory ( ) {
return request ( '/api/clubs/public-directory' )
}
/** Vereinsinternes Mitgliederverzeichnis (Trainer-/Co-Auswahl). */
export async function clubMembersDirectory ( clubId ) {
return request ( ` /api/clubs/ ${ clubId } /members/directory ` )
}
/** Eigene Beitrittsanträge. */
export async function getMyClubJoinRequests ( ) {
return request ( '/api/me/club-join-requests' )
}
export async function createClubJoinRequest ( payload ) {
return request ( '/api/me/club-join-requests' , {
method : 'POST' ,
body : JSON . stringify ( payload ) ,
} )
}
export async function withdrawClubJoinRequest ( requestId ) {
return request ( ` /api/me/club-join-requests/ ${ requestId } ` , { method : 'DELETE' } )
}
/** Offene Anträge (Vereins-/Plattform-Admin). */
export async function listClubJoinRequests ( clubId ) {
return request ( ` /api/clubs/ ${ clubId } /join-requests ` )
}
export async function acceptClubJoinRequest ( clubId , requestId , roles = [ 'trainer' ] ) {
return request ( ` /api/clubs/ ${ clubId } /join-requests/ ${ requestId } /accept ` , {
method : 'POST' ,
body : JSON . stringify ( { roles } ) ,
} )
}
export async function rejectClubJoinRequest ( clubId , requestId ) {
return request ( ` /api/clubs/ ${ clubId } /join-requests/ ${ requestId } /reject ` , {
method : 'POST' ,
body : JSON . stringify ( { } ) ,
} )
}
2026-05-09 09:13:38 +02:00
/** Aggregierter Posteingang: offene Beitrittsanträge für Vereins-/Plattform-Admins. */
export async function getInboxJoinRequests ( ) {
return request ( '/api/me/inbox/join-requests' )
}
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
export async function listDivisions ( clubId ) {
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
const query = clubId ? ` ?club_id= ${ clubId } ` : ''
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
return request ( ` /api/divisions ${ query } ` )
}
export async function createDivision ( data ) {
return request ( '/api/divisions' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateDivision ( id , data ) {
return request ( ` /api/divisions/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteDivision ( id ) {
return request ( ` /api/divisions/ ${ id } ` , { method : 'DELETE' } )
}
export async function listTrainingGroups ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/groups ${ query ? '?' + query : '' } ` )
}
export async function getTrainingGroup ( id ) {
return request ( ` /api/groups/ ${ id } ` )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
export async function createTrainingGroup ( data ) {
return request ( '/api/groups' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
export async function updateTrainingGroup ( id , data ) {
return request ( ` /api/groups/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainingGroup ( id ) {
return request ( ` /api/groups/ ${ id } ` , { method : 'DELETE' } )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Skills & Methods
// ============================================================================
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
export async function listSkills ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/skills ${ query ? '?' + query : '' } ` )
}
2026-04-27 11:53:25 +02:00
/** Admin: Fähigkeiten hierarchisch sortiert (Hauptkategorie → Kategorie → Sortierung) */
export async function listSkillsCatalog ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/skills/catalog ${ query ? '?' + query : '' } ` )
}
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
export async function getSkill ( id ) {
return request ( ` /api/skills/ ${ id } ` )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
export async function createSkill ( data ) {
return request ( '/api/skills' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
export async function updateSkill ( id , data ) {
return request ( ` /api/skills/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteSkill ( id ) {
return request ( ` /api/skills/ ${ id } ` , { method : 'DELETE' } )
}
export async function listMethods ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/methods ${ query ? '?' + query : '' } ` )
}
export async function getMethod ( id ) {
return request ( ` /api/methods/ ${ id } ` )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
export async function createMethod ( data ) {
return request ( '/api/methods' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
export async function updateMethod ( id , data ) {
return request ( ` /api/methods/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteMethod ( id ) {
return request ( ` /api/methods/ ${ id } ` , { method : 'DELETE' } )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Exercises
// ============================================================================
export async function listExercises ( filters = { } ) {
2026-04-27 14:27:25 +02:00
const q = new URLSearchParams ( )
Object . entries ( filters ) . forEach ( ( [ k , v ] ) => {
2026-04-28 08:02:08 +02:00
if ( v === undefined || v === null ) return
2026-04-28 09:30:33 +02:00
if ( typeof v === 'boolean' ) {
2026-05-06 13:52:24 +02:00
q . set ( k , v ? 'true' : 'false' )
2026-04-28 09:30:33 +02:00
return
}
2026-04-28 08:02:08 +02:00
if ( Array . isArray ( v ) ) {
if ( v . length === 0 ) return
v . forEach ( ( item ) => {
if ( item !== '' && item !== undefined && item !== null && String ( item ) . trim ( ) !== '' ) {
q . append ( k , String ( item ) )
}
} )
return
2026-04-27 14:27:25 +02:00
}
2026-04-28 08:02:08 +02:00
if ( String ( v ) . trim ( ) !== '' ) q . set ( k , String ( v ) )
2026-04-27 14:27:25 +02:00
} )
const query = q . toString ( )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
return request ( ` /api/exercises ${ query ? '?' + query : '' } ` )
}
2026-04-27 14:48:46 +02:00
/** Formular → API-Body (M:N gemäß EXERCISES_API_SPEC + training_types) */
2026-05-07 13:34:22 +02:00
export function buildExerciseApiPayload ( formData , extras = { } ) {
2026-04-27 14:27:25 +02:00
const num = ( v ) => ( v === '' || v == null ? null : Number ( v ) )
2026-04-27 14:48:46 +02:00
const goalHtml = formData . goal || ''
const execHtml = formData . execution || ''
const goalText = stripHtmlToText ( goalHtml )
const execText = stripHtmlToText ( execHtml )
if ( ! goalText && ! execText ) {
throw new Error ( 'Ziel oder Durchführung ausfüllen (mindestens eines, auch mit Formatierung).' )
2026-04-27 14:27:25 +02:00
}
2026-04-27 14:48:46 +02:00
const mapFocus = ( formData . focus _areas _multi || [ ] )
. filter ( ( x ) => x && x . focus _area _id )
. map ( ( x ) => ( { focus _area _id : Number ( x . focus _area _id ) , is _primary : ! ! x . is _primary } ) )
const mapStyles = ( formData . training _styles _multi || [ ] )
. filter ( ( x ) => x && x . training _style _id )
. map ( ( x ) => ( { training _style _id : Number ( x . training _style _id ) , is _primary : ! ! x . is _primary } ) )
const mapTTypes = ( formData . training _types _multi || [ ] )
. filter ( ( x ) => x && x . training _type _id )
. map ( ( x ) => ( { training _type _id : Number ( x . training _type _id ) , is _primary : ! ! x . is _primary } ) )
const mapTg = ( formData . target _groups _multi || [ ] )
. filter ( ( x ) => x && x . target _group _id )
. map ( ( x ) => ( { target _group _id : Number ( x . target _group _id ) , is _primary : ! ! x . is _primary } ) )
2026-05-13 16:34:38 +02:00
const visibilityNorm = String ( formData . visibility || 'private' ) . trim ( ) . toLowerCase ( )
2026-05-13 06:12:47 +02:00
const payload = {
2026-04-27 14:27:25 +02:00
title : ( formData . title || '' ) . trim ( ) ,
summary : formData . summary || null ,
2026-04-27 14:48:46 +02:00
goal : goalHtml . trim ( ) ? goalHtml : null ,
execution : execHtml . trim ( ) ? execHtml : null ,
2026-04-27 14:27:25 +02:00
preparation : formData . preparation || null ,
trainer _notes : formData . trainer _notes || null ,
duration _min : num ( formData . duration _min ) ,
duration _max : num ( formData . duration _max ) ,
group _size _min : num ( formData . group _size _min ) ,
group _size _max : num ( formData . group _size _max ) ,
equipment : Array . isArray ( formData . equipment ) ? formData . equipment : [ ] ,
2026-04-27 14:48:46 +02:00
focus _areas _multi : mapFocus ,
training _styles _multi : mapStyles ,
training _types _multi : mapTTypes ,
target _groups _multi : mapTg ,
2026-04-27 15:01:47 +02:00
age _groups : [ ] ,
2026-04-27 14:27:25 +02:00
skills : ( formData . skills || [ ] ) . map ( ( s ) => ( {
skill _id : s . skill _id ,
is _primary : ! ! s . is _primary ,
intensity : s . intensity || null ,
required _level : s . required _level || null ,
target _level : s . target _level || null ,
} ) ) ,
2026-05-13 16:34:38 +02:00
visibility : visibilityNorm ,
2026-04-27 14:27:25 +02:00
status : formData . status || 'draft' ,
2026-05-13 16:34:38 +02:00
club _id : visibilityNorm === 'club' ? num ( formData . club _id ) : null ,
2026-05-13 06:12:47 +02:00
exercise _kind :
String ( formData . exercise _kind || 'simple' ) . toLowerCase ( ) === 'combination'
? 'combination'
: 'simple' ,
2026-05-07 13:34:22 +02:00
... extras ,
2026-04-27 14:27:25 +02:00
}
2026-05-13 06:12:47 +02:00
const isCombo = payload . exercise _kind === 'combination'
if ( isCombo ) {
let mpObj = { }
const mpRaw = typeof formData . method _profile _json === 'string' ? formData . method _profile _json . trim ( ) : ''
if ( mpRaw ) {
try {
const parsed = JSON . parse ( mpRaw )
if ( ! parsed || typeof parsed !== 'object' || Array . isArray ( parsed ) ) {
throw new Error ( 'Ablaufprofil muss ein JSON-Objekt sein.' )
}
mpObj = parsed
} catch ( e ) {
if ( e instanceof SyntaxError ) {
throw new Error ( 'Ablaufprofil (JSON): Syntax ungültig.' )
}
throw e
}
}
const slotRows = Array . isArray ( formData . combination _slots ) ? formData . combination _slots : [ ]
const combination _slots = [ ]
2026-05-13 07:53:00 +02:00
function parseTimingField ( raw ) {
if ( raw === '' || raw == null || raw === undefined ) return undefined
const n = parseInt ( String ( raw ) , 10 )
return Number . isFinite ( n ) ? n : undefined
}
2026-05-13 06:12:47 +02:00
for ( let i = 0 ; i < slotRows . length ; i += 1 ) {
const row = slotRows [ i ] || { }
2026-05-13 07:53:00 +02:00
let ids = Array . isArray ( row . candidate _exercise _ids )
? row . candidate _exercise _ids . map ( ( x ) => Number ( x ) ) . filter ( ( n ) => Number . isFinite ( n ) )
: [ ]
/** Legacy: noch idsText Unterstützung für Import von älteren FormStand */
if ( ( ! ids || ids . length === 0 ) && typeof row . idsText === 'string' && row . idsText . trim ( ) ) {
ids = row . idsText
. split ( /[\s,;]+/ )
. map ( ( s ) => s . trim ( ) )
. filter ( Boolean )
. map ( ( s ) => parseInt ( s , 10 ) )
. filter ( ( n ) => Number . isFinite ( n ) )
2026-05-13 06:12:47 +02:00
}
2026-05-13 07:53:00 +02:00
2026-05-13 06:12:47 +02:00
combination _slots . push ( {
2026-05-13 07:53:00 +02:00
slot _index : i ,
2026-05-13 06:12:47 +02:00
title : ( typeof row . title === 'string' && row . title . trim ( ) ) || null ,
2026-05-13 07:53:00 +02:00
candidate _exercise _ids : ids ,
2026-05-13 06:12:47 +02:00
} )
}
2026-05-13 07:53:00 +02:00
const slot _profiles _v1 _next = [ ]
for ( let i = 0 ; i < slotRows . length ; i += 1 ) {
const row = slotRows [ i ] || { }
const o = { slot _index : i }
2026-05-13 08:19:46 +02:00
const advanceMode = normalizeAdvanceMode ( row . advance _mode )
if ( advanceMode !== 'timed' ) o . advance _mode = advanceMode
2026-05-13 07:53:00 +02:00
const load = parseTimingField ( row . load _sec )
const crs = parseTimingField ( row . consecutive _reps )
2026-05-13 08:58:41 +02:00
const rsc = parseTimingField ( row . rep _series _count )
2026-05-13 07:53:00 +02:00
const intra = parseTimingField ( row . intra _rep _rest _sec )
const tran = parseTimingField ( row . transition _after _sec )
2026-05-13 08:58:41 +02:00
const serienUi = parseComboRepSeriesCountUi ( row . rep _series _count )
const allowInterSeriesPause =
advanceMode === 'timed' ||
( ( advanceMode === 'rep' || advanceMode === 'manual' ) && serienUi >= 2 )
2026-05-13 08:19:46 +02:00
if ( advanceMode === 'timed' && load !== undefined && load >= 0 ) o . load _sec = Math . round ( load )
2026-05-13 07:53:00 +02:00
if ( crs !== undefined && crs >= 1 ) o . consecutive _reps = Math . round ( crs )
2026-05-13 08:58:41 +02:00
if (
rsc !== undefined &&
rsc >= 1 &&
( advanceMode === 'rep' || advanceMode === 'manual' )
) {
o . rep _series _count = Math . round ( rsc )
}
if ( intra !== undefined && intra >= 0 && allowInterSeriesPause ) o . intra _rep _rest _sec = Math . round ( intra )
2026-05-13 07:53:00 +02:00
if ( tran !== undefined && tran >= 0 ) o . transition _after _sec = Math . round ( tran )
if ( Object . keys ( o ) . length > 1 ) slot _profiles _v1 _next . push ( o )
}
2026-05-13 06:12:47 +02:00
payload . method _archetype = ( formData . method _archetype || '' ) . trim ( ) || null
2026-05-13 07:53:00 +02:00
if ( slot _profiles _v1 _next . length > 0 ) mpObj . slot _profiles _v1 = slot _profiles _v1 _next
else delete mpObj . slot _profiles _v1
2026-05-13 06:12:47 +02:00
payload . method _profile = mpObj
payload . combination _slots = combination _slots
} else {
payload . method _archetype = null
payload . method _profile = { }
}
return payload
2026-04-27 14:27:25 +02:00
}
export async function uploadExerciseMedia ( exerciseId , formData ) {
const token = localStorage . getItem ( 'authToken' )
2026-05-07 21:55:22 +02:00
const headers = mergeActiveClubHeader ( { } )
2026-04-27 14:27:25 +02:00
if ( token ) headers [ 'X-Auth-Token' ] = token
const response = await fetch ( ` ${ API _URL } /api/exercises/ ${ exerciseId } /media ` , {
method : 'POST' ,
headers ,
body : formData ,
} )
if ( ! response . ok ) {
2026-05-08 10:56:43 +02:00
const text = await response . text ( )
let parsed = null
try {
parsed = text ? JSON . parse ( text ) : null
} catch {
parsed = null
}
const d = parsed ? . detail
2026-05-07 13:25:42 +02:00
if (
response . status === 409 &&
d &&
typeof d === 'object' &&
! Array . isArray ( d ) &&
typeof d . code === 'string'
) {
const e = new Error (
typeof d . message === 'string' ? d . message : 'Upload konnte nicht verarbeitet werden' ,
)
e . code = d . code
e . status = 409
e . payload = d
throw e
}
2026-05-08 10:56:43 +02:00
if ( response . status === 413 ) {
const nginx = ( text || '' ) . toLowerCase ( ) . includes ( 'nginx' )
throw new Error (
nginx
? 'Die Anfrage ist zu groß (413). Häufig: nginx „client_max_body_size“ — z. B. große/r mehrere Videos oder Bulk-Upload. Dateien kleiner aufteilen oder Server-Limit erhöhen (Frontend-Image Neu bauen).'
: 'Die Anfrage ist zu groß (413). Dateigröße oder Server-Limit prüfen.' ,
)
}
2026-04-27 14:27:25 +02:00
const msg =
typeof d === 'string'
? d
2026-05-07 13:25:42 +02:00
: d != null && typeof d === 'object' && typeof d . message === 'string'
? d . message
: d != null
? JSON . stringify ( d )
2026-05-08 10:56:43 +02:00
: text && text . length < 400 && ! /^\s*</ . test ( text )
? ` HTTP ${ response . status } : ${ text . trim ( ) } `
: ` HTTP ${ response . status } `
2026-04-27 14:27:25 +02:00
throw new Error ( msg )
}
return response . json ( )
}
export async function updateExerciseMedia ( exerciseId , mediaId , data ) {
return request ( ` /api/exercises/ ${ exerciseId } /media/ ${ mediaId } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteExerciseMedia ( exerciseId , mediaId ) {
return request ( ` /api/exercises/ ${ exerciseId } /media/ ${ mediaId } ` , { method : 'DELETE' } )
}
export async function reorderExerciseMedia ( exerciseId , mediaIds ) {
return request ( ` /api/exercises/ ${ exerciseId } /media/reorder ` , {
method : 'PUT' ,
body : JSON . stringify ( { media _ids : mediaIds } ) ,
} )
}
2026-05-07 13:25:42 +02:00
/** Papierkorb / Recovery — `action`: trash_soft | trash_hidden | recover | purge | reactivate */
2026-05-07 14:33:02 +02:00
export async function postMediaAssetLifecycle ( assetId , action , extra = { } ) {
2026-05-07 12:55:50 +02:00
return request ( ` /api/media-assets/ ${ assetId } /lifecycle ` , {
method : 'POST' ,
2026-05-07 14:33:02 +02:00
body : JSON . stringify ( { action , ... extra } ) ,
2026-05-07 12:55:50 +02:00
} )
}
2026-05-07 13:00:57 +02:00
/** Archiv: aktive media_assets sichtbar für den Nutzer (Bibliotheksrechte). */
export async function listMediaAssets ( params = { } ) {
const sp = new URLSearchParams ( )
if ( params . q ) sp . set ( 'q' , params . q )
if ( params . limit != null ) sp . set ( 'limit' , String ( params . limit ) )
if ( params . offset != null ) sp . set ( 'offset' , String ( params . offset ) )
2026-05-07 14:10:26 +02:00
if ( params . lifecycle ) sp . set ( 'lifecycle' , String ( params . lifecycle ) )
2026-05-07 16:15:07 +02:00
if ( params . media _kind ) sp . set ( 'media_kind' , String ( params . media _kind ) )
if ( params . club _id != null && params . club _id !== '' ) sp . set ( 'club_id' , String ( params . club _id ) )
if ( params . uploaded _by != null && params . uploaded _by !== '' ) sp . set ( 'uploaded_by' , String ( params . uploaded _by ) )
if ( params . include _filter _meta ) sp . set ( 'include_filter_meta' , 'true' )
2026-05-07 13:00:57 +02:00
const qs = sp . toString ( )
return request ( ` /api/media-assets ${ qs ? ` ? ${ qs } ` : '' } ` )
}
2026-05-07 14:10:26 +02:00
export async function patchMediaAsset ( assetId , data ) {
return request ( ` /api/media-assets/ ${ assetId } ` , {
method : 'PATCH' ,
body : JSON . stringify ( data ) ,
} )
}
2026-05-07 14:33:02 +02:00
export async function bulkMediaLifecycle ( data ) {
return request ( '/api/media-assets/bulk-lifecycle' , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function bulkPatchMediaAssets ( data ) {
return request ( '/api/media-assets/bulk-patch' , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
2026-05-07 21:36:35 +02:00
/ * *
* Mehrere Dateien ins Medienarchiv ( ` POST /api/media-assets/bulk-upload ` ) .
* @ param { File [ ] } files
* @ param { { visibility ? : string , club _id ? : number } } [ options ]
* /
export async function bulkUploadMediaAssets ( files , options = { } ) {
const visibility = options . visibility || 'private'
const token = localStorage . getItem ( 'authToken' )
const headers = mergeActiveClubHeader ( { } )
if ( token ) headers [ 'X-Auth-Token' ] = token
const formData = new FormData ( )
formData . append ( 'visibility' , String ( visibility ) )
if ( options . club _id != null && options . club _id !== '' ) {
formData . append ( 'club_id' , String ( options . club _id ) )
}
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
// Copyright + P-06: Rechte-Erklaerung + Kontextfelder
if ( options . copyright _notice != null && String ( options . copyright _notice ) . trim ( ) )
formData . append ( 'copyright_notice' , String ( options . copyright _notice ) . trim ( ) )
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
const p06Fields = [
'rights_holder_confirmed' ,
'contains_identifiable_persons' ,
'person_consent_confirmed' ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
'person_consent_context' ,
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
'contains_minors' ,
'parental_consent_confirmed' ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
'parental_consent_context' ,
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
'contains_music' ,
'music_rights_confirmed' ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
'music_rights_context' ,
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
'contains_third_party_content' ,
'third_party_rights_confirmed' ,
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
'third_party_rights_context' ,
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
]
for ( const f of p06Fields ) {
feat(p06): Copyright-Feld und Einwilligungskontext in Rechte-Erklaerung
Migration 049: 4 optionale TEXT-Spalten in media_asset_rights_declarations
(person_consent_context, parental_consent_context, music_rights_context,
third_party_rights_context) fuer Freitext zum Einwilligungskontext.
Backend:
- media_rights.py: write_rights_declaration speichert 4 Kontextfelder
- media_assets.py: copyright_notice + 4 Kontextfelder in Bulk-Upload,
RightsDeclarationBody, MediaAssetPatch, MediaBulkPatchBody
- exercises.py: copyright_notice + 4 Kontextfelder in upload_exercise_media,
wird in INSERT gespeichert
Frontend (alle 3 Formulare):
- RightsDeclarationDialog: Copyright-Eingabefeld (immer sichtbar) +
Freitext-Textarea bei jeder Ja-Antwort (Personen, Minderjaehrige,
Musik, Fremdinhalte)
- ExerciseInlineFileMediaModal: gleiche Felder inline im Upload-Tab
- ExerciseInlineEmbedModal: gleiche Felder inline
- api.js: copyright_notice + 4 Kontextfelder in bulkUploadMediaAssets
version: 0.8.77
module: media_rights 1.1.0, media_assets 1.14.0, exercises 2.21.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 09:06:47 +02:00
if ( options [ f ] != null && options [ f ] !== '' ) formData . append ( f , String ( options [ f ] ) )
feat(compliance): P-06 Upload-Einwilligungsdialog v1-conservative
Implementiert server-seitige Rechteerklärungspflicht für alle Medien-Uploads
und Sichtbarkeits-Promotions (konservative Erstannahme: alle Uploads).
Backend:
- backend/media_rights.py (NEU): Kernmodul — validate_rights_declaration,
check_rights_coverage, assert_rights_for_promotion, assert_rights_for_exercise_link,
write_rights_declaration, update_rights_quick_fields
- backend/migrations/048_media_rights_declarations.sql (NEU): Tabelle
media_asset_rights_declarations (Append-only Audit-Log), Felder
rights_status/rights_visibility_level in media_assets
- backend/routers/media_assets.py: P-06-Pflichtprüfung in PATCH (single + bulk),
POST /api/media-assets/{id}/rights-declarations (Re-Deklaration),
GET /api/admin/media-rights/legacy-summary|legacy-assets (Admin-Endpoints)
- backend/routers/exercises.py: P-06-Felder in upload_exercise_media,
assert_rights_for_exercise_link in attach_exercise_media_from_asset
- backend/main.py: admin_rights_router registriert
Frontend:
- frontend/src/components/RightsDeclarationDialog.jsx (NEU): 9-Felder-Dialog
(konservativ: immer alle Fragen), Client-Validierung, VORLÄUFIG-Hinweis
- frontend/src/pages/MediaLibraryPage.jsx: Dialog-Intercept vor Upload,
Altbestand-Indikator (legacy_unreviewed)
- frontend/src/utils/api.js: P-06-Felder in bulkUploadMediaAssets weitergeleitet
Tests:
- backend/tests/test_media_rights_declaration.py (NEU): 28 Unit-/Integrationstests
- backend/tests/test_media_assets_archive.py: P-06 fetchone-Slots + Mock ergänzt
- backend/tests/test_media_assets_copyright_promotion.py: check_rights_coverage gemockt
- tests/dev-smoke-test.spec.js: 5 P-06 E2E-Tests ergänzt
Dokumentation:
- docs/compliance-implementation.md: P-06-Abschnitt
- docs/compliance-package-register.md: Status ⚠️ teilweise umgesetzt (KRIT-04 offen)
- docs/compliance-roadmap.md: P-06 im Freigaben-Log
Offen: KRIT-04 (rechtliche Finalisierung Einwilligungsformulierung) — technisch
vollständig, Rechtstext VORLÄUFIG.
version: 0.8.75
module: media_rights 1.0.0, media_assets 1.13.0, exercises 2.20.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 08:12:44 +02:00
}
2026-05-07 21:36:35 +02:00
const arr = Array . isArray ( files ) ? files : [ files ]
for ( const f of arr ) {
if ( f ) formData . append ( 'files' , f )
}
const url = ` ${ API _URL } /api/media-assets/bulk-upload `
const response = await fetch ( url , {
method : 'POST' ,
headers ,
body : formData ,
} )
if ( ! response . ok ) {
const text = await response . text ( )
let parsed = null
try {
parsed = JSON . parse ( text )
} catch {
parsed = null
}
if ( parsed ? . detail != null ) {
const d = parsed . detail
throw new Error ( typeof d === 'string' ? d : JSON . stringify ( d ) )
}
const snippet = ( text || '' ) . replace ( /\s+/g , ' ' ) . trim ( ) . slice ( 0 , 180 )
throw new Error ( snippet ? ` HTTP ${ response . status } : ${ snippet } ` : ` HTTP ${ response . status } ` )
}
return response . json ( )
}
2026-05-07 13:00:57 +02:00
/** Übung: bestehendes Archiv-Medium verknüpfen (`POST /api/exercises/{id}/media/from-asset`). */
2026-05-11 09:24:39 +02:00
export async function getMediaAssetJournal ( assetId ) {
return request ( ` /api/admin/media-rights/assets/ ${ assetId } /journal ` )
}
2026-05-11 10:26:50 +02:00
export async function addMediaAssetDeclarationCorrection ( assetId , body ) {
return request ( ` /api/admin/media-rights/assets/ ${ assetId } /correction ` , {
method : 'POST' ,
body : JSON . stringify ( body ) ,
} )
}
2026-05-07 13:00:57 +02:00
export async function attachExerciseMediaFromAsset ( exerciseId , body ) {
return request ( ` /api/exercises/ ${ exerciseId } /media/from-asset ` , {
method : 'POST' ,
body : JSON . stringify ( body ) ,
} )
}
2026-05-11 12:33:13 +02:00
// P-11: Legal-Hold-Endpunkte
export async function setMediaAssetLegalHold ( assetId , reasonCode , reasonNote ) {
return request ( ` /api/admin/media-assets/ ${ assetId } /legal-hold ` , {
method : 'POST' ,
body : JSON . stringify ( { reason _code : reasonCode , reason _note : reasonNote } ) ,
} )
}
export async function releaseMediaAssetLegalHold ( assetId , releaseNote ) {
return request ( ` /api/admin/media-assets/ ${ assetId } /legal-hold/release ` , {
method : 'POST' ,
body : JSON . stringify ( { release _note : releaseNote } ) ,
} )
}
export async function listMediaAssetsWithLegalHold ( limit = 100 , offset = 0 ) {
return request ( ` /api/admin/media-assets/legal-hold?limit= ${ limit } &offset= ${ offset } ` )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
export async function getExercise ( id ) {
return request ( ` /api/exercises/ ${ id } ` )
}
export async function createExercise ( data ) {
return request ( '/api/exercises' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateExercise ( id , data ) {
2026-05-07 13:34:22 +02:00
const token = localStorage . getItem ( 'authToken' )
const headers = mergeActiveClubHeader ( { 'Content-Type' : 'application/json' } )
if ( token ) headers [ 'X-Auth-Token' ] = token
const url = ` ${ API _URL } /api/exercises/ ${ id } `
const response = await fetch ( url , {
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
method : 'PUT' ,
2026-05-07 13:34:22 +02:00
headers ,
body : JSON . stringify ( data ) ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
} )
2026-05-07 13:34:22 +02:00
if ( ! response . ok ) {
const text = await response . text ( )
let parsed = null
try {
parsed = JSON . parse ( text )
} catch {
parsed = null
}
const d = parsed ? . detail
if (
response . status === 422 &&
d &&
typeof d === 'object' &&
! Array . isArray ( d ) &&
typeof d . code === 'string'
) {
const e = new Error ( typeof d . message === 'string' ? d . message : 'Validierung fehlgeschlagen' )
e . status = 422
e . code = d . code
e . payload = d
throw e
}
if ( parsed ? . detail != null ) {
const msg = typeof parsed . detail === 'string' ? parsed . detail : JSON . stringify ( parsed . detail )
throw new Error ( msg )
}
const snippet = ( text || '' ) . replace ( /\s+/g , ' ' ) . trim ( ) . slice ( 0 , 180 )
throw new Error ( snippet ? ` HTTP ${ response . status } : ${ snippet } ` : ` HTTP ${ response . status } ` )
}
return response . json ( )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
2026-05-06 11:02:46 +02:00
/** Massenänderung Übungen: Sichtbarkeit, Status, Katalog-Zuordnungen (`PATCH /api/exercises/bulk-metadata`). */
2026-05-05 22:18:04 +02:00
export async function bulkPatchExercisesMetadata ( data ) {
return request ( '/api/exercises/bulk-metadata' , {
method : 'PATCH' ,
body : JSON . stringify ( data ) ,
} )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
export async function deleteExercise ( id ) {
return request ( ` /api/exercises/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-28 10:59:09 +02:00
export async function createExerciseVariant ( exerciseId , data ) {
return request ( ` /api/exercises/ ${ exerciseId } /variants ` , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function updateExerciseVariant ( exerciseId , variantId , data ) {
return request ( ` /api/exercises/ ${ exerciseId } /variants/ ${ variantId } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteExerciseVariant ( exerciseId , variantId ) {
return request ( ` /api/exercises/ ${ exerciseId } /variants/ ${ variantId } ` , { method : 'DELETE' } )
}
export async function reorderExerciseVariants ( exerciseId , variantIds ) {
return request ( ` /api/exercises/ ${ exerciseId } /variants/reorder ` , {
method : 'PUT' ,
body : JSON . stringify ( { variant _ids : variantIds } ) ,
} )
}
2026-04-30 11:47:50 +02:00
// Progressionsgraphen (Übung → Übung), Migration 032/033
export async function listExerciseProgressionGraphs ( ) {
return request ( '/api/exercise-progression-graphs' )
}
export async function getExerciseProgressionGraph ( id , { includeEdges = false } = { } ) {
const q = includeEdges ? '?include_edges=true' : ''
return request ( ` /api/exercise-progression-graphs/ ${ id } ${ q } ` )
}
export async function createExerciseProgressionGraph ( data ) {
return request ( '/api/exercise-progression-graphs' , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function updateExerciseProgressionGraph ( id , data ) {
return request ( ` /api/exercise-progression-graphs/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteExerciseProgressionGraph ( id ) {
return request ( ` /api/exercise-progression-graphs/ ${ id } ` , { method : 'DELETE' } )
}
export async function listExerciseProgressionEdges ( graphId , query = { } ) {
const q = new URLSearchParams ( )
if ( query . from _exercise _id != null ) q . set ( 'from_exercise_id' , String ( query . from _exercise _id ) )
if ( query . to _exercise _id != null ) q . set ( 'to_exercise_id' , String ( query . to _exercise _id ) )
const qs = q . toString ( )
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges ${ qs ? ` ? ${ qs } ` : '' } ` )
}
export async function createExerciseProgressionEdge ( graphId , data ) {
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges ` , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function updateExerciseProgressionEdge ( graphId , edgeId , data ) {
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges/ ${ edgeId } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteExerciseProgressionEdge ( graphId , edgeId ) {
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges/ ${ edgeId } ` , { method : 'DELETE' } )
}
2026-05-03 18:07:52 +02:00
export async function createExerciseProgressionSequence ( graphId , data ) {
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges/sequence ` , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteExerciseProgressionEdgesBatch ( graphId , edgeIds ) {
return request ( ` /api/exercise-progression-graphs/ ${ graphId } /edges/delete-batch ` , {
method : 'POST' ,
body : JSON . stringify ( { edge _ids : edgeIds } ) ,
} )
}
2026-04-27 15:01:47 +02:00
/** KI-Ausbaustufe (EXERCISES_API_SPEC): benötigt Backend + z. B. OPENROUTER_API_KEY. */
export async function suggestExerciseAi ( payload ) {
return request ( '/api/exercises/ai/suggest' , {
method : 'POST' ,
body : JSON . stringify ( payload ) ,
} )
}
export async function regenerateExerciseAi ( exerciseId , payload ) {
return request ( ` /api/exercises/ ${ exerciseId } /ai/regenerate ` , {
method : 'POST' ,
body : JSON . stringify ( payload ) ,
} )
}
2026-04-23 07:52:03 +02:00
// ============================================================================
// Catalogs (Admin-verwaltbare Stammdaten)
// ============================================================================
// Focus Areas
export async function listFocusAreas ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/focus-areas ${ query ? '?' + query : '' } ` )
}
export async function createFocusArea ( data ) {
return request ( '/api/focus-areas' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateFocusArea ( id , data ) {
return request ( ` /api/focus-areas/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteFocusArea ( id ) {
return request ( ` /api/focus-areas/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-24 07:51:40 +02:00
// Admin Hierarchy (Tree View)
export async function getAdminHierarchy ( ) {
return request ( '/api/admin/hierarchy' )
}
2026-04-27 11:32:30 +02:00
// ============================================================================
// Reifegradmodelle / Fähigkeitsmatrix
// ============================================================================
export async function listMaturityModels ( filters = { } ) {
const query = new URLSearchParams (
Object . entries ( filters ) . filter ( ( [ , v ] ) => v !== undefined && v !== null && v !== '' )
) . toString ( )
return request ( ` /api/maturity-models ${ query ? '?' + query : '' } ` )
}
export async function resolveMaturityModel ( filters = { } ) {
const query = new URLSearchParams (
Object . entries ( filters ) . filter ( ( [ , v ] ) => v !== undefined && v !== null && v !== '' )
) . toString ( )
return request ( ` /api/maturity-models/resolve ${ query ? '?' + query : '' } ` )
}
export async function getMaturityModel ( id ) {
return request ( ` /api/maturity-models/ ${ id } ` )
}
export async function createMaturityModel ( data ) {
return request ( '/api/maturity-models' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateMaturityModel ( id , data ) {
return request ( ` /api/maturity-models/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteMaturityModel ( id ) {
return request ( ` /api/maturity-models/ ${ id } ` , { method : 'DELETE' } )
}
export async function addMaturityModelSkill ( modelId , data ) {
return request ( ` /api/maturity-models/ ${ modelId } /skills ` , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function removeMaturityModelSkill ( modelId , skillId ) {
return request ( ` /api/maturity-models/ ${ modelId } /skills/ ${ skillId } ` , { method : 'DELETE' } )
}
export async function upsertMaturityModelSkillLevels ( modelId , data ) {
return request ( ` /api/maturity-models/ ${ modelId } /skill-levels ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
2026-04-27 12:35:48 +02:00
/** Hierarchische Zuordnung Modell → Fokus / Stilrichtung / Trainingsstil (training_types) */
export async function listMaturityModelContextBindings ( ) {
return request ( '/api/maturity-model-context-bindings' )
}
export async function upsertMaturityModelContextBinding ( data ) {
return request ( '/api/maturity-model-context-bindings' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function deleteMaturityModelContextBinding ( id ) {
return request ( ` /api/maturity-model-context-bindings/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-27 12:48:22 +02:00
export async function importMaturityModelBundle ( payload ) {
return request ( '/api/maturity-models/import' , {
method : 'POST' ,
body : JSON . stringify ( payload )
} )
}
export async function exportMaturityModelBundle ( modelId ) {
return request ( ` /api/maturity-models/ ${ modelId } /export ` )
}
export async function exportResolvedMaturityBundle ( filters = { } ) {
const query = new URLSearchParams (
Object . entries ( filters ) . filter ( ( [ , v ] ) => v !== undefined && v !== null && v !== '' )
) . toString ( )
return request ( ` /api/maturity-models/export-resolved ${ query ? '?' + query : '' } ` )
}
2026-04-27 13:13:36 +02:00
/** Komplett: Fähigkeitskatalog + Reifegradmodelle + Kontext-Bindings (slug-/namensbasiert auf Ziel-DB) */
export async function exportMatrixStackBundle ( ) {
return request ( '/api/admin/matrix-stack/export' )
}
export async function importMatrixStackBundle ( payload ) {
return request ( '/api/admin/matrix-stack/import' , {
method : 'POST' ,
body : JSON . stringify ( payload )
} )
}
2026-04-23 12:27:09 +02:00
// Style Directions (formerly Training Styles)
export async function listStyleDirections ( filters = { } ) {
2026-04-23 07:52:03 +02:00
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/training-styles ${ query ? '?' + query : '' } ` )
}
2026-04-24 15:20:39 +02:00
// Backward-compatibility alias
export const listTrainingStyles = listStyleDirections
2026-04-23 12:27:09 +02:00
export async function createStyleDirection ( data ) {
2026-04-23 07:52:03 +02:00
return request ( '/api/training-styles' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
2026-04-23 12:27:09 +02:00
export async function updateStyleDirection ( id , data ) {
2026-04-23 07:52:03 +02:00
return request ( ` /api/training-styles/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
2026-04-23 12:27:09 +02:00
export async function deleteStyleDirection ( id ) {
2026-04-23 07:52:03 +02:00
return request ( ` /api/training-styles/ ${ id } ` , { method : 'DELETE' } )
}
// Training Characters
export async function listTrainingCharacters ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/training-characters ${ query ? '?' + query : '' } ` )
}
export async function createTrainingCharacter ( data ) {
return request ( '/api/training-characters' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTrainingCharacter ( id , data ) {
return request ( ` /api/training-characters/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainingCharacter ( id ) {
return request ( ` /api/training-characters/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-23 12:12:48 +02:00
// Training Types (Breitensport, Leistungssport, etc.)
export async function listTrainingTypes ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/training-types ${ query ? '?' + query : '' } ` )
}
export async function createTrainingType ( data ) {
return request ( '/api/training-types' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTrainingType ( id , data ) {
return request ( ` /api/training-types/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainingType ( id ) {
return request ( ` /api/training-types/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-27 11:53:25 +02:00
// Skill main categories (Hauptgruppen im Fähigkeitskatalog)
export async function listSkillMainCategories ( ) {
return request ( '/api/skill-main-categories' )
}
export async function createSkillMainCategory ( data ) {
return request ( '/api/skill-main-categories' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateSkillMainCategory ( id , data ) {
return request ( ` /api/skill-main-categories/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteSkillMainCategory ( id ) {
return request ( ` /api/skill-main-categories/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-23 07:52:03 +02:00
// Skill Categories
export async function listSkillCategories ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/skill-categories ${ query ? '?' + query : '' } ` )
}
export async function createSkillCategory ( data ) {
return request ( '/api/skill-categories' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateSkillCategory ( id , data ) {
return request ( ` /api/skill-categories/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteSkillCategory ( id ) {
return request ( ` /api/skill-categories/ ${ id } ` , { method : 'DELETE' } )
}
// Trainer Focus Areas
export async function listTrainerFocusAreas ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/trainer-focus-areas ${ query ? '?' + query : '' } ` )
}
export async function assignTrainerFocusArea ( data ) {
return request ( '/api/trainer-focus-areas' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainerFocusArea ( id ) {
return request ( ` /api/trainer-focus-areas/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-23 08:55:54 +02:00
// Target Groups (Zielgruppen)
export async function listTargetGroups ( filters = { } ) {
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/target-groups ${ query ? '?' + query : '' } ` )
}
export async function createTargetGroup ( data ) {
return request ( '/api/target-groups' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTargetGroup ( id , data ) {
return request ( ` /api/target-groups/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTargetGroup ( id ) {
return request ( ` /api/target-groups/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-23 12:27:09 +02:00
// Style Direction → Target Groups (M:N Assignments)
export async function listStyleDirectionTargetGroups ( filters = { } ) {
2026-04-23 10:45:14 +02:00
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/training-style-target-groups ${ query ? '?' + query : '' } ` )
}
2026-04-23 12:27:09 +02:00
export async function createStyleDirectionTargetGroup ( data ) {
2026-04-23 10:45:14 +02:00
return request ( '/api/training-style-target-groups' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
2026-04-23 12:27:09 +02:00
export async function updateStyleDirectionTargetGroup ( id , data ) {
2026-04-23 10:45:14 +02:00
return request ( ` /api/training-style-target-groups/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
2026-04-23 12:27:09 +02:00
export async function deleteStyleDirectionTargetGroup ( id ) {
2026-04-23 10:45:14 +02:00
return request ( ` /api/training-style-target-groups/ ${ id } ` , { method : 'DELETE' } )
}
2026-04-23 12:27:09 +02:00
export async function getStyleDirectionsHierarchy ( filters = { } ) {
2026-04-23 10:45:14 +02:00
const query = new URLSearchParams ( filters ) . toString ( )
return request ( ` /api/training-styles/hierarchy ${ query ? '?' + query : '' } ` )
}
2026-04-23 14:22:17 +02:00
// Trainer Contexts (Fokussierte Ansichten)
export async function listTrainerContexts ( ) {
return request ( '/api/trainer-contexts' )
}
export async function createTrainerContext ( data ) {
return request ( '/api/trainer-contexts' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTrainerContext ( id , data ) {
return request ( ` /api/trainer-contexts/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainerContext ( id ) {
return request ( ` /api/trainer-contexts/ ${ id } ` , { method : 'DELETE' } )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Training Planning
// ============================================================================
2026-05-05 15:40:29 +02:00
/** Query-Parameter wie GET /api/training-units. */
2026-04-28 16:18:25 +02:00
export async function listTrainingUnits ( filters = { } ) {
const q = new URLSearchParams ( )
if ( filters . group _id != null && filters . group _id !== '' ) {
q . set ( 'group_id' , String ( filters . group _id ) )
}
2026-05-05 15:40:29 +02:00
if ( filters . club _id != null && filters . club _id !== '' ) {
q . set ( 'club_id' , String ( filters . club _id ) )
}
2026-04-28 16:18:25 +02:00
if ( filters . start _date ) q . set ( 'start_date' , filters . start _date )
if ( filters . end _date ) q . set ( 'end_date' , filters . end _date )
if ( filters . status ) q . set ( 'status' , filters . status )
2026-05-07 09:01:01 +02:00
if ( filters . debrief _pending === true ) q . set ( 'debrief_pending' , 'true' )
2026-05-05 15:40:29 +02:00
if ( filters . assigned _to _me === true ) q . set ( 'assigned_to_me' , 'true' )
if ( filters . sort ) q . set ( 'sort' , String ( filters . sort ) )
if ( filters . limit != null && filters . limit !== '' ) q . set ( 'limit' , String ( filters . limit ) )
2026-05-14 08:44:59 +02:00
if ( filters . cursor _planned _date ) q . set ( 'cursor_planned_date' , String ( filters . cursor _planned _date ) )
if ( filters . cursor _planned _time != null && filters . cursor _planned _time !== '' ) {
q . set ( 'cursor_planned_time' , String ( filters . cursor _planned _time ) )
}
if ( filters . cursor _id != null && filters . cursor _id !== '' ) q . set ( 'cursor_id' , String ( filters . cursor _id ) )
2026-04-28 16:18:25 +02:00
const qs = q . toString ( )
return request ( ` /api/training-units ${ qs ? ` ? ${ qs } ` : '' } ` )
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
2026-05-14 07:47:27 +02:00
/** Dashboard Kurzüberblick: Entwürfe / meine Übungen / YTD abgeschlossene Einheiten (ein Roundtrip). */
export async function getDashboardKpis ( ) {
return request ( '/api/dashboard/kpis' )
}
2026-05-07 10:19:55 +02:00
/** Dashboard: Übungen in geplanten Einheiten, die für den Verein noch auf Sichtbarkeit „Verein“ gehören. */
export async function getTrainingExerciseClubVisibilityQueue ( filters = { } ) {
const q = new URLSearchParams ( )
if ( filters . start _date ) q . set ( 'start_date' , String ( filters . start _date ) )
if ( filters . end _date ) q . set ( 'end_date' , String ( filters . end _date ) )
if ( filters . assigned _to _me === false ) q . set ( 'assigned_to_me' , 'false' )
if ( filters . limit _units != null && filters . limit _units !== '' ) {
q . set ( 'limit_units' , String ( filters . limit _units ) )
}
const qs = q . toString ( )
return request ( ` /api/training-units/exercises-club-visibility-queue ${ qs ? ` ? ${ qs } ` : '' } ` )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
export async function getTrainingUnit ( id ) {
return request ( ` /api/training-units/ ${ id } ` )
}
export async function createTrainingUnit ( data ) {
return request ( '/api/training-units' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTrainingUnit ( id , data ) {
return request ( ` /api/training-units/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
feat: Training Planning (core feature) complete
Backend:
- Created migration 006_training_planning.sql
- training_units table (planned vs actual, status, notes)
- training_unit_exercises M:N (order, duration, modifications)
- Created routers/training_planning.py with full CRUD
- List with filters (group, date range, status)
- Get detail with exercises
- Create/Update/Delete with access control
- Quick create endpoint (auto-fills from group defaults)
- Permission: trainer for own groups, admin for all
- Registered training_planning router in main.py
Frontend:
- Complete TrainingPlanningPage with full planning workflow
- Group selector + date range picker
- List view with status badges (planned/completed/cancelled)
- Create/Edit modal with exercise management
- Drag to reorder exercises (▲▼)
- Quick create button (one-click with group defaults)
- Durchführung section (actual date/time, attendance, status)
- Public notes + trainer-only notes
- Updated api.js with deleteTrainingUnit and quickCreateTrainingUnit
- Added /planning route to App.jsx
- Navigation already configured (Calendar icon)
This is the CORE feature - trainers can now plan and document training sessions.
Next: Admin routes, role system refinement, testing
2026-04-22 16:54:34 +02:00
export async function deleteTrainingUnit ( id ) {
return request ( ` /api/training-units/ ${ id } ` , { method : 'DELETE' } )
}
export async function quickCreateTrainingUnit ( data ) {
return request ( '/api/training-units/quick-create' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
2026-05-05 13:31:26 +02:00
/** Rahmen-Slot → geplante Einheit (tiefe Kopie, origin_framework_slot_id). */
export async function createTrainingUnitFromFrameworkSlot ( data ) {
return request ( '/api/training-units/from-framework-slot' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
2026-04-28 16:31:45 +02:00
export async function listTrainingPlanTemplates ( ) {
return request ( '/api/training-plan-templates' )
}
export async function getTrainingPlanTemplate ( id ) {
return request ( ` /api/training-plan-templates/ ${ id } ` )
}
export async function createTrainingPlanTemplate ( data ) {
return request ( '/api/training-plan-templates' , {
method : 'POST' ,
body : JSON . stringify ( data )
} )
}
export async function updateTrainingPlanTemplate ( id , data ) {
return request ( ` /api/training-plan-templates/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data )
} )
}
export async function deleteTrainingPlanTemplate ( id ) {
return request ( ` /api/training-plan-templates/ ${ id } ` , { method : 'DELETE' } )
}
2026-05-12 21:35:07 +02:00
export async function listTrainingModules ( ) {
return request ( '/api/training-modules' )
}
export async function getTrainingModule ( id ) {
return request ( ` /api/training-modules/ ${ id } ` )
}
export async function createTrainingModule ( data ) {
return request ( '/api/training-modules' , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function updateTrainingModule ( id , data ) {
return request ( ` /api/training-modules/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteTrainingModule ( id ) {
return request ( ` /api/training-modules/ ${ id } ` , { method : 'DELETE' } )
}
/** Kopiert Modul-Inhalte ans Ende eines Abschnitts (section_order_index 0-basiert). */
export async function applyTrainingModuleToTrainingUnit ( unitId , data ) {
return request ( ` /api/training-units/ ${ unitId } /apply-training-module ` , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
2026-05-05 09:02:13 +02:00
export async function listTrainingFrameworkPrograms ( ) {
return request ( '/api/training-framework-programs' )
}
export async function getTrainingFrameworkProgram ( id ) {
return request ( ` /api/training-framework-programs/ ${ id } ` )
}
export async function createTrainingFrameworkProgram ( data ) {
return request ( '/api/training-framework-programs' , {
method : 'POST' ,
body : JSON . stringify ( data ) ,
} )
}
export async function updateTrainingFrameworkProgram ( id , data ) {
return request ( ` /api/training-framework-programs/ ${ id } ` , {
method : 'PUT' ,
body : JSON . stringify ( data ) ,
} )
}
export async function deleteTrainingFrameworkProgram ( id ) {
return request ( ` /api/training-framework-programs/ ${ id } ` , { method : 'DELETE' } )
}
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// ============================================================================
// Version & Health
// ============================================================================
export async function getVersion ( ) {
return request ( '/api/version' )
}
export async function healthCheck ( ) {
return request ( '/health' )
}
export const api = {
// Auth
login ,
register ,
logout ,
getCurrentProfile ,
2026-05-05 16:12:17 +02:00
listProfiles ,
2026-05-05 21:05:52 +02:00
listAdminUsers ,
2026-04-29 11:19:05 +02:00
updateProfile ,
2026-05-09 10:15:16 +02:00
managementPasswordReset ,
2026-04-29 11:19:05 +02:00
changePassword ,
2026-04-29 11:37:54 +02:00
verifyEmail ,
resendVerification ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// Clubs & Groups
listClubs ,
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
getClub ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
createClub ,
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
updateClub ,
deleteClub ,
2026-05-05 16:29:11 +02:00
listClubMembers ,
getClubMember ,
addClubMember ,
updateClubMember ,
removeClubMember ,
2026-05-05 16:40:49 +02:00
listPublicClubsDirectory ,
clubMembersDirectory ,
getMyClubJoinRequests ,
createClubJoinRequest ,
withdrawClubJoinRequest ,
listClubJoinRequests ,
acceptClubJoinRequest ,
rejectClubJoinRequest ,
2026-05-09 09:13:38 +02:00
getInboxJoinRequests ,
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
listDivisions ,
createDivision ,
updateDivision ,
deleteDivision ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
listTrainingGroups ,
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
getTrainingGroup ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
createTrainingGroup ,
feat: Clubs & Organization Management complete
Backend:
- Created routers/clubs.py with full CRUD
- Clubs: list, get, create, update, delete (admin only)
- Divisions: list, create, update, delete (admin only)
- Training Groups: list, get, create, update, delete (admin/trainer)
- Registered clubs router in main.py
- Permission checks: admin for clubs/divisions, trainer for groups
Frontend:
- Complete ClubsPage with 3 tabs (Vereine, Sparten, Gruppen)
- Role-based UI (admin sees all actions, trainer can manage groups)
- Full CRUD forms with modals
- Mobile-responsive card layouts
- Updated api.js with all club/division/group functions
Migration already exists: 002_organization.sql (clubs, divisions, training_groups)
Next: Skills & Methods display (read-only)
2026-04-22 16:48:02 +02:00
updateTrainingGroup ,
deleteTrainingGroup ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// Skills & Methods
listSkills ,
2026-04-27 11:53:25 +02:00
listSkillsCatalog ,
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
getSkill ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
createSkill ,
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
updateSkill ,
deleteSkill ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
listMethods ,
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
getMethod ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
createMethod ,
feat: Skills & Methods catalog complete
Backend:
- Created routers/skills.py with full CRUD
- Skills: list (with category filter), get, create, update, delete
- Methods: list (with category filter), get, create, update, delete
- Default: only show active items
- Read access: all authenticated users
- Write access: admin only
- Registered skills router in main.py
Frontend:
- Complete SkillsPage with 2 tabs (Fähigkeiten, Trainingsmethoden)
- Browse by category with cards layout
- Admin CRUD forms (importance rating for skills, duration/group size for methods)
- Mobile-responsive grid layout
- Updated api.js with all skill/method functions
- Added /skills route to App.jsx
Migration already exists: 003_catalogs.sql (skills, training_methods + seed data)
Next: Training Planning (core feature)
2026-04-22 16:50:31 +02:00
updateMethod ,
deleteMethod ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// Exercises
listExercises ,
getExercise ,
createExercise ,
updateExercise ,
2026-05-05 22:18:04 +02:00
bulkPatchExercisesMetadata ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
deleteExercise ,
2026-04-28 10:59:09 +02:00
createExerciseVariant ,
updateExerciseVariant ,
deleteExerciseVariant ,
reorderExerciseVariants ,
2026-04-27 14:27:25 +02:00
buildExerciseApiPayload ,
2026-04-27 15:01:47 +02:00
suggestExerciseAi ,
regenerateExerciseAi ,
2026-04-27 14:27:25 +02:00
uploadExerciseMedia ,
updateExerciseMedia ,
deleteExerciseMedia ,
reorderExerciseMedia ,
2026-05-07 12:55:50 +02:00
postMediaAssetLifecycle ,
2026-05-07 13:00:57 +02:00
listMediaAssets ,
2026-05-07 14:10:26 +02:00
patchMediaAsset ,
2026-05-07 14:33:02 +02:00
bulkMediaLifecycle ,
bulkPatchMediaAssets ,
2026-05-07 21:36:35 +02:00
bulkUploadMediaAssets ,
2026-05-11 09:24:39 +02:00
getMediaAssetJournal ,
2026-05-11 10:26:50 +02:00
addMediaAssetDeclarationCorrection ,
2026-05-07 13:00:57 +02:00
attachExerciseMediaFromAsset ,
2026-05-11 12:33:13 +02:00
setMediaAssetLegalHold ,
releaseMediaAssetLegalHold ,
listMediaAssetsWithLegalHold ,
2026-04-30 11:47:50 +02:00
listExerciseProgressionGraphs ,
getExerciseProgressionGraph ,
createExerciseProgressionGraph ,
updateExerciseProgressionGraph ,
deleteExerciseProgressionGraph ,
listExerciseProgressionEdges ,
createExerciseProgressionEdge ,
updateExerciseProgressionEdge ,
deleteExerciseProgressionEdge ,
2026-05-03 18:07:52 +02:00
createExerciseProgressionSequence ,
deleteExerciseProgressionEdgesBatch ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// Training Planning
listTrainingUnits ,
2026-05-14 07:47:27 +02:00
getDashboardKpis ,
2026-05-07 10:19:55 +02:00
getTrainingExerciseClubVisibilityQueue ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
getTrainingUnit ,
createTrainingUnit ,
updateTrainingUnit ,
feat: Training Planning (core feature) complete
Backend:
- Created migration 006_training_planning.sql
- training_units table (planned vs actual, status, notes)
- training_unit_exercises M:N (order, duration, modifications)
- Created routers/training_planning.py with full CRUD
- List with filters (group, date range, status)
- Get detail with exercises
- Create/Update/Delete with access control
- Quick create endpoint (auto-fills from group defaults)
- Permission: trainer for own groups, admin for all
- Registered training_planning router in main.py
Frontend:
- Complete TrainingPlanningPage with full planning workflow
- Group selector + date range picker
- List view with status badges (planned/completed/cancelled)
- Create/Edit modal with exercise management
- Drag to reorder exercises (▲▼)
- Quick create button (one-click with group defaults)
- Durchführung section (actual date/time, attendance, status)
- Public notes + trainer-only notes
- Updated api.js with deleteTrainingUnit and quickCreateTrainingUnit
- Added /planning route to App.jsx
- Navigation already configured (Calendar icon)
This is the CORE feature - trainers can now plan and document training sessions.
Next: Admin routes, role system refinement, testing
2026-04-22 16:54:34 +02:00
deleteTrainingUnit ,
quickCreateTrainingUnit ,
2026-05-05 15:06:34 +02:00
createTrainingUnitFromFrameworkSlot ,
2026-04-28 16:31:45 +02:00
listTrainingPlanTemplates ,
getTrainingPlanTemplate ,
createTrainingPlanTemplate ,
updateTrainingPlanTemplate ,
deleteTrainingPlanTemplate ,
2026-05-12 21:35:07 +02:00
listTrainingModules ,
getTrainingModule ,
createTrainingModule ,
updateTrainingModule ,
deleteTrainingModule ,
applyTrainingModuleToTrainingUnit ,
2026-05-05 09:02:13 +02:00
listTrainingFrameworkPrograms ,
getTrainingFrameworkProgram ,
createTrainingFrameworkProgram ,
updateTrainingFrameworkProgram ,
deleteTrainingFrameworkProgram ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
2026-04-23 07:52:03 +02:00
// Catalogs
listFocusAreas ,
createFocusArea ,
updateFocusArea ,
deleteFocusArea ,
2026-04-24 08:25:03 +02:00
getAdminHierarchy ,
2026-04-23 12:27:09 +02:00
listStyleDirections ,
2026-04-27 15:03:48 +02:00
listTrainingStyles ,
2026-04-23 12:27:09 +02:00
createStyleDirection ,
updateStyleDirection ,
deleteStyleDirection ,
2026-04-23 07:52:03 +02:00
listTrainingCharacters ,
createTrainingCharacter ,
updateTrainingCharacter ,
deleteTrainingCharacter ,
2026-04-23 12:12:48 +02:00
listTrainingTypes ,
createTrainingType ,
updateTrainingType ,
deleteTrainingType ,
2026-04-27 11:53:25 +02:00
listSkillMainCategories ,
createSkillMainCategory ,
updateSkillMainCategory ,
deleteSkillMainCategory ,
2026-04-23 07:52:03 +02:00
listSkillCategories ,
createSkillCategory ,
updateSkillCategory ,
deleteSkillCategory ,
listTrainerFocusAreas ,
assignTrainerFocusArea ,
deleteTrainerFocusArea ,
2026-04-23 08:55:54 +02:00
listTargetGroups ,
createTargetGroup ,
updateTargetGroup ,
deleteTargetGroup ,
2026-04-23 12:27:09 +02:00
listStyleDirectionTargetGroups ,
createStyleDirectionTargetGroup ,
updateStyleDirectionTargetGroup ,
deleteStyleDirectionTargetGroup ,
2026-04-27 11:32:30 +02:00
listMaturityModels ,
2026-04-27 12:35:48 +02:00
listMaturityModelContextBindings ,
upsertMaturityModelContextBinding ,
deleteMaturityModelContextBinding ,
2026-04-27 12:48:22 +02:00
importMaturityModelBundle ,
exportMaturityModelBundle ,
exportResolvedMaturityBundle ,
2026-04-27 13:13:36 +02:00
exportMatrixStackBundle ,
importMatrixStackBundle ,
2026-04-27 11:32:30 +02:00
resolveMaturityModel ,
getMaturityModel ,
createMaturityModel ,
updateMaturityModel ,
deleteMaturityModel ,
addMaturityModelSkill ,
removeMaturityModelSkill ,
upsertMaturityModelSkillLevels ,
2026-04-23 12:27:09 +02:00
getStyleDirectionsHierarchy ,
2026-04-23 14:22:17 +02:00
listTrainerContexts ,
createTrainerContext ,
updateTrainerContext ,
deleteTrainerContext ,
2026-04-23 07:52:03 +02:00
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
// System
getVersion ,
2026-04-24 16:06:49 +02:00
healthCheck ,
// MediaWiki Import
previewMediaWikiImport : ( category , importType = 'exercise' , limit = 10 ) =>
request ( ` /api/import/mediawiki/preview?category= ${ encodeURIComponent ( category ) } &import_type= ${ importType } &limit= ${ limit } ` ) ,
executeMediaWikiImport : ( data ) =>
request ( '/api/import/mediawiki/execute' , {
method : 'POST' ,
body : JSON . stringify ( data )
} ) ,
getMediaWikiImportStatus : ( logId ) =>
request ( ` /api/import/mediawiki/status/ ${ logId } ` ) ,
listMediaWikiImportLogs : ( ) =>
request ( '/api/import/mediawiki/logs' ) ,
deleteMediaWikiImportReference : ( refId ) =>
2026-05-10 11:53:07 +02:00
request ( ` /api/import/mediawiki/references/ ${ refId } ` , { method : 'DELETE' } ) ,
// Legal Documents (public)
getPublishedLegalDocument : ( documentType ) =>
request ( ` /api/legal-documents/ ${ documentType } /published ` ) ,
// Legal Documents (superadmin)
listLegalDocuments : ( documentType ) =>
request ( ` /api/admin/legal-documents ${ documentType ? ` ?document_type= ${ documentType } ` : '' } ` ) ,
createLegalDocument : ( data ) =>
request ( '/api/admin/legal-documents' , { method : 'POST' , body : JSON . stringify ( data ) } ) ,
getLegalDocument : ( id ) =>
request ( ` /api/admin/legal-documents/ ${ id } ` ) ,
updateLegalDocument : ( id , data ) =>
request ( ` /api/admin/legal-documents/ ${ id } ` , { method : 'PUT' , body : JSON . stringify ( data ) } ) ,
publishLegalDocument : ( id , changeNote ) =>
request ( ` /api/admin/legal-documents/ ${ id } /publish ` , {
method : 'POST' ,
body : JSON . stringify ( { change _note : changeNote } ) ,
} ) ,
archiveLegalDocument : ( id ) =>
request ( ` /api/admin/legal-documents/ ${ id } /archive ` , { method : 'POST' } ) ,
2026-05-10 12:25:04 +02:00
copyLegalDocumentAsDraft : ( id ) =>
request ( ` /api/admin/legal-documents/ ${ id } /copy-as-draft ` , { method : 'POST' } ) ,
2026-05-10 11:53:07 +02:00
getLegalDocumentAudit : ( id ) =>
request ( ` /api/admin/legal-documents/ ${ id } /audit ` ) ,
2026-05-11 17:54:53 +02:00
// P-13: Content-Melde-Backend
submitContentReport : ( body ) =>
request ( '/api/content-reports' , { method : 'POST' , body : JSON . stringify ( body ) } ) ,
getInboxContentReports : ( ) =>
request ( '/api/me/inbox/content-reports' ) ,
getContentReport : ( id ) =>
request ( ` /api/content-reports/ ${ id } ` ) ,
patchContentReport : ( id , body ) =>
request ( ` /api/content-reports/ ${ id } ` , { method : 'PATCH' , body : JSON . stringify ( body ) } ) ,
setLegalHoldFromReport : ( id , body ) =>
request ( ` /api/content-reports/ ${ id } /legal-hold ` , { method : 'POST' , body : JSON . stringify ( body ) } ) ,
feat: Complete MVP setup - Docker, Frontend, Migrations, CI/CD
Docker & Deployment:
- docker-compose.yml (Prod: Port 3003/8003)
- docker-compose.dev-env.yml (Dev: Port 3098/8098)
- Backend Dockerfile (Python 3.12-slim)
- Frontend Dockerfile (Node 20 + Nginx)
- Gitea Actions (deploy-dev.yml, deploy-prod.yml)
Frontend:
- React 18 + Vite setup
- package.json, vite.config.js, index.html
- App.jsx (minimal with version display)
- api.js (complete API client)
- app.css + AuthContext from Mitai
- main.jsx entry point
Backend Migrations:
- 001_auth_membership.sql (Auth + Features + Tier Limits)
- 002_organization.sql (Clubs, Divisions, Training Groups)
- 003_catalogs.sql (Skills + Methods with sample data)
Documentation:
- .claude/rules/ (ARCHITECTURE, CODING_RULES, etc.)
- SHINKAN_PROJECT_SETUP.md (technical setup guide)
Server:
- Directories created on Pi: /home/lars/docker/shinkan[-dev]
- Gitea Runner configured and running
Ready for first deployment to dev.shinkan.jinkendo.de
version: 0.1.0
date: 2026-04-21
2026-04-21 14:36:52 +02:00
}
export default api