From 005b7547e6f75af2fead0373f20068c5ec799a57 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Wed, 24 Dec 2025 10:52:54 +0000 Subject: [PATCH 001/173] docs: initialize documentation with nuxt content - detailed design --- .devcontainer/setup_commands.sh | 2 +- docs/.gitignore | 26 ++++++++++ docs/README.md | 13 +++++ docs/app/app.vue | 8 +++ docs/app/assets/css/main.css | 10 ++++ docs/app/layouts/default.vue | 59 +++++++++++++++++++++++ docs/app/pages/[...slug].vue | 40 +++++++++++++++ docs/app/pages/index.vue | 3 ++ docs/content.config.ts | 10 ++++ docs/content/detailed/01.introduction.md | 20 ++++++++ docs/nuxt.config.ts | 20 ++++++++ docs/package.json | 20 ++++++++ docs/public/favicon.ico | Bin 0 -> 4286 bytes docs/tsconfig.json | 18 +++++++ eslint.config.mjs | 1 + package.json | 4 +- pnpm-lock.yaml | 26 +++++++--- pnpm-workspace.yaml | 4 ++ 18 files changed, 276 insertions(+), 8 deletions(-) create mode 100644 docs/.gitignore create mode 100644 docs/README.md create mode 100644 docs/app/app.vue create mode 100644 docs/app/assets/css/main.css create mode 100644 docs/app/layouts/default.vue create mode 100644 docs/app/pages/[...slug].vue create mode 100644 docs/app/pages/index.vue create mode 100644 docs/content.config.ts create mode 100644 docs/content/detailed/01.introduction.md create mode 100644 docs/nuxt.config.ts create mode 100644 docs/package.json create mode 100644 docs/public/favicon.ico create mode 100644 docs/tsconfig.json diff --git a/.devcontainer/setup_commands.sh b/.devcontainer/setup_commands.sh index accec38e..31382f03 100644 --- a/.devcontainer/setup_commands.sh +++ b/.devcontainer/setup_commands.sh @@ -19,7 +19,7 @@ install_deps() { npm install -g npm && npm install -g pnpm pnpm config set global-bin-dir "$HOME/.local/bin" pnpm config set store-dir "$HOME/.pnpm-store" - pnpm install + pnpm install -r } container_watch() { diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..da0a37b5 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,26 @@ +# Nuxt dev/build outputs +.output +.data +.nuxt +.nitro +.cache +dist + +# Node dependencies +node_modules + +# Logs +logs +*.log + +# Misc +.DS_Store +.fleet +.idea + +# Local env files +.env +.env.* +!.env.example + +pnpm-lock.yaml \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000..a686153a --- /dev/null +++ b/docs/README.md @@ -0,0 +1,13 @@ +# Documentation for JAIRO Cloud Groups Manager + +## development server + +```bash +pnpm docs:dev +``` + +## Static site generation + +```bash +pnpm docs:genrate +``` diff --git a/docs/app/app.vue b/docs/app/app.vue new file mode 100644 index 00000000..46c5a19c --- /dev/null +++ b/docs/app/app.vue @@ -0,0 +1,8 @@ + diff --git a/docs/app/assets/css/main.css b/docs/app/assets/css/main.css new file mode 100644 index 00000000..d71ade55 --- /dev/null +++ b/docs/app/assets/css/main.css @@ -0,0 +1,10 @@ +@import "tailwindcss"; +@import "@nuxt/ui"; + +@theme static { + --font-sans: 'Noto Sans JP', system-ui, sans-serif; +} + +:root { + --ui-container: 90rem; +} diff --git a/docs/app/layouts/default.vue b/docs/app/layouts/default.vue new file mode 100644 index 00000000..f23cd3e9 --- /dev/null +++ b/docs/app/layouts/default.vue @@ -0,0 +1,59 @@ + + + diff --git a/docs/app/pages/[...slug].vue b/docs/app/pages/[...slug].vue new file mode 100644 index 00000000..f4100f45 --- /dev/null +++ b/docs/app/pages/[...slug].vue @@ -0,0 +1,40 @@ + + + diff --git a/docs/app/pages/index.vue b/docs/app/pages/index.vue new file mode 100644 index 00000000..eb25e491 --- /dev/null +++ b/docs/app/pages/index.vue @@ -0,0 +1,3 @@ + diff --git a/docs/content.config.ts b/docs/content.config.ts new file mode 100644 index 00000000..66a157f5 --- /dev/null +++ b/docs/content.config.ts @@ -0,0 +1,10 @@ +import { defineCollection, defineContentConfig } from '@nuxt/content' + +export default defineContentConfig({ + collections: { + detailed: defineCollection({ + type: 'page', + source: 'detailed/**/*', + }), + }, +}) diff --git a/docs/content/detailed/01.introduction.md b/docs/content/detailed/01.introduction.md new file mode 100644 index 00000000..33746846 --- /dev/null +++ b/docs/content/detailed/01.introduction.md @@ -0,0 +1,20 @@ +--- +title: はじめに +description: 設計書の目的、前提条件、全体像について記述する。 +--- + +JAIRO Cloud Groups Manager(以下、当機能)は、JAIRO Cloud のリポジトリ管理者が GakuNin Cloud Gateway 内のグループを管理するためのWebクライアントである。 + +## 目的 +本書は、当機能のサーバーサイドの詳細設計を記述したものである。 + + +## 前提条件 +本機能は、以下のサービスに依存する。 +- mAP Core Authorization Server +- mAP Core API V2 + +また、本機能は以下の環境で動作することを前提とする。 +- Python 3.14.x +- Flask 3.1.x +- Celery 5.6.x diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts new file mode 100644 index 00000000..7c844028 --- /dev/null +++ b/docs/nuxt.config.ts @@ -0,0 +1,20 @@ +// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + modules: [ + '@nuxt/ui', + '@nuxt/content', + ], + + ssr: true, + devtools: { enabled: true }, + css: ['~/assets/css/main.css'], + devServer: { port: 4040 }, + + compatibilityDate: '2025-01-15', + nitro: { + prerender: { + crawlLinks: true, + routes: ['/'], + }, + }, +}) diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 00000000..8d71db4e --- /dev/null +++ b/docs/package.json @@ -0,0 +1,20 @@ +{ + "name": "docs", + "private": true, + "type": "module", + "scripts": { + "build": "nuxt build", + "dev": "nuxt dev", + "generate": "nuxt generate", + "preview": "nuxt preview" + }, + "dependencies": { + "@nuxt/content": "^3.9.0", + "@nuxt/ui": "4.3.0", + "nuxt": "^4.2.2", + "typescript": "^5.9.3" + }, + "devDependencies": { + "better-sqlite3": "^12.5.0" + } +} diff --git a/docs/public/favicon.ico b/docs/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..18993ad91cfd43e03b074dd0b5cc3f37ab38e49c GIT binary patch literal 4286 zcmeHLOKuuL5PjK%MHWVi6lD zOGiREbCw`xmFozJ^aNatJY>w+g ze6a2@u~m#^BZm@8wco9#Crlli0uLb^3E$t2-WIc^#(?t)*@`UpuofJ(Uyh@F>b3Ph z$D^m8Xq~pTkGJ4Q`Q2)te3mgkWYZ^Ijq|hkiP^9`De={bQQ%heZC$QU2UpP(-tbl8 zPWD2abEew;oat@w`uP3J^YpsgT%~jT(Dk%oU}sa$7|n6hBjDj`+I;RX(>)%lm_7N{+B7Mu%H?422lE%MBJH!!YTN2oT7xr>>N-8OF$C&qU^ z>vLsa{$0X%q1fjOe3P1mCv#lN{xQ4_*HCSAZjTb1`}mlc+9rl8$B3OP%VT@mch_~G z7Y+4b{r>9e=M+7vSI;BgB?ryZDY4m>&wcHSn81VH1N~`0gvwH{ z8dv#hG|OK`>1;j7tM#B)Z7zDN?{6=dUal}$e Date: Wed, 24 Dec 2025 13:15:02 +0000 Subject: [PATCH 002/173] docs(dd): describe about app foundation --- docs/app/assets/css/main.css | 1 + docs/content/detailed/01.introduction.md | 8 +- .../detailed/02.foundation/.navigation.yml | 1 + docs/content/detailed/02.foundation/01.app.md | 14 + .../detailed/02.foundation/02.config.md | 269 ++++++++++++++++++ .../detailed/02.foundation/03.const.md | 78 +++++ .../detailed/02.foundation/04.factory.md | 74 +++++ docs/content/detailed/02.foundation/05.ext.md | 113 ++++++++ .../detailed/02.foundation/06.celery.md | 15 + docs/nuxt.config.ts | 14 + 10 files changed, 584 insertions(+), 3 deletions(-) create mode 100644 docs/content/detailed/02.foundation/.navigation.yml create mode 100644 docs/content/detailed/02.foundation/01.app.md create mode 100644 docs/content/detailed/02.foundation/02.config.md create mode 100644 docs/content/detailed/02.foundation/03.const.md create mode 100644 docs/content/detailed/02.foundation/04.factory.md create mode 100644 docs/content/detailed/02.foundation/05.ext.md create mode 100644 docs/content/detailed/02.foundation/06.celery.md diff --git a/docs/app/assets/css/main.css b/docs/app/assets/css/main.css index d71ade55..c688d737 100644 --- a/docs/app/assets/css/main.css +++ b/docs/app/assets/css/main.css @@ -3,6 +3,7 @@ @theme static { --font-sans: 'Noto Sans JP', system-ui, sans-serif; + --font-mono: 'Source Code Pro', ui-monospace, monospace; } :root { diff --git a/docs/content/detailed/01.introduction.md b/docs/content/detailed/01.introduction.md index 33746846..4a725563 100644 --- a/docs/content/detailed/01.introduction.md +++ b/docs/content/detailed/01.introduction.md @@ -1,11 +1,13 @@ --- -title: はじめに -description: 設計書の目的、前提条件、全体像について記述する。 +navigation: + title: はじめに +title: JAIRO Cloud Groups Manager +description: リポジトリ管理者向けのグループ管理ツール。 --- JAIRO Cloud Groups Manager(以下、当機能)は、JAIRO Cloud のリポジトリ管理者が GakuNin Cloud Gateway 内のグループを管理するためのWebクライアントである。 -## 目的 +## 本書について 本書は、当機能のサーバーサイドの詳細設計を記述したものである。 diff --git a/docs/content/detailed/02.foundation/.navigation.yml b/docs/content/detailed/02.foundation/.navigation.yml new file mode 100644 index 00000000..d1d6574a --- /dev/null +++ b/docs/content/detailed/02.foundation/.navigation.yml @@ -0,0 +1 @@ +title: アプリケーション基盤 diff --git a/docs/content/detailed/02.foundation/01.app.md b/docs/content/detailed/02.foundation/01.app.md new file mode 100644 index 00000000..a6ebfa6c --- /dev/null +++ b/docs/content/detailed/02.foundation/01.app.md @@ -0,0 +1,14 @@ +--- +title: app +description: アプリケーションのエントリーポイントを提供する。 +--- + +## app + +アプリケーションのエントリポイントとなるグローバル変数。 +[`factory:create_app`](04.factory.md#create_app) から得た Flask アプリケーションインスタンスを保持する。WSGI サーバーの起動に利用する。 + +#### シグネチャ +```python[app.py] +app: Flask +``` diff --git a/docs/content/detailed/02.foundation/02.config.md b/docs/content/detailed/02.foundation/02.config.md new file mode 100644 index 00000000..8498ab37 --- /dev/null +++ b/docs/content/detailed/02.foundation/02.config.md @@ -0,0 +1,269 @@ +--- +title: config +description: サーバー設定値の管理機能を提供する。 +--- + +## RuntimeConfig +サーバー設定値を管理するクラス。 +運用上適切な値を設定することを想定したサーバ設定値を外部から読み込む機能を提供する。 +安全な設定値の一元管理と、プロパティによる動的生成を担う。 + +#### シグネチャ +```python[config.py] +class RuntimeConfig(pydantic_settings.BaseSettings): +``` + +#### 依存するライブラリ +- **Pydantic / pydantic-settings**: 設定値の型安全管理・読み込みに使用。 +- **SQLAlchemy**: データベース接続 URI の型にこのライブラリの型を使用。 + +#### 継承元 +`pydantic_settings.BaseSettings` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ----------- | --------------------------------------- | --------------------- | +| SERVER_NAME | str | サーバ名 | +| SECRET_KEY | str | 暗号化用キー | +| LOG | [LogConfig](#logconfig) | ログ設定 | +| SP | [SpConfig](#configspconfig) | SP 設定 | +| MAP_CORE | [MapCoreConfig](#configmapcoreconfig) | mAP Core サービス設定 | +| CELERY | [CeleryConfig](#configceleryconfig) | Celery 設定 | +| POSTGRES | [PostgresConfig](#configpostgresconfig) | Postgres 設定 | + +#### プロパティ +これらは `@computed_field` デコレータを使用し、他の属性値から算出される値で属性としてアクセスされる。 + +| プロパティ名 | 型 | 説明 | +| ----------------------- | --------------------- | ------------------- | +| SQLALCHEMY_DATABASE_URI | sqlalchemy.engine.URL | データベース接続URI | + +#### メソッド + +| 名前 | 型 | 説明 | +| ---------------------------- | ----- | -------------------------------------- | +| settings_customise_sources() | tuple | 設定値の読み込み順序を制御するメソッド | + +### SQLALCHEMY_DATABASE_URI +Postgres 設定値をもとにデータベース接続 URI を生成するプロパティ。 +Flask-SQLAlchemy はこの値を使用してデータベース接続を行う。 + +#### シグネチャ +```python[config.py] +@computed_field +@cached_property +def SQLALCHEMY_DATABASE_URI(self) -> sqlalchemy.engine.URL: +``` + +#### デコレータ +- `@computed_field`: Pydantic の計算フィールドを定義するデコレータ。 +- `@cached_property`: プロパティの結果をキャッシュするデコレータ。 + +#### 処理内容 +1. 属性 `POSTGRES` の各値を使用して、SQLAlchemy のデータベース接続 URI を生成する。 + スキーマは `postgresql+psycopg` を使用する。 +2. 生成したデータベース接続 URI を返す。 + + +### settings_customise_sources +設定値の読み込みを制御するメソッド。 +標準で提供される設定ソースに加え、TOML ファイルからの読み込みを追加する。 +戻り値のタプルに含まれる順序で設定ソースが適用される。 + +#### シグネチャ +```python[config.py] +@override +@classmethod +def settings_customise_sources( + cls, settings_cls: type[BaseSettings], + init_settings: PydanticBaseSettingsSource, + env_settings: PydanticBaseSettingsSource, + dotenv_settings: PydanticBaseSettingsSource, + file_secret_settings: PydanticBaseSettingsSource +) -> tuple[PydanticBaseSettingsSource, ...]: +``` + +#### デコレータ +- `@override`: メソッドがスーパークラスのメソッドをオーバーライドしていることを示すデコレータ。 +- `@classmethod`: クラスメソッドを定義するデコレータ。 + +#### 引数 + +| 名前 | 型 | 説明 | +| -------------------- | -------------------------- | --------------------------------------- | +| settings_cls | type\[BaseSettings\] | 設定クラスの型 | +| init_settings | PydanticBaseSettingsSource | コンストラクタ引数からの設定ソース | +| env_settings | PydanticBaseSettingsSource | 環境変数からの設定ソース | +| dotenv_settings | PydanticBaseSettingsSource | .env ファイルからの設定ソース | +| file_secret_settings | PydanticBaseSettingsSource | Kubernetes シークレットからの設定ソース | + +#### 戻り値 +| 型 | 説明 | +| ---------------------------------------- | -------------------------------- | +| tuple\[PydanticBaseSettingsSource, ...\] | カスタムされた設定ソースのタプル | + +#### 処理内容 +1. スーパークラスの `settings_customise_sources` メソッドを呼び出し、標準の設定ソースタプルを取得する。 +2. 引数 `init_settings` から `_toml_file` キーを取り出し、TOML ファイルのパスを取得する。 +3. TOML ファイルのパスが存在しない場合、標準の設定ソースタプルをそのまま戻り値として返す。 + 存在する場合、設定ソースタプルの末尾に TOML ファイルのパスで初期化した `pydantic_settings.TomlSettingsSource` を追加し、戻り値として返す。 + + +## LogConfig +アプリケーションログに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class LogConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------- | --- | ---------------- | +| level | str | ログレベル | +| format | str | ログフォーマット | +| datefmt | str | 日付フォーマット | + + +## SpConfig +SP 関連のサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class SpConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| --------- | --- | ------------------ | +| entity_id | str | SP エンティティ ID | +| crt | str | SP 証明書パス | +| key | str | SP 秘密鍵パス | + + +## MapCoreConfig +mAP Core サービスに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class MapCoreConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| -------- | --- | -------------------------- | +| base_url | str | mAP Core サービスの URL | +| timeout | int | リクエストタイムアウト秒数 | + + +## CeleryConfig +Celery アプリケーションに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class CeleryConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| -------------- | --- | ------------------------ | +| broker_url | str | Celery ブローカーのURL | +| result_backend | str | Celery バックエンドのURL | + + +## PostgresConfig +Postgres に関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class PostgresConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| -------- | --- | ---------------------- | +| user | str | データベースユーザー名 | +| password | str | データベースパスワード | +| host | str | データベースホスト名 | +| port | int | データベースポート番号 | +| db | str | データベース名 | + + +## setup_config +サーバー設定を初期化する関数。 + +#### シグネチャ +```python[config.py] +def setup_config(path_or_obj: str | RuntimeConfig) -> RuntimeConfig: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ----------- | -------------------------------------- | -------------------------------------------------- | +| path_or_obj | str \| [RuntimeConfig](#runtimeconfig) | 設定ファイルのパス、またはサーバー設定インスタンス | + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------- | ------------------------------------ | +| [RuntimeConfig](#runtimeconfig) | 初期化されたサーバー設定インスタンス | + +#### 処理概要 +1. 引数 `path_or_obj` が文字列の場合、その値をファイルパスとして設定ファイルを読み込み、 + サーバー設定インスタンス [`RuntimeConfig`](#runtimeconfig) を生成する。 +1. 戻り値としてサーバー設定インスタンスを返却する。 + + +## config +読み込み済みのサーバー設定にアクセスするためのプロキシとなるグローバル変数。 +アプリケーションのどこからでもサーバー設定にアクセスできる。 + +#### シグネチャ +```python[config.py] +config: RuntimeConfig +``` + +#### 依存するライブラリ +- **Werkzeug**: サーバー設定のインスタンスの遅延取得に使用。 +- **Flask**: `current_app` から Flask 拡張を取得するために使用。 + +#### 処理内容 +1. [`LocalProxy`](https://werkzeug.palletsprojects.com/en/stable/local/#werkzeug.local.LocalProxy) を使用し、 + `current_app` から Flask 拡張 [`"jairocloud-groups-manager"`](./05.ext.md#jairocloudgroupsmanager) を取得し、`config` 属性を参照する。 diff --git a/docs/content/detailed/02.foundation/03.const.md b/docs/content/detailed/02.foundation/03.const.md new file mode 100644 index 00000000..21d10f32 --- /dev/null +++ b/docs/content/detailed/02.foundation/03.const.md @@ -0,0 +1,78 @@ +--- +title: const +description: サーバー内で使用される定数を提供する。 +--- + +## DEFAULT_LOG_FORMAT +デフォルトのログフォーマット文字列。 +ログメッセージの出力形式を定義するために使用される。 + + +## DEFAULT_LOG_FORMAT_DEV +開発環境向けのデフォルトログフォーマット文字列。 +ログメッセージの出力形式を定義するために使用される。 + + +## DEFAULT_LOG_DATEFMT +デフォルトのログ日付フォーマット文字列。 +ログメッセージの日付表示形式を定義するために使用される。 + + +## DEFAULT_CONFIG_PATH +デフォルトの設定ファイルパス。 +アプリケーション起動時に設定ファイルが指定されなかった場合に使用される。 + + +## MAP_USER_SCHEMA +mAP Core API V2 のユーザーリソースのスキーマ識別子。 +ユーザー情報の取得や操作に使用される。 + + +## MAP_GROUP_SCHEMA +mAP Core API V2 のグループリソースのスキーマ識別子。 +グループ情報の取得や操作に使用される。 + + +## MAP_SERVICE_SCHEMA +mAP Core API V2 のサービスリソースのスキーマ識別子。 +サービス情報の取得や操作に使用される。 + + +## MAP_ERROR_SCHEMA +mAP Core API V2 のエラーレスポンスのスキーマ識別子。 +APIエラー情報の取得や操作に使用される。 + + +## MAP_PATCH_SCHEMA +mAP Core API V2 のパッチリクエストのスキーマ識別子。 +パッチ情報の取得や操作に使用される。 + + +## MAP_OAUTH_ISSUE_ENDPOINT +mAP Core Authorization Server のクライアント認証エンドポイントの URL パス。 +クライアントIDおよびシークレットの取得に使用される。 + + +## MAP_OAUTH_AUTHORIZE_ENDPOINT +mAP Core Authorization Server の認可エンドポイントの URL パス。 +OAuth2.0 の認可コードの取得に使用される。 + + +## MAP_OAUTH_TOKEN_ENDPOINT +mAP Core Authorization Server のトークンエンドポイントの URL パス。 +アクセストークンおよびリフレッシュトークンの取得に使用される。 + + +## MAP_USERS_ENDPOINT +mAP Core API V2 のユーザーリソースエンドポイントの URL パス。 +ユーザー情報の取得や操作に使用される。 + + +## MAP_GROUPS_ENDPOINT +mAP Core API V2 のグループリソースエンドポイントの URL パス。 +グループ情報の取得や操作に使用される。 + + +## MAP_SERVICES_ENDPOINT +mAP Core API V2 のサービスリソースエンドポイントの URL パス。 +サービス情報の取得や操作に使用される。 diff --git a/docs/content/detailed/02.foundation/04.factory.md b/docs/content/detailed/02.foundation/04.factory.md new file mode 100644 index 00000000..3e48531f --- /dev/null +++ b/docs/content/detailed/02.foundation/04.factory.md @@ -0,0 +1,74 @@ +--- +title: factory +description: アプリケーションのファクトリ関数を提供する。 +--- + +## create_app +アプリケーションファクトリ関数。Flask アプリケーションを初期化する。 + +#### シグネチャ +```python[factory.py] +@overload +def create_app(import_name: str) -> Flask: ... +@overload +def create_app(import_name: str, *, config_path: str) -> Flask: ... +@overload +def create_app(import_name: str, *, config: RuntimeConfig) -> Flask: ... + +def create_app(import_name: str, config_path: Any = None, config: Any = None) -> Flask: +``` + +#### 引数 + +| 名前 | 型 | デフォルト | 説明 | +| ----------- | --------------------------------------------- | ---------- | ------------------------------------------ | +| import_name | str | - | Flaskアプリケーションのインポート名 | +| config_path | str | None | (オーバーロード)設定ファイルのパス | +| config | [RuntimeConfig](./02.config.md#runtimeconfig) | None | (オーバーロード)サーバー設定インスタンス | + +#### 戻り値 + +| 型 | 説明 | +| ----- | --------------------------------------------- | +| Flask | 初期化されたFlaskアプリケーションインスタンス | + +#### 依存するライブラリ +- **Flask**: サーバーサイドアプリケーションフレームワークとして使用。 + +#### 処理概要 +1. 引数 `import_name` を使用してFlaskアプリケーションのインスタンスを生成する。 +2. Flask 拡張 [`ext:JAIROCloudGroupsManager`](./05.ext.md#jairocloudgroupsmanager) をアプリケーションインスタンスで初期化する。 + このとき、引数 `config_path` または `config` を渡す。 +3. [`celery_init_app`](#celery_init_app) を呼び出し、Celery アプリケーションを初期化する。 +4. 初期化された Flask アプリケーションインスタンスを戻り値として返却する。 + + +## celery_init_app +Celery アプリケーションを生成するファクトリ関数。Flask 拡張として初期化を行う。 + +#### シグネチャ +```python[factory.py] +def celery_init_app(app: Flask) -> Celery: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | ----- | --------------------------------- | +| app | Flask | Flaskアプリケーションインスタンス | + +#### 戻り値 + +| 型 | 説明 | +| ------ | ---------------------------------- | +| Celery | Celeryアプリケーションインスタンス | + +#### 依存するライブラリ +- **Celery**: 非同期タスクの実行と管理に使用。 + +#### 処理概要 +1. Celery アプリケーションのインスタンスを生成する。 + このとき、タスクが Flask アプリケーションのコンテキスト内で実行されるように設定する。 +2. 引数 `app` から設定値 `CELERY` を取得し、Celery アプリケーションの設定に登録する。 +3. Celery アプリケーションを Flask 拡張として `"celery"` という名前で登録する。 +4. 初期化済みの Celery アプリケーションインスタンスを戻り値として返却する。 diff --git a/docs/content/detailed/02.foundation/05.ext.md b/docs/content/detailed/02.foundation/05.ext.md new file mode 100644 index 00000000..89121be6 --- /dev/null +++ b/docs/content/detailed/02.foundation/05.ext.md @@ -0,0 +1,113 @@ +--- +title: ext +description: Flask アプリケーションの拡張機能を提供する。 +--- + +## JAIROCloudGroupsManager +当機能の全体的な初期化処理を行い、Flask 拡張として登録するクラス。 + +#### シグネチャ +```python[ext.py] +class JAIROCloudGroupsManager: +``` + +#### 属性 + +| 名前 | 型 | 説明 | +| ------ | -------------------------------------------------- | ---------------------------------------------------------- | +| config | str \| [RuntimeConfig](02.config.md#runtimeconfig) | サーバー設定のファイルパス、またはサーバー設定インスタンス | + +#### メソッド + +| 名前 | 型 | 説明 | +| -------------------------- | ---- | ------------------------------------------ | +| __init__() | None | クラスの初期化メソッド | +| init_app() | None | Flask アプリケーションを初期化するメソッド | +| init_config() | None | サーバー設定値を初期化するメソッド | +| init_db_app() | None | データベースを初期化するメソッド | + + +### \_\_init__ +クラスの初期化メソッド。 + +#### シグネチャ +```python[ext.py] +def __init__(self, app: Flask = None, config: str | RuntimeConfig = None) -> None: +``` + +#### 引数 + +| 名前 | 型 | デフォルト | 説明 | +| ------ | -------------------------------------------------- | ---------- | ---------------------------------------------- | +| app | Flask | None | Flask アプリケーションインスタンス | +| config | str \| [RuntimeConfig](02.config.md#runtimeconfig) | None | サーバー設定値のインポート名またはオブジェクト | + +#### 処理内容 +1. 属性 `config` に引数 `config` の値を設定する。 + 引数の指定がなければ、[`DEFAULT_CONFIG_PATH`](./03.const.md#default_config_path) を設定する。 +2. 引数 `app` が指定されている場合、[`init_app`](#init_app) メソッドを呼び出す。 + + +### init_app +Flask 拡張として初期化を行うメソッド。 + +#### シグネチャ +```python[ext.py] +def init_app(self, app: Flask) -> None: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | ----- | ---------------------------------- | +| app | Flask | Flask アプリケーションインスタンス | + +#### 処理内容 +1. [`init_config`](#init_config) メソッドを呼び出し、サーバー設定値を初期化する。 +2. [`init_db_app`](#init_db_app) メソッドを呼び出し、データベースを初期化する。 +3. アプリケーションのロガーの設定を行う。 +4. API のルーティングを登録する。 +5. 自身を Flask 拡張として `"jairocloud-groups-manager"` の名前で登録する。 + + +### init_config +サーバー設定値を初期化するメソッド。 + +#### シグネチャ +```python[ext.py] +def init_config(self, app: Flask) -> None: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | ----- | ---------------------------------- | +| app | Flask | Flask アプリケーションインスタンス | + +#### 処理内容 +1. 属性 `config` の値で [`config:setup_config`](02.config.md#setup_config) 関数を呼び出し、 + サーバー設定値を初期化する。 +1. 引数 `app` の設定オブジェクトにサーバー設定値を登録する。 +2. 引数 `app` の設定オブジェクトに環境変数を読み込ませる。 + + +### init_db_app +データベースに関する初期化を行うメソッド。 + +#### シグネチャ +```python[ext.py] +def init_db_app(self, app: Flask) -> None: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | ----- | ---------------------------------- | +| app | Flask | Flask アプリケーションインスタンス | + +#### 依存するライブラリ +- **Flask-SQLAlchemy**: データベース拡張機能の初期化に使用。 + +#### 処理内容 +1. データベース拡張機能を初期化する。 +2. テーブルに対応するモデルクラスをインポートし、読み込ませる。 diff --git a/docs/content/detailed/02.foundation/06.celery.md b/docs/content/detailed/02.foundation/06.celery.md new file mode 100644 index 00000000..487d3ca5 --- /dev/null +++ b/docs/content/detailed/02.foundation/06.celery.md @@ -0,0 +1,15 @@ +--- +title: celery +description: Celery アプリケーションのエントリーポイントを提供する。 +--- + +## celery_app +Celery アプリケーションのエントリポイントとなるグローバル変数。 + +#### シグネチャ +```python[celery_app.py] +celery_app: Celery +``` + +#### 処理内容 +1. [`factory:create_app`](./04.factory.md#create_app) から得た Flask アプリケーションのインスタンスより、Flask 拡張 `"celery"` として初期化された Celery アプリケーションのインスタンスを取得し、保持する。 diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index 7c844028..ea8997e8 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -8,6 +8,20 @@ export default defineNuxtConfig({ ssr: true, devtools: { enabled: true }, css: ['~/assets/css/main.css'], + + content: { + build: { + markdown: { + highlight: { + theme: { + default: 'github-light', + dark: 'github-dark', + }, + langs: ['python'], + }, + }, + }, + }, devServer: { port: 4040 }, compatibilityDate: '2025-01-15', From a448ad0a70b5a87750b0700148c66f92a826b726 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Mon, 5 Jan 2026 02:08:15 +0000 Subject: [PATCH 003/173] rename(docs): add index for detailed docs --- docs/content.config.ts | 2 +- .../02.foundation/.navigation.yml | 0 .../02.foundation/01.app.md | 0 .../02.foundation/02.config.md | 18 +++++------ .../02.foundation/03.const.md | 0 .../02.foundation/04.factory.md | 0 .../02.foundation/05.ext.md | 0 .../02.foundation/06.celery.md | 0 docs/content/01.detailed/index.md | 30 +++++++++++++++++++ docs/content/detailed/01.introduction.md | 22 -------------- 10 files changed, 40 insertions(+), 32 deletions(-) rename docs/content/{detailed => 01.detailed}/02.foundation/.navigation.yml (100%) rename docs/content/{detailed => 01.detailed}/02.foundation/01.app.md (100%) rename docs/content/{detailed => 01.detailed}/02.foundation/02.config.md (92%) rename docs/content/{detailed => 01.detailed}/02.foundation/03.const.md (100%) rename docs/content/{detailed => 01.detailed}/02.foundation/04.factory.md (100%) rename docs/content/{detailed => 01.detailed}/02.foundation/05.ext.md (100%) rename docs/content/{detailed => 01.detailed}/02.foundation/06.celery.md (100%) create mode 100644 docs/content/01.detailed/index.md delete mode 100644 docs/content/detailed/01.introduction.md diff --git a/docs/content.config.ts b/docs/content.config.ts index 66a157f5..7f3131f1 100644 --- a/docs/content.config.ts +++ b/docs/content.config.ts @@ -4,7 +4,7 @@ export default defineContentConfig({ collections: { detailed: defineCollection({ type: 'page', - source: 'detailed/**/*', + source: '01.detailed/**/*', }), }, }) diff --git a/docs/content/detailed/02.foundation/.navigation.yml b/docs/content/01.detailed/02.foundation/.navigation.yml similarity index 100% rename from docs/content/detailed/02.foundation/.navigation.yml rename to docs/content/01.detailed/02.foundation/.navigation.yml diff --git a/docs/content/detailed/02.foundation/01.app.md b/docs/content/01.detailed/02.foundation/01.app.md similarity index 100% rename from docs/content/detailed/02.foundation/01.app.md rename to docs/content/01.detailed/02.foundation/01.app.md diff --git a/docs/content/detailed/02.foundation/02.config.md b/docs/content/01.detailed/02.foundation/02.config.md similarity index 92% rename from docs/content/detailed/02.foundation/02.config.md rename to docs/content/01.detailed/02.foundation/02.config.md index 8498ab37..b64111ac 100644 --- a/docs/content/detailed/02.foundation/02.config.md +++ b/docs/content/01.detailed/02.foundation/02.config.md @@ -22,15 +22,15 @@ class RuntimeConfig(pydantic_settings.BaseSettings): #### クラス属性 -| 名前 | 型 | 説明 | -| ----------- | --------------------------------------- | --------------------- | -| SERVER_NAME | str | サーバ名 | -| SECRET_KEY | str | 暗号化用キー | -| LOG | [LogConfig](#logconfig) | ログ設定 | -| SP | [SpConfig](#configspconfig) | SP 設定 | -| MAP_CORE | [MapCoreConfig](#configmapcoreconfig) | mAP Core サービス設定 | -| CELERY | [CeleryConfig](#configceleryconfig) | Celery 設定 | -| POSTGRES | [PostgresConfig](#configpostgresconfig) | Postgres 設定 | +| 名前 | 型 | 説明 | +| ----------- | --------------------------------- | --------------------- | +| SERVER_NAME | str | サーバ名 | +| SECRET_KEY | str | 暗号化用キー | +| LOG | [LogConfig](#logconfig) | ログ設定 | +| SP | [SpConfig](#spconfig) | SP 設定 | +| MAP_CORE | [MapCoreConfig](#mapcoreconfig) | mAP Core サービス設定 | +| CELERY | [CeleryConfig](#celeryconfig) | Celery 設定 | +| POSTGRES | [PostgresConfig](#postgresconfig) | Postgres 設定 | #### プロパティ これらは `@computed_field` デコレータを使用し、他の属性値から算出される値で属性としてアクセスされる。 diff --git a/docs/content/detailed/02.foundation/03.const.md b/docs/content/01.detailed/02.foundation/03.const.md similarity index 100% rename from docs/content/detailed/02.foundation/03.const.md rename to docs/content/01.detailed/02.foundation/03.const.md diff --git a/docs/content/detailed/02.foundation/04.factory.md b/docs/content/01.detailed/02.foundation/04.factory.md similarity index 100% rename from docs/content/detailed/02.foundation/04.factory.md rename to docs/content/01.detailed/02.foundation/04.factory.md diff --git a/docs/content/detailed/02.foundation/05.ext.md b/docs/content/01.detailed/02.foundation/05.ext.md similarity index 100% rename from docs/content/detailed/02.foundation/05.ext.md rename to docs/content/01.detailed/02.foundation/05.ext.md diff --git a/docs/content/detailed/02.foundation/06.celery.md b/docs/content/01.detailed/02.foundation/06.celery.md similarity index 100% rename from docs/content/detailed/02.foundation/06.celery.md rename to docs/content/01.detailed/02.foundation/06.celery.md diff --git a/docs/content/01.detailed/index.md b/docs/content/01.detailed/index.md new file mode 100644 index 00000000..1337797f --- /dev/null +++ b/docs/content/01.detailed/index.md @@ -0,0 +1,30 @@ +--- +navigation: + title: はじめに +title: サーバーサイド詳細設計書 +description: リポジトリ管理者向けのグループ管理ツール。 +headline: JAIRO Cloud Groups Manager +--- + +## 本書について +本書は、JAIRO Cloud Groups Manager(以降、当機能)のサーバーサイドの詳細設計を記述したものである。 + + +## 前提条件 +本機能は、以下のサービスに依存する。 +- mAP Core Authorization Server +- mAP Core API V2 + +また、本機能は以下の環境で動作することを前提とする。 +- Python 3.14.x +- Flask 3.1.x +- Celery 5.6.x + + +## 主要な依存ライブラリ +- **Flask**: Webアプリケーションフレームワーク。REST APIやルーティング、リクエスト管理を提供する。 +- **SQLAlchemy**: ORM。DB接続・モデル定義・クエリ発行を担当する。 +- **Celery**: 分散タスクキュー。非同期処理(例:グループ作成・更新のバックグラウンド処理)を実現する。 +- **Pydantic**: JSONおよびPythonオブジェクト間の型安全なシリアライズ・デシリアライズを行う。 +- **Redis**: Celeryのバックエンド、セッション管理、キャッシュストアとして利用する。 +- **amqp (RabbitMQ)**: Celeryのメッセージブローカーとして使用する。 diff --git a/docs/content/detailed/01.introduction.md b/docs/content/detailed/01.introduction.md deleted file mode 100644 index 4a725563..00000000 --- a/docs/content/detailed/01.introduction.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -navigation: - title: はじめに -title: JAIRO Cloud Groups Manager -description: リポジトリ管理者向けのグループ管理ツール。 ---- - -JAIRO Cloud Groups Manager(以下、当機能)は、JAIRO Cloud のリポジトリ管理者が GakuNin Cloud Gateway 内のグループを管理するためのWebクライアントである。 - -## 本書について -本書は、当機能のサーバーサイドの詳細設計を記述したものである。 - - -## 前提条件 -本機能は、以下のサービスに依存する。 -- mAP Core Authorization Server -- mAP Core API V2 - -また、本機能は以下の環境で動作することを前提とする。 -- Python 3.14.x -- Flask 3.1.x -- Celery 5.6.x From 45e157ae322ab3668083af24a14b4a96f7ef81d6 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Mon, 5 Jan 2026 02:12:55 +0000 Subject: [PATCH 004/173] docs: add API specifications --- docs/content/02.api/endpoints.md | 43 ++++++++++++++++++++++++++++++++ docs/content/02.api/index.md | 14 +++++++++++ 2 files changed, 57 insertions(+) create mode 100644 docs/content/02.api/endpoints.md create mode 100644 docs/content/02.api/index.md diff --git a/docs/content/02.api/endpoints.md b/docs/content/02.api/endpoints.md new file mode 100644 index 00000000..a0dcf2b0 --- /dev/null +++ b/docs/content/02.api/endpoints.md @@ -0,0 +1,43 @@ +--- +title: エンドポイント一覧 +description: 当機能が提供する API のエンドポイント一覧。 +headline: JAIRO Cloud Groups Manager +--- + +## 認証・セッション関連 API + +| メソッド | エンドポイント | ログイン済み | 未ログイン | 概要 | +| -------- | ---------------- | :----------: | :--------: | ---------- | +| POST | /api/auth/login | - | ○ | ログイン | +| POST | /api/auth/logout | ○ | - | ログアウト | + +## リポジトリ管理 API + +| メソッド | エンドポイント | システム管理者 | リポジトリ管理者 | 概要 | +| -------- | --------------------- | :------------: | :--------------: | -------------------------- | +| GET | /api/repositories | ○ | ○ | リポジトリの一覧取得・検索 | +| POST | /api/repositories | ○ | × | リポジトリの作成 | +| GET | /api/repositories/:id | ○ | ○ | リポジトリの情報取得 | +| PUT | /api/repositories/:id | ○ | × | リポジトリの更新 | +| DELETE | /api/repositories/:id | ○ | × | リポジトリの削除 | + +## グループ管理 API + +| メソッド | エンドポイント | システム管理者 | リポジトリ管理者 | 説明 | +| -------- | --------------- | :------------: | :--------------: | ------------------------ | +| GET | /api/groups | ○ | ○ | グループの一覧取得・検索 | +| POST | /api/groups | ○ | ○ | グループの作成 | +| GET | /api/groups/:id | ○ | ○ | グループの情報取得 | +| PUT | /api/groups/:id | ○ | ○ | グループの更新 | +| PATCH | /api/groups/:id | ○ | ○ | グループの部分更新 | +| DELETE | /api/groups/:id | ○ | ○ | グループの削除 | + +## ユーザー管理 API + +| メソッド | エンドポイント | システム管理者 | リポジトリ管理者 | 概要 | +| ---------- | -------------- | :------------: | :--------------: | ------------------------------ | +| GET | /api/users | ○ | ○ | ユーザーの取得・検索 | +| POST | /api/users | ○ | ○ | ユーザーの作成 | +| GET | /api/users/:id | ○ | ○ | ユーザーの情報取得 | +| PUT | /api/users/:id | ○ | ○ | ユーザーの更新 | +| ~~DELETE~~ | /api/users/:id | ○ | × | ユーザーの削除(実装予定なし) | diff --git a/docs/content/02.api/index.md b/docs/content/02.api/index.md new file mode 100644 index 00000000..a88dd72d --- /dev/null +++ b/docs/content/02.api/index.md @@ -0,0 +1,14 @@ +--- +navigation: + title: はじめに +title: API 仕様書 +description: リポジトリ管理者向けのグループ管理ツール。 +headline: JAIRO Cloud Groups Manager +--- + +## 本書について +本書は、JAIRO Cloud Groups Manager(以降、当機能)が提供する API の仕様を記述したものである。 + + +## 注意事項 +この API は主にクライアントサイドのアプリケーションから利用されることを想定しており、単独での利用は考慮されていない。 From 1d6b6f9d4b9bea8bb8f462e83bfcc58cd02f29e3 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Mon, 5 Jan 2026 02:26:50 +0000 Subject: [PATCH 005/173] docs: create top page and nav header --- docs/app/assets/css/main.css | 6 +++ docs/app/components/DocsHeader.vue | 31 +++++++++++++++ docs/app/composables/useNavigation.ts | 24 ++++++++++++ docs/app/layouts/default.vue | 48 ----------------------- docs/app/layouts/docs.vue | 49 ++++++++++++++++++++++++ docs/app/pages/[...slug].vue | 40 ++++++++++++++++--- docs/app/pages/index.vue | 27 ++++++++++++- docs/content.config.ts | 17 ++++++++ docs/content/01.detailed/.navigation.yml | 2 + docs/content/02.api/.navigation.yml | 2 + docs/content/02.api/endpoints.md | 2 +- docs/content/index.md | 4 ++ 12 files changed, 197 insertions(+), 55 deletions(-) create mode 100644 docs/app/components/DocsHeader.vue create mode 100644 docs/app/composables/useNavigation.ts create mode 100644 docs/app/layouts/docs.vue create mode 100644 docs/content/01.detailed/.navigation.yml create mode 100644 docs/content/02.api/.navigation.yml create mode 100644 docs/content/index.md diff --git a/docs/app/assets/css/main.css b/docs/app/assets/css/main.css index c688d737..c42b39f9 100644 --- a/docs/app/assets/css/main.css +++ b/docs/app/assets/css/main.css @@ -6,6 +6,12 @@ --font-mono: 'Source Code Pro', ui-monospace, monospace; } +@layer components { + :not(h1, h2, h3, h4, h5, h6) > a code { + @apply text-primary!; + } +} + :root { --ui-container: 90rem; } diff --git a/docs/app/components/DocsHeader.vue b/docs/app/components/DocsHeader.vue new file mode 100644 index 00000000..2df35632 --- /dev/null +++ b/docs/app/components/DocsHeader.vue @@ -0,0 +1,31 @@ + + + + diff --git a/docs/app/composables/useNavigation.ts b/docs/app/composables/useNavigation.ts new file mode 100644 index 00000000..e223a971 --- /dev/null +++ b/docs/app/composables/useNavigation.ts @@ -0,0 +1,24 @@ +import type { Collections } from '@nuxt/content' + +export const useNavigation = async () => { + const route = useRoute() + + const collection = computed(() => { + return route.path.split('/') + .find(segment => segment.length > 0) as Exclude + }) + + const { data: navigation } = await useAsyncData(`navigation-${collection.value}`, () => { + return queryCollectionNavigation(collection.value) + }, { + transform: (data) => { + return data?.find(item => item.path === `/${collection.value}`)?.children || data || [] + }, + watch: [collection], + }) + + return { + collection, + navigation, + } +} diff --git a/docs/app/layouts/default.vue b/docs/app/layouts/default.vue index f23cd3e9..4dfd7f6a 100644 --- a/docs/app/layouts/default.vue +++ b/docs/app/layouts/default.vue @@ -1,59 +1,11 @@ - - diff --git a/docs/app/layouts/docs.vue b/docs/app/layouts/docs.vue new file mode 100644 index 00000000..9c2200e3 --- /dev/null +++ b/docs/app/layouts/docs.vue @@ -0,0 +1,49 @@ + + + + diff --git a/docs/app/pages/[...slug].vue b/docs/app/pages/[...slug].vue index f4100f45..c27920e1 100644 --- a/docs/app/pages/[...slug].vue +++ b/docs/app/pages/[...slug].vue @@ -1,19 +1,49 @@ + + diff --git a/docs/content.config.ts b/docs/content.config.ts index 7f3131f1..be31115f 100644 --- a/docs/content.config.ts +++ b/docs/content.config.ts @@ -1,10 +1,27 @@ import { defineCollection, defineContentConfig } from '@nuxt/content' +import { z } from 'zod' export default defineContentConfig({ collections: { + index: defineCollection({ + type: 'page', + source: 'index.md', + }), detailed: defineCollection({ type: 'page', source: '01.detailed/**/*', + schema: z.object({ + headline: z.string().optional(), + icon: z.string().optional(), + }), + }), + api: defineCollection({ + type: 'page', + source: '02.api/**/*', + schema: z.object({ + headline: z.string().optional(), + icon: z.string().optional(), + }), }), }, }) diff --git a/docs/content/01.detailed/.navigation.yml b/docs/content/01.detailed/.navigation.yml new file mode 100644 index 00000000..1670fe94 --- /dev/null +++ b/docs/content/01.detailed/.navigation.yml @@ -0,0 +1,2 @@ +title: 詳細設計書 +icon: i-lucide-book-text diff --git a/docs/content/02.api/.navigation.yml b/docs/content/02.api/.navigation.yml new file mode 100644 index 00000000..9db192c6 --- /dev/null +++ b/docs/content/02.api/.navigation.yml @@ -0,0 +1,2 @@ +title: API 仕様書 +icon: i-lucide-cog diff --git a/docs/content/02.api/endpoints.md b/docs/content/02.api/endpoints.md index a0dcf2b0..e08554f4 100644 --- a/docs/content/02.api/endpoints.md +++ b/docs/content/02.api/endpoints.md @@ -1,7 +1,7 @@ --- title: エンドポイント一覧 description: 当機能が提供する API のエンドポイント一覧。 -headline: JAIRO Cloud Groups Manager +headline: API 仕様書 --- ## 認証・セッション関連 API diff --git a/docs/content/index.md b/docs/content/index.md new file mode 100644 index 00000000..56ed6910 --- /dev/null +++ b/docs/content/index.md @@ -0,0 +1,4 @@ +--- +title: Jairo Cloud Groups Manager ドキュメント +description: JAIRO Cloud Groups Manager は、JAIRO Cloud のリポジトリ管理者が GakuNin Cloud Gateway 内のグループを管理するためのWebクライアントである。 +--- From bd5f714681a63db43d00202bd22c9b00cf15b0fd Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Mon, 5 Jan 2026 02:35:56 +0000 Subject: [PATCH 006/173] docs(dd): add controller specifications --- .../01.detailed/04.api/.navigation.yml | 2 + docs/content/01.detailed/04.api/01.router.md | 27 ++ docs/content/01.detailed/04.api/02.auth.md | 7 + .../01.detailed/04.api/03.repositories.md | 7 + docs/content/01.detailed/04.api/04.groups.md | 7 + docs/content/01.detailed/04.api/05.users.md | 278 ++++++++++++++++++ 6 files changed, 328 insertions(+) create mode 100644 docs/content/01.detailed/04.api/.navigation.yml create mode 100644 docs/content/01.detailed/04.api/01.router.md create mode 100644 docs/content/01.detailed/04.api/02.auth.md create mode 100644 docs/content/01.detailed/04.api/03.repositories.md create mode 100644 docs/content/01.detailed/04.api/04.groups.md create mode 100644 docs/content/01.detailed/04.api/05.users.md diff --git a/docs/content/01.detailed/04.api/.navigation.yml b/docs/content/01.detailed/04.api/.navigation.yml new file mode 100644 index 00000000..635e400d --- /dev/null +++ b/docs/content/01.detailed/04.api/.navigation.yml @@ -0,0 +1,2 @@ +title: コントローラ + diff --git a/docs/content/01.detailed/04.api/01.router.md b/docs/content/01.detailed/04.api/01.router.md new file mode 100644 index 00000000..ac88a4a5 --- /dev/null +++ b/docs/content/01.detailed/04.api/01.router.md @@ -0,0 +1,27 @@ +--- +title: api.router +description: API エンドポイントのルーティング定義を提供する。 +--- + +## create_api_blueprint +それぞれの機能の API エンドポイントをひとつの Blueprint にまとめ、ルーティングを定義する。また、予期せぬエラーが発生した場合の共通エラーハンドラを登録する。 + +#### シグネチャ +```python[router.py] +def create_api_blueprint() -> Blueprint: +``` + +#### 戻り値 + +| 型 | 説明 | +| --------- | ------------------------------ | +| Blueprint | API エンドポイントの Blueprint | + +#### 依存するライブラリ +- **Flask**: Blueprint およびエラーハンドラの定義に使用。 + +#### 処理概要 +1. API エンドポイントの `Blueprint` インスタンスを生成する。 +2. 各機能モジュールから Blueprint をインポートし、生成した API Blueprint に登録する。 +3. 予期せぬエラーが発生した場合の共通エラーハンドラを登録する。レスポンスは `ErrorResponse` でシリアライズする。 +4. API Blueprint を戻り値として返す。 diff --git a/docs/content/01.detailed/04.api/02.auth.md b/docs/content/01.detailed/04.api/02.auth.md new file mode 100644 index 00000000..644c3d19 --- /dev/null +++ b/docs/content/01.detailed/04.api/02.auth.md @@ -0,0 +1,7 @@ +--- +title: api.auth +description: 認証・セッション関連の API エンドポイントを提供する。 +--- + +## bp +認証・セッション関連の API を提供する Blueprint インスタンス。 diff --git a/docs/content/01.detailed/04.api/03.repositories.md b/docs/content/01.detailed/04.api/03.repositories.md new file mode 100644 index 00000000..dd7f8937 --- /dev/null +++ b/docs/content/01.detailed/04.api/03.repositories.md @@ -0,0 +1,7 @@ +--- +title: api.repositories +description: クライアントサイドが使用するリポジトリ管理関連の API を提供する。 +--- + +## bp +リポジトリ管理関連の API を提供する Blueprint インスタンス。 diff --git a/docs/content/01.detailed/04.api/04.groups.md b/docs/content/01.detailed/04.api/04.groups.md new file mode 100644 index 00000000..b393baba --- /dev/null +++ b/docs/content/01.detailed/04.api/04.groups.md @@ -0,0 +1,7 @@ +--- +title: api.groups +description: クライアントサイドが使用するグループ管理関連の API を提供する。 +--- + +## bp +グループ管理関連の API を提供する Blueprint インスタンス。 diff --git a/docs/content/01.detailed/04.api/05.users.md b/docs/content/01.detailed/04.api/05.users.md new file mode 100644 index 00000000..35e9c513 --- /dev/null +++ b/docs/content/01.detailed/04.api/05.users.md @@ -0,0 +1,278 @@ +--- +title: api.users +description: クライアントサイドが使用するユーザー管理関連の API のコントローラ機能を提供する。 +--- + +## bp +ユーザー管理関連の API を提供する Blueprint インスタンス。 +名前は `"users"`、URL プレフィックスは [`router:create_api_blueprint`](./02.router.md#create_api_blueprint) によって `"/api/users"` に設定される。 + +#### シグネチャ +```python[users.py] +bp: Blueprint +``` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 + + +## get +ユーザーの取得・検索の機能を提供するコントローラ。 + +#### シグネチャ +```python[users.py] +@bp.get("/") +@login_required +@roles_required("system_admin", "repository_admin") +@validate() +def get(query: UsersQuery) -> tuple[UsersResult | ErrorResponse, int]: +``` + +#### エンドポイント +`GET /api/users` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 +- **Flask-Login**: ログインユーザーの認証を行う。 +- **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 + +#### デコレータ +- `@bp.get`: GET リクエストを処理するエンドポイントを定義する。 +- `@login_required`: ログインを要求する。 +- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ----- | ---------- | -------------------------------------- | +| query | UsersQuery | ユーザー検索条件を含むクエリパラメータ | + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------ | -------------------------------------------------------------- | +| tuple\[UsersResult, Literal\[200]] | ユーザーの検索結果を含むレスポンス | +| tuple\[ErrorResponse, Literal\[403]] | ロールの権限を越える検索条件が指定された場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[500]] | 検索処理に失敗した場合のエラーレスポンス | + +#### 処理内容 + +1. リクエストコンテキストからログインユーザー情報を取得する。 +2. `build_user_search_criteria` を呼び出し、引数 `query` とログインユーザーのロールに基づいた `UserSearchCriteria` オブジェクトを構築する。 + 検索条件にロールの権限を越える条件が含まれている場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 403 とともに返す。 +3. `UserSearchCriteria` オブジェクトをもとに、`services.users:search` を呼び出してユーザーの検索結果を取得する。 +4. 検索処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 +5. 検索結果を `UsersResult` オブジェクトに格納し、ステータスコード 200 とともに返す。 + + +## post +ユーザーの作成の機能を提供するコントローラ。 + +#### シグネチャ +```python[users.py] +@bp.post("/") +@login_required +@roles_required("system_admin", "repository_admin") +@validate() +def post(body: UserDetail) -> tuple[UserDetail, int, dict[str, str]] | tuple[ErrorResponse, int]: +``` + +#### エンドポイント +`POST /api/users` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 +- **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 +- **Flask-Login**: ユーザー認証を行う。 + +#### デコレータ +- `@bp.post`: POST リクエストを処理するエンドポイントを定義する。 +- `@login_required`: ログインを要求する。 +- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | ---------- | ------------------------------------------ | +| body | UserDetail | 作成するユーザー情報を含むリクエストボディ | + +#### 戻り値 + +| 型 | 説明 | +| -------------------------------------------------- | ---------------------------------------------------------- | +| tuple\[UserDetail, Literal\[201], dict\[str, str]] | 作成されたユーザー情報と Location ヘッダーを含むレスポンス | +| tuple\[ErrorResponse, Literal\[403]] | ユーザー作成権限がない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[409]] | ユーザーIDまたはePPNがすでに存在する場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[500]] | ユーザー作成処理に失敗した場合のエラーレスポンス | + +#### 処理内容 + +1. リクエストコンテキストからログインユーザー情報を取得する。 +2. ユーザーID、ePPNがすでに存在するかを確認する。 + 存在する場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 409 とともに返す。 +3. ログインユーザーのロールをもとに、ユーザーを作成できるかを確認する。 + - システム管理者の場合、すべてのユーザーを作成できる。 + - リポジトリ管理者の場合、ログインユーザーが管理するリポジトリに所属するユーザーのみ作成できる。 + すべての所属リポジトリがログインユーザーが管理するリポジトリに含まれていなければ、ステータスコード 403 とともに返す。 +4. 引数 `body` をもとに、`services.users:create` を呼び出してユーザーを作成する。 +5. ユーザー作成処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 +6. 作成されたユーザー情報を `UserDetail` オブジェクトとして、ステータスコード 201 および `Location` ヘッダーとともに返す。 + `Location` ヘッダーには作成されたユーザーの情報取得 API の絶対 URL を設定する。 + + +## id_get +ユーザーの情報取得の機能を提供するコントローラ。 + +#### シグネチャ +```python[users.py] +@bp.get("/") +@login_required +@roles_required("system_admin", "repository_admin") +@validate() +def id_get(user_id: str) -> tuple[UserDetail | ErrorResponse, int]: +``` + +#### エンドポイント +`GET /api/users/` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 +- **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 +- **Flask-Login**: ユーザー認証を行う。 + +#### デコレータ +- `@bp.get`: GET リクエストを処理するエンドポイントを定義する。 +- `@login_required`: ログインを要求する。 +- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ------- | --- | ---------- | +| user_id | str | ユーザーID | + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------ | -------------------------------------------------------- | +| tuple\[UserDetail, Literal\[200]] | ユーザー情報を含むレスポンス | +| tuple\[ErrorResponse, Literal\[403]] | ユーザー情報へのアクセス権限がない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[404]] | ユーザーが存在しない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[500]] | ユーザー情報の取得に失敗した場合のエラーレスポンス | + +**処理内容** +1. リクエストコンテキストからログインユーザー情報を取得する。 +2. 引数 `user_id` をもとに、`services.users:get_by_id` を呼び出してユーザー情報を取得する。 +3. ユーザーが存在しなかった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 +4. 取得に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 +5. 取得されたユーザー情報とログインユーザーのロールに基づき、ユーザー情報へのアクセスできるかを確認する。 + - システム管理者の場合、すべてのユーザー情報にアクセスできる。 + - リポジトリ管理者の場合、ログインユーザーが管理するリポジトリに所属するユーザーにのみアクセスできる。 + 所属リポジトリがログインユーザーの管理するリポジトリに含まれていない場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 403 とともに返す。 +6. 取得したユーザー情報を `UserDetail` オブジェクトとして、ステータスコード 200 とともに返す。 + + +## id_put +ユーザーの更新の機能を提供するコントローラ。 + +#### シグネチャ +```python[users.py] +@bp.put("/") +@login_required +@roles_required("system_admin", "repository_admin") +@validate() +def id_put(user_id: str, body: UserDetail) -> tuple[UserDetail | ErrorResponse, int]: +``` + +#### エンドポイント +`PUT /api/users/` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 +- **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 +- **Flask-Login**: ユーザー認証を行う。 + +#### デコレータ +- `@bp.put`: PUT リクエストを処理するエンドポイントを定義する。 +- `@login_required`: ログインを要求する。 +- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ------- | ---------- | ------------------------------------------ | +| user_id | str | ユーザーID | +| body | UserDetail | 更新するユーザー情報を含むリクエストボディ | + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------ | ------------------------------------------------------ | +| tuple\[UserDetail, Literal\[200]] | 更新されたユーザー情報を含むレスポンス | +| tuple\[ErrorResponse, Literal\[403]] | ユーザー情報を更新する権限がない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[404]] | ユーザーが存在しない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[409]] | 更新内容に競合があった場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[500]] | ユーザー更新処理に失敗した場合のエラーレスポンス | + +#### 処理内容 +1. リクエストコンテキストからログインユーザー情報を取得する。 +2. 引数 `body` とログインユーザーのロールをもとに、ユーザーを更新できるかを確認する。 + - システム管理者の場合、すべてのユーザーを更新できる。 + - リポジトリ管理者の場合、ログインユーザーが管理するリポジトリに所属するユーザーのみ更新できる。 + すべての所属リポジトリがログインユーザーの管理するリポジトリに含まれていない場合、ステータスコード 403 とともに返す。 +3. 引数 `body` をもとに、`services.users:update` を呼び出してユーザー情報を更新する。 +4. ユーザーが存在しない場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 +5. 更新内容に競合があった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 409 とともに返す。 +6. ユーザー更新処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 +7. 更新されたユーザー情報を`UserDetail` オブジェクトとして、ステータスコード 200 とともに返す。 + + +## ~~id_delete~~ +ユーザーの削除の機能を提供するコントローラ。実装予定なし。 + +#### シグネチャ +```python[users.py] +@bp.delete("/") +@login_required +@roles_required("system_admin") +@validate() +def id_delete(user_id: str) -> tuple[ Literal[""] | ErrorResponse, int]: +``` + +#### エンドポイント +`DELETE /api/users/` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 +- **Flask-Login**: ユーザー認証を行う。 +- **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 + +#### デコレータ +- `@bp.delete`: DELETE リクエストを処理するエンドポイントを定義する。 +- `@login_required`: ログインを要求する。 +- `@roles_required`: ログインユーザーに「システム管理者」のロールを要求する。 +- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ------- | --- | ---------- | +| user_id | str | ユーザーID | + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------ | ------------------------------------------------ | +| tuple\[Literal\[""\], Literal\[204]] | 正常にユーザーが削除された場合のレスポンス | +| tuple\[ErrorResponse, Literal\[404]] | ユーザーが存在しない場合のエラーレスポンス | +| tuple\[ErrorResponse, Literal\[500]] | ユーザー削除処理に失敗した場合のエラーレスポンス | + +#### 処理内容 +1. 引数 `user_id` をもとに、`services.users:delete` を呼び出してユーザーを削除する。 +2. ユーザーが存在しなかった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 +3. ユーザー削除処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 +4. 正常にユーザーが削除された場合、空文字列とステータスコード 204 とともに返す。 From 9cbdea58bdebada598eb4b411b48454eb0b8e9b3 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 9 Jan 2026 11:18:31 +0000 Subject: [PATCH 007/173] docs: update constant definitions --- .../01.detailed/02.foundation/03.const.md | 179 ++++++++++++++++++ docs/nuxt.config.ts | 1 + 2 files changed, 180 insertions(+) diff --git a/docs/content/01.detailed/02.foundation/03.const.md b/docs/content/01.detailed/02.foundation/03.const.md index 21d10f32..c66300ed 100644 --- a/docs/content/01.detailed/02.foundation/03.const.md +++ b/docs/content/01.detailed/02.foundation/03.const.md @@ -7,72 +7,251 @@ description: サーバー内で使用される定数を提供する。 デフォルトのログフォーマット文字列。 ログメッセージの出力形式を定義するために使用される。 +#### シグネチャ +```python[const.py] +DEFAULT_LOG_FORMAT: str +``` + +#### 値 + +```python +"[%(asctime)s.%(msecs)03dZ] %(levelname)-8s %(message)s (%(addr)s - %(user)s)" +``` + ## DEFAULT_LOG_FORMAT_DEV 開発環境向けのデフォルトログフォーマット文字列。 ログメッセージの出力形式を定義するために使用される。 +#### シグネチャ +```python[const.py] +DEFAULT_LOG_FORMAT_DEV: str +``` + +#### 値 + +```python +"[%(asctime)s.%(msecs)03dZ] %(levelname)-8s %(message)s (%(pathname)s:%(lineno)d)" +``` + + ## DEFAULT_LOG_DATEFMT デフォルトのログ日付フォーマット文字列。 ログメッセージの日付表示形式を定義するために使用される。 +#### シグネチャ +```python[const.py] +DEFAULT_LOG_DATEFMT: str +``` + +#### 値 + +```python +"%Y-%m-%dT%H:%M:%S" +``` ## DEFAULT_CONFIG_PATH デフォルトの設定ファイルパス。 アプリケーション起動時に設定ファイルが指定されなかった場合に使用される。 +#### シグネチャ +```python[const.py] +DEFAULT_CONFIG_PATH: str +``` + +#### 値 + +```python +"configs/server.config.toml" +``` ## MAP_USER_SCHEMA mAP Core API V2 のユーザーリソースのスキーマ識別子。 ユーザー情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_USER_SCHEMA: str +``` + +#### 値 + +```python +"urn:ietf:params:scim:schemas:mace:gakunin.jp:core:2.0:User" +``` + ## MAP_GROUP_SCHEMA mAP Core API V2 のグループリソースのスキーマ識別子。 グループ情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_GROUP_SCHEMA: str +``` + +#### 値 + +```python +"urn:ietf:params:scim:schemas:mace:gakunin.jp:core:2.0:Group" +``` ## MAP_SERVICE_SCHEMA mAP Core API V2 のサービスリソースのスキーマ識別子。 サービス情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_SERVICE_SCHEMA: str +``` + +#### 値 + +```python +"urn:ietf:params:scim:schemas:mace:gakunin.jp:core:2.0:Service" +``` + ## MAP_ERROR_SCHEMA mAP Core API V2 のエラーレスポンスのスキーマ識別子。 APIエラー情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_ERROR_SCHEMA: str +``` + +#### 値 + +```python +"urn:ietf:params:scim:schemas:mace:gakunin.jp:core:2.0:Error" +``` + ## MAP_PATCH_SCHEMA mAP Core API V2 のパッチリクエストのスキーマ識別子。 パッチ情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_PATCH_SCHEMA: str +``` + +#### 値 + +```python +"urn:ietf:params:scim:api:messages:2.0:PatchOp" +``` + ## MAP_OAUTH_ISSUE_ENDPOINT mAP Core Authorization Server のクライアント認証エンドポイントの URL パス。 クライアントIDおよびシークレットの取得に使用される。 +#### シグネチャ +```python[const.py] +MAP_OAUTH_ISSUE_ENDPOINT: str +``` + +#### 値 + +```python +"/oauth/sslauth/issue.php" +``` + ## MAP_OAUTH_AUTHORIZE_ENDPOINT mAP Core Authorization Server の認可エンドポイントの URL パス。 OAuth2.0 の認可コードの取得に使用される。 +#### シグネチャ +```python[const.py] +MAP_OAUTH_AUTHORIZE_ENDPOINT: str +``` + +#### 値 + +```python +"/oauth/shib/authrequest.php" +``` + ## MAP_OAUTH_TOKEN_ENDPOINT mAP Core Authorization Server のトークンエンドポイントの URL パス。 アクセストークンおよびリフレッシュトークンの取得に使用される。 +#### シグネチャ +```python[const.py] +MAP_OAUTH_TOKEN_ENDPOINT: str +``` + +#### 値 + +```python +"/oauth/token.php" +``` + ## MAP_USERS_ENDPOINT mAP Core API V2 のユーザーリソースエンドポイントの URL パス。 ユーザー情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_USERS_ENDPOINT: str +``` + +#### 値 + +```python +"/api/v2/Users" +``` + + +## MAP_EXIST_EPPN_ENDPOINT +mAP Core API V2 の ePPN 存在確認エンドポイントの URL パス。 +ePPN に基づいてユーザーの存在確認に使用される。 + +#### シグネチャ +```python[const.py] +MAP_EXIST_EPPN_ENDPOINT: str +``` + +#### 値 + +```python +"/api/v2/Existeppn" +``` + ## MAP_GROUPS_ENDPOINT mAP Core API V2 のグループリソースエンドポイントの URL パス。 グループ情報の取得や操作に使用される。 +#### シグネチャ +```python[const.py] +MAP_GROUPS_ENDPOINT: str +``` + +#### 値 + +```python +"/api/v2/Groups" +``` + ## MAP_SERVICES_ENDPOINT mAP Core API V2 のサービスリソースエンドポイントの URL パス。 サービス情報の取得や操作に使用される。 + +#### シグネチャ +```python[const.py] +MAP_SERVICES_ENDPOINT: str +``` + +#### 値 + +```python +"/api/v2/Services" +``` diff --git a/docs/nuxt.config.ts b/docs/nuxt.config.ts index ea8997e8..b36f7a55 100644 --- a/docs/nuxt.config.ts +++ b/docs/nuxt.config.ts @@ -15,6 +15,7 @@ export default defineNuxtConfig({ highlight: { theme: { default: 'github-light', + light: 'github-light', dark: 'github-dark', }, langs: ['python'], From fa4ca37f6aa5a12f0911bba0ef952290e89c4950 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Sat, 10 Jan 2026 02:03:13 +0000 Subject: [PATCH 008/173] docs: add detailed entity models for mAP --- .../01.detailed/03.entities/.navigation.yml | 2 + .../01.detailed/03.entities/01.map-service.md | 150 +++++++++++++ .../01.detailed/03.entities/02.map-group.md | 204 ++++++++++++++++++ .../01.detailed/03.entities/03.map_user.md | 149 +++++++++++++ .../01.detailed/03.entities/04.map_error.md | 35 +++ 5 files changed, 540 insertions(+) create mode 100644 docs/content/01.detailed/03.entities/.navigation.yml create mode 100644 docs/content/01.detailed/03.entities/01.map-service.md create mode 100644 docs/content/01.detailed/03.entities/02.map-group.md create mode 100644 docs/content/01.detailed/03.entities/03.map_user.md create mode 100644 docs/content/01.detailed/03.entities/04.map_error.md diff --git a/docs/content/01.detailed/03.entities/.navigation.yml b/docs/content/01.detailed/03.entities/.navigation.yml new file mode 100644 index 00000000..de2e6d71 --- /dev/null +++ b/docs/content/01.detailed/03.entities/.navigation.yml @@ -0,0 +1,2 @@ +title: エンティティ + diff --git a/docs/content/01.detailed/03.entities/01.map-service.md b/docs/content/01.detailed/03.entities/01.map-service.md new file mode 100644 index 00000000..0b181e6c --- /dev/null +++ b/docs/content/01.detailed/03.entities/01.map-service.md @@ -0,0 +1,150 @@ +--- +title: entities.map_service +description: mAP Core API V2 の Service リソースに対応するスキーマおよびエンティティモデルを提供する。 +--- + +## MapService +mAP Core API V2 の Service リソースに対応するモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + +#### シグネチャ +```python[map_service.py] +class MapService(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| -------------- | ------------------------------------------ | ---------------------------------------- | +| schemas | list\[str] | リソースがサポートするスキーマのリスト | +| id | str | サービス ID | +| service_name | str | サービス名 | +| service_url | str | サービスの URL | +| suspended | bool | サービスが休止中かどうか | +| meta | [Meta](#meta) | サービスに関連するメタデータ | +| entity_id | list\[[ServiceEntityID](#serviceentityid)] | サービスに紐づくエンティティ ID のリスト | +| administrators | list\[[Administrator](#administrator)] | サービス管理者のリスト | +| groups | list\[[Group](#group)] | サービスに関連するグループのリスト | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は Service リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `schemas` 属性の値は読み取り専用であり、 + 定数 [`MAP_SERVICE_SCHEMA`](../02.foundation/03.const.md#map_service_schema) をただ一つ含むリストである。 +- `meta` 属性は読み取り専用である。 + + +## Meta +mAP Core API V2 の Service リソースの `meta` フィールドに対応するモデル定義。 +[`MapService`](#mapservice) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_service.py] +class Meta(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | --------------------- | +| resource_type | Literal\["Service"] | リソースタイプ名 | +| created | datetime | リソース作成日時 | +| last_modified | datetime | リソース最終更新日時 | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は Service リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `resource_type` 属性の値は定数 `"Service"` に固定される。 +- `created` および `last_modified` 属性は ISO 8601 形式の日時文字列としてシリアライズされる。 +- すべてのフィールドは読み取り専用である。 + + +## ServiceEntityID +mAP Core API V2 の Service リソースの `entityId` フィールドの要素に対応するモデル定義。 +[`MapService`](#mapservice) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_service.py] +class ServiceEntityID(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| value | str | エンティティ ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + + +## Administrator +mAP Core API V2 の Service リソースの `administrators` フィールドの要素に対応するモデル定義。 +[`MapService`](#mapservice) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_service.py] +class Administrator(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | ---------------------------- | +| ref | str | ユーザーリソースの参照先 URL | +| display | str | 管理者ユーザーの表示名 | +| value | str | 管理者ユーザーの ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は Service リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 + + +## Group +mAP Core API V2 の Service リソースの `groups` フィールドの要素に対応するモデル定義。 +[`MapService`](#mapservice) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_service.py] +class Group(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | ---------------------------- | +| ref | str | グループリソースの参照先 URL | +| display | str | グループの表示名 | +| value | str | グループの ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は Service リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 diff --git a/docs/content/01.detailed/03.entities/02.map-group.md b/docs/content/01.detailed/03.entities/02.map-group.md new file mode 100644 index 00000000..5b545058 --- /dev/null +++ b/docs/content/01.detailed/03.entities/02.map-group.md @@ -0,0 +1,204 @@ +--- +title: entities.map_group +description: mAP Core API V2 の Group リソースに対応するスキーマおよびエンティティモデルを提供する。 +--- + +## MapGroup +mAP Core API V2 の Group リソースに対応するモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + +#### シグネチャ +```python[map_group.py] +class MapGroup(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ---------------------- | --------------------------------------- | -------------------------------------- | +| schemas | list[Schema] | リソースがサポートするスキーマのリスト | +| id | str | グループ ID | +| external_id | str | グループの外部識別子 | +| display_name | str | グループの表示名 | +| public | bool | グループの公開状態 | +| description | str | グループの説明 | +| suspended | bool | グループが休止中かどうか | +| member_list_visibility | Literal\["Public", "Private", "Hidden"] | メンバーリストの可視性 | +| meta | [Meta](#meta) | グループに関連するメタデータ | +| members | list\[[Member](#member)] | グループのメンバーのリスト | +| administrators | list\[[Administrator](#administrator)] | グループ管理者のリスト | +| services | list\[[Service](#service)] | グループに関連するサービスのリスト | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は Group リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `schemas` 属性の値は読み取り専用であり、 + 定数 [`MAP_GROUP_SCHEMA`](../02.foundation/03.const.md#map_group_schema) をただ一つ含むリストである。 +- `meta` 属性は読み取り専用である。 + + +## Meta +mAP Core API V2 の Group リソースの `meta` フィールドに対応するモデル定義。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +class Meta(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | --------------------- | +| resource_type | Literal\["Group"] | リソースタイプ名 | +| created | datetime | リソース作成日時 | +| last_modified | datetime | リソース最終更新日時 | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は Group リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `resource_type` 属性の値は定数 `"Group"` に固定される。 +- `created` および `last_modified` 属性は ISO 8601 形式の日時文字列としてシリアライズされる。 +- すべてのフィールドは読み取り専用である。 + + +## Member +mAP Core API V2 の Group リソースの `members` フィールドの要素に対応する型エイリアス。 +[`MemberUser`](#memberuser) 及び [`MemberGroup`](#membergroup) のユニオン型として定義される。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +type Member = MemberUser | MemberGroup +``` + +#### 注釈 +- `MemberUser` および `MemberGroup` クラスは `type` 属性の値を型識別子(discriminator)に使用する。 + +## MemberUser +mAP Core API V2 の Group リソースの `members` フィールドの要素のうち所属ユーザーに対応するモデル定義。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +class MemberUser(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | -------------------------- | +| ref | str | ユーザーリソースの参照 URL | +| type | Literal\["User"] | メンバータイプ名 | +| display | str | ユーザーの表示名 | +| value | str | ユーザー ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は Group リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 +- `type` 属性の値は定数 `"User"` に固定される。 + + +## MemberGroup +mAP Core API V2 の Group リソースの `members` フィールドの要素のうち下位グループに対応するモデル定義。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +class MemberGroup(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 +| 名前 | 型 | 説明 | +| ------------ | ------------------- | -------------------------- | +| ref | str | グループリソースの参照 URL | +| type | Literal\["Group"] | メンバータイプ名 | +| display | str | グループの表示名 | +| value | str | グループ ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は Group リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 +- `type` 属性の値は定数 `"Group"` に固定される。 + + +## Administrator +mAP Core API V2 の Group リソースの `administrators` フィールドの要素に対応するモデル定義。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +class Administrator(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | ---------------------------- | +| ref | str | ユーザーリソースの参照先 URL | +| display | str | 管理者ユーザーの表示名 | +| value | str | 管理者ユーザーの ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は Group リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 + + +## Service +mAP Core API V2 の Group リソースの `services` フィールドの要素に対応するモデル定義。 +[`MapGroup`](#mapgroup) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_group.py] +class Service(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ---------------------- | ------------------- | ---------------------------------------------------- | +| ref | str | サービスリソースの参照 URL | +| display | str | サービスの表示名 | +| value | str | サービス ID | +| administrator_of_group | int | サービスがグループの管理者であるかどうかを示すフラグ | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は User リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `ref` 属性は Group リソースにおける `$ref` フィールドに対応する。また、シリアライズに含めない。 diff --git a/docs/content/01.detailed/03.entities/03.map_user.md b/docs/content/01.detailed/03.entities/03.map_user.md new file mode 100644 index 00000000..8bdf4d01 --- /dev/null +++ b/docs/content/01.detailed/03.entities/03.map_user.md @@ -0,0 +1,149 @@ +--- +title: entities.map_user +description: mAP Core API V2 の User リソースに対応するスキーマおよびエンティティモデルを提供する。 +--- + +## MapUser +mAP Core API V2 の User リソースに対応するモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + +#### シグネチャ +```python[map_user.py] +class MapUser(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| -------------------------- | ---------------------- | -------------------------------------- | +| schemas | list\[str] | リソースがサポートするスキーマのリスト | +| id | str | ユーザー ID | +| external_id | str | ユーザーの外部識別子 | +| user_name | str | ユーザー名 | +| preferred_language | str | 優先言語 | +| meta | [Meta](#meta) | ユーザーに関連するメタデータ | +| edu_person_principal_names | list\[[EPPN](#eppn)] | ユーザーの EPPN のリスト | +| emails | list\[[Email](#email)] | ユーザーのメールアドレスのリスト | +| groups | list\[[Group](#group)] | ユーザーが所属するグループのリスト | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は User リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `schemas` 属性の値は読み取り専用であり、 + 定数 [`MAP_USER_SCHEMA`](../02.foundation/03.const.md#map_user_schema) をただ一つ含むリストである。 +- `meta` 属性は読み取り専用である。 + + +## Meta +mAP Core API V2 の User リソースの `meta` フィールドに対応するモデル定義。 +[`MapUser`](#mapuser) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_user.py] +class Meta(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | --------------------------- | +| resource_type | Literal\["User"] | リソースタイプ名 | +| created | datetime | リソースの作成日時 | +| last_modified | datetime | リソースの最終更新日時 | +| created_by | str | リソース作成者のユーザー ID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は User リソースのフィールドに camelCase ⇔ snake_case で対応している。 +- `resource_type` 属性の値は定数 `"User"` に固定される。 +- `created` および `last_modified` 属性は ISO 8601 形式の日時文字列としてシリアライズされる。 +- すべてのフィールドは読み取り専用である。 + + +## EPPN +mAP Core API V2 の User リソースの `eduPersonPrincipalNames` フィールドの要素に対応するモデル定義。 +[`MapUser`](#mapuser) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_user.py] +class EPPN(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | --------------------- | +| value | str | EPPN の値 | +| idp_entity_id | str | IdP のエンティティID | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- すべての属性は eduPersonPrincipalNames フィールドのサブフィールドに camelCase ⇔ snake_case で対応している。 + + +## Email +mAP Core API V2 の User リソースの `emails` フィールドの要素に対応するモデル定義。 +[`MapUser`](#mapuser) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_user.py] +class Email(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| value | str | メールアドレスの値 | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + + +## Group +mAP Core API V2 の User リソースの `groups` フィールドの要素に対応するモデル定義。 +[`MapUser`](#mapuser) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[map_user.py] +class Group(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | -------------------------- | +| value | str | グループ ID | +| ref | str | グループリソースの参照 URL | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `ref` 属性は User リソースにおける `$ref` フィールドに対応する。 diff --git a/docs/content/01.detailed/03.entities/04.map_error.md b/docs/content/01.detailed/03.entities/04.map_error.md new file mode 100644 index 00000000..e65f637f --- /dev/null +++ b/docs/content/01.detailed/03.entities/04.map_error.md @@ -0,0 +1,35 @@ +--- +title: entities.map_error +description: mAP Core API V2 の Error レスポンスに対応するスキーマおよびエンティティモデルを提供する。 +--- + +## MapError +mAP Core API V2 のエラーレスポンスに対応するモデル定義。 +レスポンスボディの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + +#### シグネチャ +```python[map_error.py] +class MapError(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ---------- | ----------- | -------------------------------------- | +| schemas | list\[str] | リソースがサポートするスキーマのリスト | +| status | str | HTTP ステータスコード | +| scim_type | str | SCIM エラータイプ | +| detail | str | エラーの詳細説明 | +| error_code | int \| None | エラーコード(未使用) | + +#### 注釈 +- すべての属性は Error レスポンスのフィールドに camelCase ⇔ snake_case で対応している。 +- `schemas` 属性の値は読み取り専用であり、 + 定数 [`MAP_ERROR_SCHEMA`](../02.foundation/03.const.md#map_error_schema) をただ一つ含むリストである。 +- すべてのフィールドは読み取り専用である。 From 7a8af3cf6c165bdfd5dd77e87637018c8f743af7 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Thu, 15 Jan 2026 06:30:14 +0000 Subject: [PATCH 009/173] docs: add database table definition --- docs/app/components/DocsHeader.vue | 2 +- docs/app/pages/index.vue | 2 +- docs/content.config.ts | 8 ++++++++ docs/content/03.db/.navigation.yml | 2 ++ docs/content/03.db/index.md | 14 ++++++++++++++ 5 files changed, 26 insertions(+), 2 deletions(-) create mode 100644 docs/content/03.db/.navigation.yml create mode 100644 docs/content/03.db/index.md diff --git a/docs/app/components/DocsHeader.vue b/docs/app/components/DocsHeader.vue index 2df35632..4b5090d6 100644 --- a/docs/app/components/DocsHeader.vue +++ b/docs/app/components/DocsHeader.vue @@ -4,7 +4,7 @@ import type { Collections } from '@nuxt/content' import type { NavigationMenuItem } from '@nuxt/ui' const route = useRoute() -const categories: (Exclude)[] = ['detailed', 'api'] +const categories: (Exclude)[] = ['detailed', 'api', 'db'] const categoryData = await Promise.all( categories.map(category => useAsyncData(`links-${category}`, () => { diff --git a/docs/app/pages/index.vue b/docs/app/pages/index.vue index 771f11bc..529ce7ae 100644 --- a/docs/app/pages/index.vue +++ b/docs/app/pages/index.vue @@ -1,7 +1,7 @@ + + diff --git a/docs/app/components/DocsHeader.vue b/docs/app/components/DocsHeader.vue deleted file mode 100644 index 4b5090d6..00000000 --- a/docs/app/components/DocsHeader.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - - diff --git a/docs/app/layouts/default.vue b/docs/app/layouts/default.vue index 4dfd7f6a..3bc5f34b 100644 --- a/docs/app/layouts/default.vue +++ b/docs/app/layouts/default.vue @@ -1,4 +1,6 @@ @@ -40,9 +37,7 @@ const { data: files } = useLazyAsyncData(() => `search-${collection.value}`, () diff --git a/docs/public/logo.png b/docs/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..0f421286ea76764f5f698d8e3ca7918d1c5e2395 GIT binary patch literal 4891 zcma)Ac|26@+dng7W*9TJ(HMh_C6s4uDO8xTH6kR-SR0ZiMhV%{W5yOmjitp_$JUcV zWXY16v4rq6NfN3l@=%gQX`OdGzc;;qyzl4pp3mo;`#$Gf*SW9zy07cHzvr9b;95SkZjUPQFOpom8Hr{l(pgoCQyIIE zpNZh(l$$n9Z%0t6O(w)i-O2j4k`Vzo+V}n2_6bAJ`8u;+6 z`lR6D*BQfzHxiCum%kOTEy(J8+d_qX_CVvPQOP9ZY;sp^z%Q?q2C0mR1V9w4X|Kj`gU0lal`#6*UxfYpWZawrUf@m4kHJtSgogBVDH-+Z^q`uGv^Fw) zoK10kD&_n;0^(~n-t>ktK-{Vw3h734lvv_u+=cuy77l3{j{AWlgigBRTNMu$n+^Fl;!5qW_>=t z?gm0R6irF3fk~Y}iA_imB6PU!I+V8a*zj`7pH`_G^@uqw=XGKz$Hnku%}A17%^ovwPY!6NWBNB=u`wTAxgCL(AICgAK%v!_(1>7bZM8*%C*^3%5OH?jNW5OSshxk zDr2326FdM?mGZ*CeT0DVaB|$xbwwyly#4d5C}?}{U}9UVzz$<}W{tLgOnd(N%qM=} zQ9n)_r?pCYx2uTh~{Z>K&jB9piM)S$KQZ$Pz%u3X+gcr>_cSIj^wSKdF=7`$C zXjT1DmR{V{m(ODApHCzi?fsGc&kIYdK`(=0lnEmg>9YaSpEm<9*ue-sJ5jUl90hU) ziYYrM7FPW7&5-OL100?xTd?5fJEL=li4~`jgF&XxY_A$vBftQxD8`Wt!(c>l+-{Z! zPA65LQ6$xB1Q-X{@ehmM5iLu4WEpZ&FCwrx%fuF?&~37PzLIY~u$Pk0w{kq14Sy9~ z^_EqWQ>(inN8DZ<^~_=;;Dp=2wngO~;$Rbgk?WfW$>s9FjA?W3&`s#N6$pPs*>+yN!XOzEq7cl7UbwgevdQ3k zNqs{~^biZB74=d4wL;F4Ov|#YTXypKd-4LB+i9h>T|oWN)3k;^55bIi!<}-2m4Dqd zecWH$uJzcQ4(R5WJnNUFLc(QES>`Y_sluNc5AI4cZe@zGs@o*Izkh|amL_W8zLc0w zr-vSbd+m4R*YYYZaQ!gh@e zYrJ2+>>y242A1$w6-dbq9{M!^7NNKx>TsT*CeEzy%`2D@aQ*I_aD2r#8Q_r=_ ziA)w5Vz+P1F6Sd#)T@$q4rlAj0s7&}U8#g7vCrA*(5wJZ;i;A9%w!1s2zl@6>)kec zN+4kseqHLeZvB9>$w33Or_5d*nK$h$)RuVtZOBHZMAnJiv9}$+Q@O(jR9hDut5W~Z z`gthHjbUjvuiknb*fht~G{bU&fegskSX3$m7K>;!As$)Z^XMI#xL;TPDd5IpqqKA` zk-=p$Gj{L(z+_ym25j3)XqeZ_`zIx{o5Ye{?UQNUE#s+1xmBVt6T8ns>X1t}Ry@)q zO1DfSBTf>RRBbr|0LFocIhA-tYoeJDR=d*&wSGtqL7b_0X79M_{R8f6dFoo3wu zI>1nn9XGm^x)SUn!Gx7Rp%I#v#CMs1?N3+EA7Cv)H%?B9qTm-&>Ud(MQF%_R;GJ(& zpZIlUMg-_j7~ciE%_@R8y7ri@Ap7Ttm#UovNs>`aGln@y78BX^4gFh0705vKfnOR!7aP+IDm+dDkL zY~M%D7GZ-6-zDEGF(cHhzA45}UC9ZVN}wUqcFPlC9DISP*`)H)DVh~$%)k{o99GZ@NA3V|#}>y6TxJR|llS6zRtlRCh=LVUgN+1Xw~F zPjS_Hp5;yKuw4iE&9kbZPgysvcV3}yO4+~Qc6t3XCj^z(0POS~$JS|KJTZ>;wy6&K z8zs)*=1aE{Uz>j8xyd90VH2IdaB=CU!C65HuxU7Uh__!mV>D#D#qXq6Bjj@dPNEq@{GSchkuKR5Tw zObZ6(8S6-_%@ljWir#VQScoaa*uT~tt!h6}bmo!Y6tkzqs$VLUK;)4|hcQzp;9aeS z4JFt$FO&weKP}95AI@X_V=uhR(*9vNC;!xHhPajaZHS`SgCLcH3xx7#4^{agXP6tz;DnNV`B5o}#PR`GGO_H{DRJLt>#I&5qSP1iXIC<4 zQ%aQTy)$T?rl)qTgV9!=pZ14tT$~JjOg4Em=^fd?&MRvusiyeG_;MQNQ3dzHTTkbu zR`N5dzk!C%%t@{d)VJCpiDWmU(maZa3-qos{UJdr&+w|sBq>wY!Jb>KCwP=&ir4m& zj#jfDv)%Ksp|2I?1*vbFRpE%cM{3=Fm|{K5>9Z<`ZK_#3xGBjgpF z;ePSQ!wH5y#bm!we*O|Qbr{;Pyj`g_v%YU~TXLoy2pO-G2J0+b-r~+~r}y}j!Ty|) zeV)bdcdmW02&<%N3CvU`HS#6xKeRe%MuutJ!K#6Za!>uh4K83`wb)XlX9{rD72D@~ zT-Lz^Yg~hD&BcZC5Tw|n>*BkbAq{mZo=4J04Z0f@Roo0&1NWk3&6pI+LtpZLLAKk) zhEknGw5?ZL{gR%!qo-50ZaI>&>qw4<`;D*ua^vihWdi#Tv?bz1;=vH5=TIl5?*@;Z zEmS{`JCBa!;0V(LDs!7^eGE(#$Mw>_>&@r(wb-y1w%(J;_edz4*)#?5&el(a7*{f@ zDCej821ZSng!}Bj;|_HBfat<4**qu9V%bTVCnNelu2nx+Sw4rjQGItd+i-S>aJA}> z)_7=*-3Q*uImg)ej`(f8)p?5| zASx7gJb7I{ovp0^e$%eBsDb&tb6!+8(Ij6bcY0#`tS{_kg)A1gw{7sz}^YCqQ8P6i6#%Fn(tDJ8okb+DY_VNwrD2T0wfvu#);=8k^_)$a*`)7 zMyg%f@42+KEKzp@vu_5-U)B!T5G53WI#sfdn>>W0NUTq9SCU7Xia!frVaW;{oa@ZQ zSyRMphM89~%|M_XE&|x{e7J;Bd)5Y)Kl0+S(O#Kh> za>TW0XalkAs^&>(DavCeAbAuqodE$m1|U0coy==5Oab)Fqn=5y4=N%p?<|R#za;_N z)NCYx)EFY$w%rCcbBB7ZMRM$o#yE+ur)ri8>;02|$2sAUWS$50P#354ymCx#G!(gJ z+4lY~OMxbp8$w}Gd-M=2Clv8P3a{WTs5N(-0f_-lX_E~zg+OIXp>xG{G)kiJZw|yb zSuBTU?Vo}RF0Zmkq&k^MINypS;-qp<^r|-P#t59$RlzT~Ui-6{#IpH#^&M(zg(QJ4`9x zowKq4tB@rxB;Gk2Z*9SJ$DZ|4O)069P%HWmB|rlW$*3)`nTv$F`b!}94}c`(YDp9; z6Jzq>6?F9(K`*2>M#Ky3Av>%A74>W6{p7j#zT_^nl#!`;b%oRpQS=N!LBS$60&+DP za+&3z{q0{4X6ZNFQ*R|FL->!6#p*?G5aXU!F4-SRUj?>Bp5A*TxHu0dZgk$~e+iz8 z!0&8mAzN=6rJtrAeWoS>TQIEnOQG#((;1M|B;r@(^=DF-yD6~^#8q-e?0;yrk*yK@ zd7Y+n0)_D1QUVzNfO4-%xugJ}kjhS`-n!Cx*h!VtwQ8aL+KQI$Vs8}@Ng@kiQfok^ t^wS5*Dr=Lp$G~7s^8ZIL{V!55VBq7Kh&|e`s#bA Date: Thu, 15 Jan 2026 10:25:48 +0000 Subject: [PATCH 011/173] docs: add service_settings table --- docs/content/03.db/01.service_settings.md | 36 +++++++++++++++++++++++ docs/content/03.db/index.md | 11 +++++++ 2 files changed, 47 insertions(+) create mode 100644 docs/content/03.db/01.service_settings.md diff --git a/docs/content/03.db/01.service_settings.md b/docs/content/03.db/01.service_settings.md new file mode 100644 index 00000000..d9d80144 --- /dev/null +++ b/docs/content/03.db/01.service_settings.md @@ -0,0 +1,36 @@ +--- +title: 動的サービス設定情報 +description: 動的なサービス設定情報を保持する。 +--- + +## service_settings テーブル +当システムの動的なサービス設定情報を保持するテーブル。キー・バリュー形式で設定情報を保存する。 + +#### カラム定義 + +| カラム名 | データ型 | NOT NULL | デフォルト値 | 説明 | +| -------- | ------------------------ | -------- | ------------------------ | -------------------------- | +| key | character varying(100) | Yes | | ユニークな設定キー | +| updated | timestamp with time zone | Yes | `timezone('UTC', now())` | 最終更新日時 | +| value | jsonb | Yes | | 設定値(JSONオブジェクト) | + +#### インデックス + +| インデックス名 | 対象カラム | 主キー | ユニーク | +| ------------------- | ---------- | ------ | -------- | +| pk_service_settings | key | Yes | Yes | + +#### 制約 + +| 制約名 | 対象カラム | 制約種別 | +| ------------------- | ---------- | -------- | +| pk_service_settings | key | 主キー | + +#### 外部キー +(なし) + +#### 外部キー(PK側) +(なし) + +#### 備考 +- レコード更新時に `updated` はアプリケーション側(Python/ORM)で自動更新される。 diff --git a/docs/content/03.db/index.md b/docs/content/03.db/index.md index e719e96d..9ad0e2d3 100644 --- a/docs/content/03.db/index.md +++ b/docs/content/03.db/index.md @@ -12,3 +12,14 @@ headline: JAIRO Cloud Groups Manager ## 前提条件 当システムは、データベースとして PostgreSQL を利用することを前提とする。 + + +## 制約名命名規則 + +| 制約種別 | 命名規則 | +| ------------ | ----------------------------------------------------------- | +| 主キー | pk_%(table_name)s | +| ユニーク | uq_%(table_name)s_%(column_0_name)s | +| インデックス | ix_%(column_0_label)s | +| チェック | ck_%(table_name)s_%(constraint_name)s | +| 外部キー | fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s | From 25cdb3d691779627e4ca59dca50c6a5bb3ae3f97 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:15:05 +0000 Subject: [PATCH 012/173] docs: add mapper model for service settings --- .../content/01.detailed/07.db/.navigation.yml | 1 + .../01.detailed/07.db/02.service_settings.md | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 docs/content/01.detailed/07.db/.navigation.yml create mode 100644 docs/content/01.detailed/07.db/02.service_settings.md diff --git a/docs/content/01.detailed/07.db/.navigation.yml b/docs/content/01.detailed/07.db/.navigation.yml new file mode 100644 index 00000000..6e6ab19e --- /dev/null +++ b/docs/content/01.detailed/07.db/.navigation.yml @@ -0,0 +1 @@ +title: データアクセス diff --git a/docs/content/01.detailed/07.db/02.service_settings.md b/docs/content/01.detailed/07.db/02.service_settings.md new file mode 100644 index 00000000..04c9691d --- /dev/null +++ b/docs/content/01.detailed/07.db/02.service_settings.md @@ -0,0 +1,40 @@ +--- +title: db.service_settings +description: 動的なサービス設定情報のデータベース保存に用いるマッパーモデルを提供する。 +--- + +## ServiceSettings +当システムの動的なサービス設定情報のデータベース保存に用いるマッパーモデル定義。 +SQLAlchemy ORM を用いてデータベースの `service_settings` テーブルとマッピングされる。 +テーブル定義書 [service_settings テーブル](../../03.db/01.service_settings.md#service_settings-テーブル) を参照。 + +#### シグネチャ +```python[service_settings.py] +class ServiceSettings(db.Model): +``` + +#### 依存するライブラリ +- **SQLAlchemy**: ORM として使用。 + +#### 継承元 +`SQLAlchemy.Model` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ----------------------------- | -------- | ------------------------------ | +| __tablename__ | str | テーブル名。`service_settings` | +| key | str | 設定情報のキー | +| updated | datetime | レコードの最終更新日時 | +| value | dict | 設定情報の JSON データ | + +#### マッピング + +| カラム名 | データ型 | 主キー | NULL 許容 | デフォルト値 | ON UPDATE | +| -------- | ---------------------------------- | ------ | --------- | ------------ | ---------- | +| key | sqlalchemy.String(100) | Yes | | | | +| updated | sqlalchemy.DateTime(timezone=True) | | | 協定世界時 | 協定世界時 | +| value | sqlalchemy.ext.mutable.MutableDict | | | | | + +#### 備考 +- `value` カラムは PostgreSQL で JSONB 型を使用する。 From 494fe6f49aad9fcfcc0bca698f8d3964c79285d2 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:18:24 +0000 Subject: [PATCH 013/173] docs: add app exception --- .../01.detailed/02.foundation/05.ext.md | 2 +- .../01.detailed/02.foundation/07.exc.md | 135 ++++++++++++++++++ docs/content/01.detailed/index.md | 4 +- docs/content/02.api/endpoints.md | 2 +- docs/content/02.api/index.md | 2 +- 5 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 docs/content/01.detailed/02.foundation/07.exc.md diff --git a/docs/content/01.detailed/02.foundation/05.ext.md b/docs/content/01.detailed/02.foundation/05.ext.md index 89121be6..2e8757cf 100644 --- a/docs/content/01.detailed/02.foundation/05.ext.md +++ b/docs/content/01.detailed/02.foundation/05.ext.md @@ -4,7 +4,7 @@ description: Flask アプリケーションの拡張機能を提供する。 --- ## JAIROCloudGroupsManager -当機能の全体的な初期化処理を行い、Flask 拡張として登録するクラス。 +当システムの全体的な初期化処理を行い、Flask 拡張として登録するクラス。 #### シグネチャ ```python[ext.py] diff --git a/docs/content/01.detailed/02.foundation/07.exc.md b/docs/content/01.detailed/02.foundation/07.exc.md new file mode 100644 index 00000000..a79bbbbb --- /dev/null +++ b/docs/content/01.detailed/02.foundation/07.exc.md @@ -0,0 +1,135 @@ +--- +title: exc +description: 当システムのビジネスロジックにおける例外定義を提供する。 +--- + +## JAIROCloudGroupsManagerError +当システムのビジネスロジックにおける基底例外クラス。すべてのカスタム例外はこのクラスを継承する。 + +#### シグネチャ +```python[exc.py] +class JAIROCloudGroupsManagerError(Exception): +``` + +#### 継承元 +`Exception` + + +## ConfigurationError +サーバー設定に関する例外クラス。 + +#### シグネチャ +```python[exc.py] +class ConfigurationError(JAIROCloudGroupsManagerError): +``` + +#### 継承元 +`JAIROCloudGroupsManagerError` + + +## CertificatesError +証明書の操作に関する例外クラス。 + +#### シグネチャ +```python[exc.py] +class CertificatesError(JAIROCloudGroupsManagerError): +``` + +#### 継承元 +`JAIROCloudGroupsManagerError` + + +## ServiceSettingsError +動的サービス設定の操作に関する基底例外クラス。 + +#### シグネチャ +```python[exc.py] +class ServiceSettingsError(JAIROCloudGroupsManagerError): +``` + +#### 継承元 +`JAIROCloudGroupsManagerError` + + +## CredentialsError +クライアント認証情報の操作に関する例外クラス。 + +#### シグネチャ +```python[exc.py] +class CredentialsError(ServiceSettingsError): +``` + +#### 継承元 +`ServiceSettingsError` + + +## OAuthTokenError +OAuth トークンの操作に関する例外クラス。 + +#### シグネチャ +```python[exc.py] +class OAuthTokenError(ServiceSettingsError): +``` + +#### 継承元 +`ServiceSettingsError` + + +## DatabaseError +データベース操作に関する例外クラス。 + +#### シグネチャ +```python[exc.py] +class DatabaseError(JAIROCloudGroupsManagerError): +``` + +#### 継承元 +`JAIROCloudGroupsManagerError` + + +## ApiClientError +API クライアントの操作に関する基底例外クラス。 + +#### シグネチャ +```python[exc.py] +class ApiClientError(JAIROCloudGroupsManagerError): +``` + +#### 継承元 +`JAIROCloudGroupsManagerError` + + +## ResourceInvalid +API クライアント操作において、リソースが不正であることを示す例外クラス。 + +#### シグネチャ +```python[exc.py] +class ResourceInvalid(ApiClientError): +``` + +#### 継承元 +`ApiClientError` + + +## ResourceNotFound +API クライアント操作において、リソースが見つからないことを示す例外クラス。 + +#### シグネチャ +```python[exc.py] +class ResourceNotFound(ApiClientError): +``` + +#### 継承元 +`ApiClientError` + + +## UnexpectedResponseError +API クライアント操作において、予期しないレスポンスが返されたことを示す例外クラス。 + +#### シグネチャ +```python[exc.py] +class UnexpectedResponseError(ApiClientError): +``` + +#### 継承元 +`ApiClientError` diff --git a/docs/content/01.detailed/index.md b/docs/content/01.detailed/index.md index 1337797f..2a816b01 100644 --- a/docs/content/01.detailed/index.md +++ b/docs/content/01.detailed/index.md @@ -7,11 +7,11 @@ headline: JAIRO Cloud Groups Manager --- ## 本書について -本書は、JAIRO Cloud Groups Manager(以降、当機能)のサーバーサイドの詳細設計を記述したものである。 +本書は、JAIRO Cloud Groups Manager(以降、当システム)のサーバーサイドの詳細設計を記述したものである。 ## 前提条件 -本機能は、以下のサービスに依存する。 +当システムは、以下のサービスに依存する。 - mAP Core Authorization Server - mAP Core API V2 diff --git a/docs/content/02.api/endpoints.md b/docs/content/02.api/endpoints.md index e08554f4..8f6007db 100644 --- a/docs/content/02.api/endpoints.md +++ b/docs/content/02.api/endpoints.md @@ -1,6 +1,6 @@ --- title: エンドポイント一覧 -description: 当機能が提供する API のエンドポイント一覧。 +description: 当システムが提供する API のエンドポイント一覧。 headline: API 仕様書 --- diff --git a/docs/content/02.api/index.md b/docs/content/02.api/index.md index a88dd72d..e9a895bd 100644 --- a/docs/content/02.api/index.md +++ b/docs/content/02.api/index.md @@ -7,7 +7,7 @@ headline: JAIRO Cloud Groups Manager --- ## 本書について -本書は、JAIRO Cloud Groups Manager(以降、当機能)が提供する API の仕様を記述したものである。 +本書は、JAIRO Cloud Groups Manager(以降、当システム)が提供する API の仕様を記述したものである。 ## 注意事項 From bbe3fc7af11ce1171e23a5e0dcc389ac0f3081ca Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:28:47 +0000 Subject: [PATCH 014/173] docs: update typography --- .../01.detailed/02.foundation/02.config.md | 10 ++-- .../{06.celery.md => 06.celery-app.md} | 2 +- docs/content/01.detailed/04.api/05.users.md | 50 +++++++++---------- 3 files changed, 31 insertions(+), 31 deletions(-) rename docs/content/01.detailed/02.foundation/{06.celery.md => 06.celery-app.md} (96%) diff --git a/docs/content/01.detailed/02.foundation/02.config.md b/docs/content/01.detailed/02.foundation/02.config.md index b64111ac..228ecab6 100644 --- a/docs/content/01.detailed/02.foundation/02.config.md +++ b/docs/content/01.detailed/02.foundation/02.config.md @@ -33,7 +33,7 @@ class RuntimeConfig(pydantic_settings.BaseSettings): | POSTGRES | [PostgresConfig](#postgresconfig) | Postgres 設定 | #### プロパティ -これらは `@computed_field` デコレータを使用し、他の属性値から算出される値で属性としてアクセスされる。 +これらは `@computed_field`{lang=python} デコレータを使用し、他の属性値から算出される値で属性としてアクセスされる。 | プロパティ名 | 型 | 説明 | | ----------------------- | --------------------- | ------------------- | @@ -57,8 +57,8 @@ def SQLALCHEMY_DATABASE_URI(self) -> sqlalchemy.engine.URL: ``` #### デコレータ -- `@computed_field`: Pydantic の計算フィールドを定義するデコレータ。 -- `@cached_property`: プロパティの結果をキャッシュするデコレータ。 +- `@computed_field`{lang=python}: Pydantic の計算フィールドを定義するデコレータ。 +- `@cached_property`{lang=python}: プロパティの結果をキャッシュするデコレータ。 #### 処理内容 1. 属性 `POSTGRES` の各値を使用して、SQLAlchemy のデータベース接続 URI を生成する。 @@ -85,8 +85,8 @@ def settings_customise_sources( ``` #### デコレータ -- `@override`: メソッドがスーパークラスのメソッドをオーバーライドしていることを示すデコレータ。 -- `@classmethod`: クラスメソッドを定義するデコレータ。 +- `@override`{lang=python}: メソッドがスーパークラスのメソッドをオーバーライドしていることを示すデコレータ。 +- `@classmethod`{lang=python}: クラスメソッドを定義するデコレータ。 #### 引数 diff --git a/docs/content/01.detailed/02.foundation/06.celery.md b/docs/content/01.detailed/02.foundation/06.celery-app.md similarity index 96% rename from docs/content/01.detailed/02.foundation/06.celery.md rename to docs/content/01.detailed/02.foundation/06.celery-app.md index 487d3ca5..f0f9d28e 100644 --- a/docs/content/01.detailed/02.foundation/06.celery.md +++ b/docs/content/01.detailed/02.foundation/06.celery-app.md @@ -1,5 +1,5 @@ --- -title: celery +title: celery_app description: Celery アプリケーションのエントリーポイントを提供する。 --- diff --git a/docs/content/01.detailed/04.api/05.users.md b/docs/content/01.detailed/04.api/05.users.md index 35e9c513..6bae16b5 100644 --- a/docs/content/01.detailed/04.api/05.users.md +++ b/docs/content/01.detailed/04.api/05.users.md @@ -29,7 +29,7 @@ def get(query: UsersQuery) -> tuple[UsersResult | ErrorResponse, int]: ``` #### エンドポイント -`GET /api/users` +**GET** /api/users #### 依存するライブラリ - **Flask**: Blueprint を使用する。 @@ -37,10 +37,10 @@ def get(query: UsersQuery) -> tuple[UsersResult | ErrorResponse, int]: - **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 #### デコレータ -- `@bp.get`: GET リクエストを処理するエンドポイントを定義する。 -- `@login_required`: ログインを要求する。 -- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 -- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 +- `@bp.get`{lang=python}: GET リクエストを処理するエンドポイントを定義する。 +- `@login_required`{lang=python}: ログインを要求する。 +- `@roles_required`{lang=python}: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 #### 引数 @@ -79,7 +79,7 @@ def post(body: UserDetail) -> tuple[UserDetail, int, dict[str, str]] | tuple[Err ``` #### エンドポイント -`POST /api/users` +**POST** /api/users #### 依存するライブラリ - **Flask**: Blueprint を使用する。 @@ -87,10 +87,10 @@ def post(body: UserDetail) -> tuple[UserDetail, int, dict[str, str]] | tuple[Err - **Flask-Login**: ユーザー認証を行う。 #### デコレータ -- `@bp.post`: POST リクエストを処理するエンドポイントを定義する。 -- `@login_required`: ログインを要求する。 -- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 -- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 +- `@bp.post`{lang=python}: POST リクエストを処理するエンドポイントを定義する。 +- `@login_required`{lang=python}: ログインを要求する。 +- `@roles_required`{lang=python}: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 #### 引数 @@ -135,7 +135,7 @@ def id_get(user_id: str) -> tuple[UserDetail | ErrorResponse, int]: ``` #### エンドポイント -`GET /api/users/` +**GET** /api/users/ #### 依存するライブラリ - **Flask**: Blueprint を使用する。 @@ -143,10 +143,10 @@ def id_get(user_id: str) -> tuple[UserDetail | ErrorResponse, int]: - **Flask-Login**: ユーザー認証を行う。 #### デコレータ -- `@bp.get`: GET リクエストを処理するエンドポイントを定義する。 -- `@login_required`: ログインを要求する。 -- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 -- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 +- `@bp.get`{lang=python}: GET リクエストを処理するエンドポイントを定義する。 +- `@login_required`{lang=python}: ログインを要求する。 +- `@roles_required`{lang=python}: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 #### 引数 @@ -188,7 +188,7 @@ def id_put(user_id: str, body: UserDetail) -> tuple[UserDetail | ErrorResponse, ``` #### エンドポイント -`PUT /api/users/` +**PUT** /api/users/ #### 依存するライブラリ - **Flask**: Blueprint を使用する。 @@ -196,10 +196,10 @@ def id_put(user_id: str, body: UserDetail) -> tuple[UserDetail | ErrorResponse, - **Flask-Login**: ユーザー認証を行う。 #### デコレータ -- `@bp.put`: PUT リクエストを処理するエンドポイントを定義する。 -- `@login_required`: ログインを要求する。 -- `@roles_required`: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 -- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 +- `@bp.put`{lang=python}: PUT リクエストを処理するエンドポイントを定義する。 +- `@login_required`{lang=python}: ログインを要求する。 +- `@roles_required`{lang=python}: ログインユーザーに「システム管理者」または「リポジトリ管理者」のロールを要求する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 #### 引数 @@ -244,7 +244,7 @@ def id_delete(user_id: str) -> tuple[ Literal[""] | ErrorResponse, int]: ``` #### エンドポイント -`DELETE /api/users/` +**DELETE** /api/users/ #### 依存するライブラリ - **Flask**: Blueprint を使用する。 @@ -252,10 +252,10 @@ def id_delete(user_id: str) -> tuple[ Literal[""] | ErrorResponse, int]: - **Flask-Pydantic**: リクエストとレスポンスのバリデーション、シリアライズ・デシリアライズを行う。 #### デコレータ -- `@bp.delete`: DELETE リクエストを処理するエンドポイントを定義する。 -- `@login_required`: ログインを要求する。 -- `@roles_required`: ログインユーザーに「システム管理者」のロールを要求する。 -- `@validate`: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 +- `@bp.delete`{lang=python}: DELETE リクエストを処理するエンドポイントを定義する。 +- `@login_required`{lang=python}: ログインを要求する。 +- `@roles_required`{lang=python}: ログインユーザーに「システム管理者」のロールを要求する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 #### 引数 From 98d3ccb5b895c648481afa01cfd50589536087e9 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:36:41 +0000 Subject: [PATCH 015/173] docs: add service for settings in db --- .../05.services/06.service_settings.md | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/content/01.detailed/05.services/06.service_settings.md diff --git a/docs/content/01.detailed/05.services/06.service_settings.md b/docs/content/01.detailed/05.services/06.service_settings.md new file mode 100644 index 00000000..8e8482b3 --- /dev/null +++ b/docs/content/01.detailed/05.services/06.service_settings.md @@ -0,0 +1,60 @@ +--- +title: services.service_settings +description: 当システムの動的なサービス設定情報の保存および取得を行う機能を提供する。 +--- + +## get_client_credentials +データベースから当システムに紐づく SP のクライアント認証情報を取得する機能を提供する。 + +#### シグネチャ +```python[service_settings.py] +def get_client_credentials() -> ClientCredentials | None: +``` + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------------------------------------------ | ---------------------------------------------------- | +| [ClientCredentials](../03.entities/07.auth.md#clientcredentials) \| None | クライアント認証情報。存在しない場合は None を返す。 | + +#### エラー + +| エラー名 | 説明 | +| --------------------------------------------------------------- | ---------------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CredentialsError](../02.foundation/07.exc.md#credentialserror) | 取得された設定からクライアント認証情報を読み取れない場合。 | + +#### 処理内容 +1. データベースからサービス設定情報を `client_credentials` キーで取得する。 +2. レコードが存在しない場合は `None` を戻り値として返す。 +3. データベース操作中に予期しないエラーが発生した場合は [DatabaseError](../02.foundation/07.exc.md#databaseerror) を送出する。 +4. 取得した設定情報を [ClientCredentials](../03.entities/07.auth.md#clientcredentials) モデルにデシリアライズして戻り値として返す。 +5. デシリアライズに失敗した場合は [CredentialsError](../02.foundation/07.exc.md#credentialserror) を送出する。 + + +## save_client_credentials +当システムに紐づく SP のクライアント認証情報をデータベースに保存する機能を提供する。 + +#### シグネチャ +```python[service_settings.py] +def save_client_credentials(credentials: ClientCredentials) -> None: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ----------- | ---------------------------------------------------------------- | ---- | +| credentials | [ClientCredentials](../03.entities/07.auth.md#clientcredentials) | | クライアント認証情報 | + +#### エラー + +| エラー名 | 説明 | +| --------------------------------------------------------------- | -------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CredentialsError](../02.foundation/07.exc.md#credentialserror) | クライアント認証情報のシリアライズに失敗した場合。 | + +#### 処理内容 +1. 引数 `credentials` を JSON データにシリアライズする。 +2. シリアライズに失敗した場合は [CredentialsError](../02.foundation/07.exc.md#credentialserror) を送出する。 +3. データベースにサービス設定情報を `client_credentials` キーで保存する。 +4. データベース操作中に予期しないエラーが発生した場合は [DatabaseError](../02.foundation/07.exc.md#databaseerror) を送出する。 From 1d9ce4fc5011fbb7c4c372b34e4f24bfc7422687 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:41:15 +0000 Subject: [PATCH 016/173] docs: add placeholder for entiteis --- .../03.entities/05.search-request.md | 5 + .../03.entities/06.patch-request.md | 125 ++++++++++++++++++ .../01.detailed/03.entities/07.auth.md | 13 ++ .../03.entities/08.repository-detail.md | 8 ++ .../03.entities/09.group-detail.md | 8 ++ .../01.detailed/03.entities/10.user-detail.md | 8 ++ 6 files changed, 167 insertions(+) create mode 100644 docs/content/01.detailed/03.entities/05.search-request.md create mode 100644 docs/content/01.detailed/03.entities/06.patch-request.md create mode 100644 docs/content/01.detailed/03.entities/07.auth.md create mode 100644 docs/content/01.detailed/03.entities/08.repository-detail.md create mode 100644 docs/content/01.detailed/03.entities/09.group-detail.md create mode 100644 docs/content/01.detailed/03.entities/10.user-detail.md diff --git a/docs/content/01.detailed/03.entities/05.search-request.md b/docs/content/01.detailed/03.entities/05.search-request.md new file mode 100644 index 00000000..8a892e45 --- /dev/null +++ b/docs/content/01.detailed/03.entities/05.search-request.md @@ -0,0 +1,5 @@ +--- +title: entities.search_request +description: mAP Core API V2 の 検索リクエストボディに対応するスキーマおよびエンティティモデルを提供する。 +--- + diff --git a/docs/content/01.detailed/03.entities/06.patch-request.md b/docs/content/01.detailed/03.entities/06.patch-request.md new file mode 100644 index 00000000..3d4585c2 --- /dev/null +++ b/docs/content/01.detailed/03.entities/06.patch-request.md @@ -0,0 +1,125 @@ +--- +title: entities.patch_request +description: mAP Core API V2 の PATCH リクエストボディに対応するスキーマおよびエンティティモデルを提供する。 +--- + +## PatchRequestPayload +mAP Core API V2 の PATCH リクエストボディに対応するモデル定義。 +Python オブジェクトからリクエストボディの JSON へのシリアライズ機能を提供する。 + +#### シグネチャ +```python[patch_request.py] +class PatchRequestPayload(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 +| 名前 | 型 | 説明 | +| ------------ | ---------------------------------------- | ---------------------------- | +| schemas | list\[str] | リクエストのスキーマのリスト | +| operations | list\[[PatchOperation](#patchoperation)] | PATCH 操作のリスト | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `operations` 属性は PATCH リクエストの `Operations` フィールドに対応する。 +- `schemas` 属性の値は読み取り専用であり、 + 定数 [`MAP_PATCH_SCHEMA`](../02.foundation/03.const.md#map_patch_schema) をただ一つ含むリストである。 + + +## PatchOperation +mAP Core API V2 の PATCH リクエストの `Operations` フィールド内の各操作に対応する型エイリアス。 +[`AddOperation`](#addoperation)、[`RemoveOperation`](#removeoperation)、[`ReplaceOperation`](#replaceoperation) のユニオン型として定義される。 +[`PatchRequestPayload`](#patchrequestpayload) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[patch_request.py] +type PatchOperation = AddOperation | RemoveOperation | ReplaceOperation +``` + +#### 注釈 +- `AddOperation`、`RemoveOperation`、`ReplaceOperation` クラスは `op` 属性の値を型識別子(discriminator)に使用する。 + + +## AddOperation +mAP Core API V2 の PATCH リクエストの `Operations` フィールド内の `add` 操作に対応するモデル定義。 +[`PatchRequestPayload`](#patchrequestpayload) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[patch_request.py] +class AddOperation(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| op | Literal\["add"] | 操作タイプ名 | +| path | str \| None | 操作対象のフィールド | +| value | Any | 操作で使用する値 | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `op` 属性の値は定数 `"add"` に固定される。 + + +## RemoveOperation +mAP Core API V2 の PATCH リクエストの `Operations` フィールド内の `remove` 操作に対応するモデル定義。 +[`PatchRequestPayload`](#patchrequestpayload) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[patch_request.py] +class RemoveOperation(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| op | Literal\["remove"] | 操作タイプ名 | +| path | str | 操作対象のフィールド | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `op` 属性の値は定数 `"remove"` に固定される。 + + +## ReplaceOperation +mAP Core API V2 の PATCH リクエストの `Operations` フィールド内の `replace` 操作に対応するモデル定義。 +[`PatchRequestPayload`](#patchrequestpayload) クラスのネストした属性として使用される。 + +#### シグネチャ +```python[patch_request.py] +class ReplaceOperation(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| op | Literal\["replace"] | 操作タイプ名 | +| path | str | 操作対象のフィールド | +| value | Any | 操作で使用する値 | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + +#### 注釈 +- `op` 属性の値は定数 `"replace"` に固定される。 diff --git a/docs/content/01.detailed/03.entities/07.auth.md b/docs/content/01.detailed/03.entities/07.auth.md new file mode 100644 index 00000000..d4ad9922 --- /dev/null +++ b/docs/content/01.detailed/03.entities/07.auth.md @@ -0,0 +1,13 @@ +--- +title: entities.auth +description: 認証情報に関するエンティティモデルを提供する。 +--- + +## ClientCredentials +クライアント認証情報を表すモデル定義。 +リクエストおよびレスポンス、およびデータベース保存において JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + + +## OAuthToken +アクセストークン情報を表すモデル定義。 +リクエストおよびレスポンス、およびデータベース保存において JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 diff --git a/docs/content/01.detailed/03.entities/08.repository-detail.md b/docs/content/01.detailed/03.entities/08.repository-detail.md new file mode 100644 index 00000000..83a80453 --- /dev/null +++ b/docs/content/01.detailed/03.entities/08.repository-detail.md @@ -0,0 +1,8 @@ +--- +title: entities.repository_detail +description: リポジトリ情報の詳細表示に用いるエンティティモデルを提供する。 +--- + +## RepositoryDetail +リポジトリ情報の詳細表示に用いるモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 diff --git a/docs/content/01.detailed/03.entities/09.group-detail.md b/docs/content/01.detailed/03.entities/09.group-detail.md new file mode 100644 index 00000000..4b6358f3 --- /dev/null +++ b/docs/content/01.detailed/03.entities/09.group-detail.md @@ -0,0 +1,8 @@ +--- +title: entities.group_detail +description: グループ情報の詳細表示に用いるエンティティモデルを提供する。 +--- + +## GroupDetail +グループ情報の詳細表示に用いるモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 diff --git a/docs/content/01.detailed/03.entities/10.user-detail.md b/docs/content/01.detailed/03.entities/10.user-detail.md new file mode 100644 index 00000000..1eaac707 --- /dev/null +++ b/docs/content/01.detailed/03.entities/10.user-detail.md @@ -0,0 +1,8 @@ +--- +title: user_detail +description: ユーザー情報の詳細表示に用いるエンティティモデルを提供する。 +--- + +## UserDetail +ユーザー情報の詳細表示に用いるモデル定義。 +リクエストおよびレスポンスの JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 From 302a8935983346835228de13b061b174a707a7e0 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:42:24 +0000 Subject: [PATCH 017/173] docs: add users client --- .../01.detailed/06.clients/04.users.md | 379 ++++++++++++++++++ .../01.detailed/06.clients/05.utils.md | 49 +++ 2 files changed, 428 insertions(+) create mode 100644 docs/content/01.detailed/06.clients/04.users.md create mode 100644 docs/content/01.detailed/06.clients/05.utils.md diff --git a/docs/content/01.detailed/06.clients/04.users.md b/docs/content/01.detailed/06.clients/04.users.md new file mode 100644 index 00000000..6af429ec --- /dev/null +++ b/docs/content/01.detailed/06.clients/04.users.md @@ -0,0 +1,379 @@ +--- +title: clients.users +description: mAP Core API V2 の User Resource API を操作するクライアント機能を提供する。 +--- + +## get_by_id +ユーザー ID に基づいて mAP Core API からユーザー情報を取得する機能を提供する。 + +#### 対応エンドポイント +**GET** /api/v2/Users/{id} + +#### シグネチャ +```python[users.py] +def get_by_id( + user_id: str, /, include: set[str] | None = None, exclude: set[str] | None = None, + *, access_token: str, client_secret: str, +) -> MapUser | MapError: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用。 +- **pydantic**: レスポンスのバリデーションおよびデシリアライズに使用。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ------------- | ------------------ | --------------------------------------------------------- | +| user_id | str | ユーザーID | +| include | set\[str\] \| None | 取得するフィールドのセット。None の場合はすべて取得 | +| exclude | set\[str\] \| None | 除外するフィールドのセット。None の場合は除外しない | +| access_token | str | (キーワード引数) mAP Core APIのアクセストークン | +| client_secret | str | (キーワード引数) mAP Core APIのクライアントシークレット | + +#### 戻り値 + +| 型 | 説明 | +| --------------------------------------------------- | ---------------------------------------------- | +| [MapUser](../03.entities/03.map_user.md#mapuser) | 取得されたユーザー情報 | +| [MapError](../03.entities/04.map_error.md#maperror) | ユーザー情報が取得されなかった場合のエラー情報 | + +#### エラー + +| 型 | 説明 | +| ----------------------------- | ------------------------------------ | +| requests.exceptions.HTTPError | 401 以上の HTTP エラーが発生した場合 | + +#### 処理内容 +1. [`utils:get_time_stamp`](./05.utils.md#get_time_stamp) を呼び出し、タイムスタンプを取得する。 +2. タイムスタンプ及び、引数 `access_token`、`client_secret` をもとに + [`utils:compute_signature`](./05.utils.md#compute_signature) を呼び出し、署名を生成する。 +3. 引数 `include` および `exclude` をもとに、取得するフィールドのパラメータを作成する。 + - `include` が None でない場合、カンマ区切りの文字列に結合する。 + - `exclude` が None でない場合、カンマ区切りの文字列に結合とする。 + - フィールド名は camelCase に変換して使用する。 +4. mAP Core API に対してリクエストを送信する。 + - エンドポイント [`MAP_USERS_ENDPOINT`](../02.foundation/03.const.md#map_users_endpoint) に対して、GET リクエストを送信する。 + - パスパラメータとして末尾に `/{user_id}` を付与する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------------ | ----------------------------------------------------- | + | time_stamp | タイムスタンプ | + | signature | 署名 | + | attributes | (存在する場合)引数 `include` で指定されたフィールド | + | excludedAttributes | (存在する場合)引数 `exclude` で指定されたフィールド | + - リクエストヘッダーに以下を付与する。 + | 名前 | 値 | + | ------------- | --------------------------------------------------------- | + | Content-Type | `application/json`( **requests** により自動設定される) | + | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | + - サーバー設定値に従い、タイムアウト値を設定する。 +5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 + - ステータスコードが 200 の場合、レスポンスボディを + [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 + - ステータスコードが 400 の場合、レスポンスボディを + [`MapError`](../03.entities/04.map_error.md#maperror) のインスタンスにデシリアライズする。 + - ステータスコードが401以上の場合、`requests.Response.raise_for_status` を呼び出し 例外 `requests.exceptions.HTTPError` を送出する。 +6. デシリアライズされたユーザー情報、あるいはエラー情報を戻り値として返す。 + + +## get_by_eppn +EPPN に基づいて mAP Core API からユーザー情報を取得する機能を提供する。 + +#### 対応エンドポイント +**GET** /api/v2/Existeppn/{eppn} + +#### シグネチャ +```python[users.py] +def get_by_eppn( + eppn: str, /, include: set[str] | None = None, exclude: set[str] | None = None, + *, access_token: str, client_secret: str +) -> MapUser | MapError: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用。 +- **pydantic**: レスポンスのバリデーションおよびデシリアライズに使用。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ------------- | ------------------ | --------------------------------------------------------- | +| eppn | str | ユーザーの EPPN | +| include | set\[str\] \| None | 取得するフィールドのセット。None の場合はすべて取得 | +| exclude | set\[str\] \| None | 除外するフィールドのセット。None の場合は除外しない | +| access_token | str | (キーワード引数) mAP Core APIのアクセストークン | +| client_secret | str | (キーワード引数) mAP Core APIのクライアントシークレット | + +#### 戻り値 + +| 型 | 説明 | +| --------------------------------------------------- | ---------------------------------------------- | +| [MapUser](../03.entities/03.map_user.md#mapuser) | 取得されたユーザー情報 | +| [MapError](../03.entities/04.map_error.md#maperror) | ユーザー情報が取得されなかった場合のエラー情報 | + + +#### エラー + +| 型 | 説明 | +| ----------------------------- | ------------------------------------ | +| requests.exceptions.HTTPError | 401 以上の HTTP エラーが発生した場合 | + +#### 処理内容 +1. [`utils:get_time_stamp`](./05.utils.md#get_time_stamp) を呼び出し、タイムスタンプを取得する。 +2. タイムスタンプ及び、引数 `access_token`、`client_secret` をもとに + [`utils:compute_signature`](./05.utils.md#compute_signature) を呼び出し、署名を生成する。 +3. 引数 `include` および `exclude` をもとに、取得するフィールドのパラメータを作成する。 + - `include` が None でない場合、カンマ区切りの文字列に結合する。 + - `exclude` が None でない場合、カンマ区切りの文字列に結合とする。 + - フィールド名は camelCase に変換して使用する。 +4. mAP Core API に対してリクエストを送信する。 + - エンドポイント [`MAP_EXIST_EPPN_ENDPOINT`](../02.foundation/03.const.md#map_exist_eppn_endpoint) に対して、GET リクエストを送信する。 + - パスパラメータとして末尾に `/{eppn}` を付与する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------------ | ----------------------------------------------------- | + | time_stamp | タイムスタンプ | + | signature | 署名 | + | attributes | (存在する場合)引数 `include` で指定されたフィールド | + | excludedAttributes | (存在する場合)引数 `exclude` で指定されたフィールド | + - リクエストヘッダーに以下を付与する。 + | 名前 | 値 | + | ------------- | --------------------------------------------------------- | + | Content-Type | `application/json`( **requests** により自動設定される) | + | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | + - サーバー設定値に従い、タイムアウト値を設定する。 +5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 + - ステータスコードが 200 の場合、レスポンスボディを + [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 + - ステータスコードが 400 の場合、レスポンスボディを + [`MapError`](../03.entities/04.map_error.md#maperror) のインスタンスにデシリアライズする。 + - ステータスコードが401以上の場合、`requests.Response.raise_for_status` を呼び出し 例外 `requests.exceptions.HTTPError` を送出する。 +6. デシリアライズされたユーザー情報、あるいはエラー情報を戻り値として返す。 + + +## post +mAP Core API に対してユーザー情報を登録する機能を提供する。 + +#### 対応エンドポイント +**POST** /api/v2/Users + +#### シグネチャ +```python[users.py] +def post( + user: MapUser, /, include: set[str] | None = None, exclude: set[str] | None = None, + *, access_token: str, client_secret: str +) -> MapUser | MapError: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用。 +- **pydantic**: リクエストおよびレスポンスのバリデーションおよびシリアライズ・デシリアライズに使用。 + +#### 引数 +| 名前 | 型 | 説明 | +| ------------- | ------------------ | ----------------------------------------------------------- | +| user | MapUser | 登録するユーザー情報 | +| include | set\[str\] \| None | 登録するフィールドのセット。None の場合はすべて登録 | +| exclude | set\[str\] \| None | 登録から除外するフィールドのセット。None の場合は除外しない | +| access_token | str | (キーワード引数) mAP Core APIのアクセストークン | +| client_secret | str | (キーワード引数) mAP Core APIのクライアントシークレット | + +#### 戻り値 +| 型 | 説明 | +| --------------------------------------------------- | -------------------------------------------- | +| [MapUser](../03.entities/03.map_user.md#mapuser) | 登録されたユーザー情報 | +| [MapError](../03.entities/04.map_error.md#maperror) | ユーザー情報の登録に失敗した場合のエラー情報 | + +#### エラー +| 型 | 説明 | +| ----------------------------- | ------------------------------------ | +| requests.exceptions.HTTPError | 401 以上の HTTP エラーが発生した場合 | + +#### 処理内容 +1. [`utils:get_time_stamp`](./05.utils.md#get_time_stamp) を呼び出し、タイムスタンプを取得する。 +2. タイムスタンプ及び、引数 `access_token`、`client_secret` をもとに + [`utils:compute_signature`](./05.utils.md#compute_signature) を呼び出し、署名を生成する。 +3. 引数 `include` および `exclude` をもとに、取得するフィールドのパラメータを作成する。 + - `include` が None でない場合、カンマ区切りの文字列に結合する。 + - `exclude` が None でない場合、カンマ区切りの文字列に結合とする。 + - フィールド名は camelCase に変換して使用する。 +4. mAP Core API に対してリクエストを送信する。 + - エンドポイント [`MAP_USERS_ENDPOINT`](../02.foundation/03.const.md#map_users_endpoint) に対して、POST リクエストを送信する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------------ | ----------------------------------------------------- | + | attributes | (存在する場合)引数 `include` で指定されたフィールド | + | excludedAttributes | (存在する場合)引数 `exclude` で指定されたフィールド | + - リクエストヘッダーに以下を付与する。 + | 名前 | 値 | + | ------------- | --------------------------------------------------------- | + | Content-Type | `application/json`( **requests** により自動設定される) | + | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | + - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 + | 名前 | 値 | + | ---------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |     time_stamp | タイムスタンプ | + |     signature | 署名 | + - サーバー設定値に従い、タイムアウト値を設定する。 +5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 + - ステータスコードが 201 の場合、レスポンスボディを + [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 + - ステータスコードが 400 の場合、レスポンスボディを + [`MapError`](../03.entities/04.map_error.md#maperror) のインスタンスにデシリアライズする。 + - ステータスコードが401以上の場合、`requests.Response.raise_for_status` を呼び出し 例外 `requests.exceptions.HTTPError` を送出する。 +6. デシリアライズされたユーザー情報、あるいはエラー情報を戻り値として返す。 + + +## ~~put_by_id~~ +ユーザー ID に基づいて mAP Core API 上のユーザー情報を更新する機能を提供する。(実装未定) + +#### 対応エンドポイント +**PUT** /api/v2/Users/{id} + +#### シグネチャ +```python[users.py] +def put_by_id( + user: MapUser, /, include: set[str] | None = None, exclude: set[str] | None = None, + *, access_token: str, client_secret: str +) -> MapUser | MapError: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用。 +- **pydantic**: リクエストおよびレスポンスのバリデーションおよびシリアライズ・デシリアライズに使用。 + +#### 引数 +| 名前 | 型 | 説明 | +| ------------- | ------------------ | ----------------------------------------------------------- | +| user | MapUser | 更新するユーザー情報 | +| include | set\[str\] \| None | 更新するフィールドのセット。None の場合はすべて更新 | +| exclude | set\[str\] \| None | 更新から除外するフィールドのセット。None の場合は除外しない | +| access_token | str | (キーワード引数) mAP Core APIのアクセストークン | +| client_secret | str | (キーワード引数) mAP Core APIのクライアントシークレット | + +#### 戻り値 +| 型 | 説明 | +| --------------------------------------------------- | -------------------------------------------- | +| [MapUser](../03.entities/03.map_user.md#mapuser) | 更新されたユーザー情報 | +| [MapError](../03.entities/04.map_error.md#maperror) | ユーザー情報の更新に失敗した場合のエラー情報 | + +#### エラー +| 型 | 説明 | +| ----------------------------- | ------------------------------------ | +| requests.exceptions.HTTPError | 401 以上の HTTP エラーが発生した場合 | + +#### 処理内容 +1. [`utils:get_time_stamp`](./05.utils.md#get_time_stamp) を呼び出し、タイムスタンプを取得する。 +2. タイムスタンプ及び、引数 `access_token`、`client_secret` をもとに + [`utils:compute_signature`](./05.utils.md#compute_signature) を呼び出し、署名を生成する。 +3. 引数 `include` および `exclude` をもとに、取得するフィールドのパラメータを作成する。 + - `include` が None でない場合、カンマ区切りの文字列に結合する。 + - `exclude` が None でない場合、カンマ区切りの文字列に結合とする。 + - フィールド名は camelCase に変換して使用する。 +4. mAP Core API に対してリクエストを送信する。 + - エンドポイント [`MAP_USERS_ENDPOINT`](../02.foundation/03.const.md#map_users_endpoint) に対して、PUT リクエストを送信する。 + - パスパラメータとして末尾に `/{user.id}` を付与する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------------ | ----------------------------------------------------- | + | attributes | (存在する場合)引数 `include` で指定されたフィールド | + | excludedAttributes | (存在する場合)引数 `exclude` で指定されたフィールド | + - リクエストヘッダーに以下を付与する。 + | 名前 | 値 | + | ------------- | --------------------------------------------------------- | + | Content-Type | `application/json`( **requests** により自動設定される) | + | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | + - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 + | 名前 | 値 | + | ---------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |     time_stamp | タイムスタンプ | + |     signature | 署名 | + - サーバー設定値に従い、タイムアウト値を設定する。 +5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 + - ステータスコードが 200 の場合、レスポンスボディを + [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 + - ステータスコードが 400 の場合、レスポンスボディを + [`MapError`](../03.entities/04.map_error.md#maperror) のインスタンスにデシリアライズする。 + - ステータスコードが401以上の場合、`requests.Response.raise_for_status` を呼び出し 例外 `requests.exceptions.HTTPError` を送出する。 +6. デシリアライズされたユーザー情報、あるいはエラー情報を戻り値として返す。 + + +## patch_by_id +ユーザー ID に基づいて mAP Core API 上のユーザー情報を部分更新する機能を提供する。 + +#### 対応エンドポイント +**PATCH** /api/v2/Users/{id} + +#### シグネチャ +```python[users.py] +def patch_by_id( + user_id: str, operations: list[PatchOperation] + /, include: set[str] | None = None, exclude: set[str] | None = None, + *, access_token: str, client_secret: str +) -> MapUser | MapError: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用。 +- **pydantic**: リクエストおよびレスポンスのバリデーションおよびシリアライズ・デシリアライズに使用。 + +#### 引数 +| 名前 | 型 | 説明 | +| ------------- | ---------------------- | ----------------------------------------------------------- | +| user_id | str | ユーザーID | +| operations | list\[PatchOperation\] | 更新操作のリスト | +| include | set\[str\] \| None | 更新するフィールドのセット。None の場合はすべて更新 | +| exclude | set\[str\] \| None | 更新から除外するフィールドのセット。None の場合は除外しない | +| access_token | str | (キーワード引数) mAP Core APIのアクセストークン | +| client_secret | str | (キーワード引数) mAP Core APIのクライアントシークレット | + +#### 戻り値 +| 型 | 説明 | +| --------------------------------------------------- | -------------------------------------------- | +| [MapUser](../03.entities/03.map_user.md#mapuser) | 更新されたユーザー情報 | +| [MapError](../03.entities/04.map_error.md#maperror) | ユーザー情報の更新に失敗した場合のエラー情報 | + +#### エラー +| 型 | 説明 | +| ----------------------------- | ------------------------------------ | +| requests.exceptions.HTTPError | 401 以上の HTTP エラーが発生した場合 | + +#### 処理内容 +1. [`utils:get_time_stamp`](./05.utils.md#get_time_stamp) を呼び出し、タイムスタンプを取得する。 +2. タイムスタンプ及び、引数 `access_token`、`client_secret` をもとに + [`utils:compute_signature`](./05.utils.md#compute_signature) を呼び出し、署名を生成する。 +3. 引数 `operations` をもとに、[`PatchRequestPayload`](../03.entities/06.patch_request.md#patchrequestpayload) のインスタンスを作成する。 +4. 引数 `include` および `exclude` をもとに、取得するフィールドのパラメータを作成する。 + - `include` が None でない場合、カンマ区切りの文字列に結合する。 + - `exclude` が None でない場合、カンマ区切りの文字列に結合とする。 + - フィールド名は camelCase に変換して使用する。 +5. mAP Core API に対してリクエストを送信する。 + - エンドポイント [`MAP_USERS_ENDPOINT`](../02.foundation/03.const.md#map_users_endpoint) に対して、PATCH リクエストを送信する。 + - パスパラメータとして末尾に `/{user.id}` を付与する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------------ | ----------------------------------------------------- | + | attributes | (存在する場合)引数 `include` で指定されたフィールド | + | excludedAttributes | (存在する場合)引数 `exclude` で指定されたフィールド | + - リクエストヘッダーに以下を付与する。 + | 名前 | 値 | + | ------------- | --------------------------------------------------------- | + | Content-Type | `application/json`( **requests** により自動設定される) | + | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | + - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 + | 名前 | 値 | + | ---------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |     time_stamp | タイムスタンプ | + |     signature | 署名 | + - サーバー設定値に従い、タイムアウト値を設定する。 +6. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 + - ステータスコードが 200 の場合、レスポンスボディを + [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 + - ステータスコードが 400 の場合、レスポンスボディを + [`MapError`](../03.entities/04.map_error.md#maperror) のインスタンスにデシリアライズする。 + - ステータスコードが401以上の場合、`requests.Response.raise_for_status` を呼び出し 例外 `requests.exceptions.HTTPError` を送出する。 +7. デシリアライズされたユーザー情報、あるいはエラー情報を戻り値として返す。 diff --git a/docs/content/01.detailed/06.clients/05.utils.md b/docs/content/01.detailed/06.clients/05.utils.md new file mode 100644 index 00000000..a8bd7ad1 --- /dev/null +++ b/docs/content/01.detailed/06.clients/05.utils.md @@ -0,0 +1,49 @@ +--- +title: clients.utils +description: mAP Core API V2 クライアント機能で共通に使用されるユーティリティ関数を提供する。 +--- + +## get_time_stamp +mAP Core API V2 を使用する際に使用するタイムスタンプを取得する。Unix 時間を秒単位で返す。 + +#### シグネチャ +```python[utils.py] +def get_time_stamp() -> str: +``` + +#### 戻り値 + +| 型 | 説明 | +| --- | ------------------ | +| str | 秒単位の Unix 時間 | + +#### 処理内容 +1. 現在の UTC 時刻を取得する。 +2. ミリ秒を切り捨てて、その文字列を戻り値として返す。 + + +## compute_signature +mAP Core API V2 を使用する際に必要な署名を計算する。 + +#### シグネチャ +```python[utils.py] +def compute_signature(client_secret: str, access_token: str, time_stamp: str) -> str: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ------------- | --- | ------------------------ | +| client_secret | str | クライアントシークレット | +| access_token | str | アクセストークン | +| time_stamp | str | タイムスタンプ | + +#### 戻り値 + +| 型 | 説明 | +| --- | ----------------------- | +| str | 16 進数表現の署名文字列 | + +#### 処理内容 +1. `client_secret`、`access_token`、`time_stamp` の順に連結した文字列を作成する。 +2. 連結した文字列の SHA-256 ハッシュを計算し、その 16 進数表現の文字列を戻り値として返す。 From 74d2771b5eb6a44bd2efa1697aff229eadeebc06 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 01:42:43 +0000 Subject: [PATCH 018/173] docs: add navigation titles --- docs/content/01.detailed/05.services/.navigation.yml | 1 + docs/content/01.detailed/06.clients/.navigation.yml | 1 + 2 files changed, 2 insertions(+) create mode 100644 docs/content/01.detailed/05.services/.navigation.yml create mode 100644 docs/content/01.detailed/06.clients/.navigation.yml diff --git a/docs/content/01.detailed/05.services/.navigation.yml b/docs/content/01.detailed/05.services/.navigation.yml new file mode 100644 index 00000000..c6b22b52 --- /dev/null +++ b/docs/content/01.detailed/05.services/.navigation.yml @@ -0,0 +1 @@ +title: サービス diff --git a/docs/content/01.detailed/06.clients/.navigation.yml b/docs/content/01.detailed/06.clients/.navigation.yml new file mode 100644 index 00000000..c9284395 --- /dev/null +++ b/docs/content/01.detailed/06.clients/.navigation.yml @@ -0,0 +1 @@ +title: 外部 API アクセス From a1a5000e903007789c00b97fb48ad1fa8aceb00f Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 02:44:56 +0000 Subject: [PATCH 019/173] docs: update server config for id patterns --- .../01.detailed/02.foundation/02.config.md | 129 ++++++++++++++++-- 1 file changed, 120 insertions(+), 9 deletions(-) diff --git a/docs/content/01.detailed/02.foundation/02.config.md b/docs/content/01.detailed/02.foundation/02.config.md index 228ecab6..71741ec3 100644 --- a/docs/content/01.detailed/02.foundation/02.config.md +++ b/docs/content/01.detailed/02.foundation/02.config.md @@ -22,15 +22,17 @@ class RuntimeConfig(pydantic_settings.BaseSettings): #### クラス属性 -| 名前 | 型 | 説明 | -| ----------- | --------------------------------- | --------------------- | -| SERVER_NAME | str | サーバ名 | -| SECRET_KEY | str | 暗号化用キー | -| LOG | [LogConfig](#logconfig) | ログ設定 | -| SP | [SpConfig](#spconfig) | SP 設定 | -| MAP_CORE | [MapCoreConfig](#mapcoreconfig) | mAP Core サービス設定 | -| CELERY | [CeleryConfig](#celeryconfig) | Celery 設定 | -| POSTGRES | [PostgresConfig](#postgresconfig) | Postgres 設定 | +| 名前 | 型 | 説明 | +| ------------ | ----------------------------------------- | --------------------- | +| SERVER_NAME | str | サーバ名 | +| SECRET_KEY | str | 暗号化用キー | +| LOG | [LogConfig](#logconfig) | ログ設定 | +| SP | [SpConfig](#spconfig) | SP 設定 | +| MAP_CORE | [MapCoreConfig](#mapcoreconfig) | mAP Core サービス設定 | +| REPOSITORIES | [RepositoriesConfig](#repositoriesconfig) | リポジトリ設定 | +| GROUPS | [GroupsConfig](#groupsconfig) | グループ設定 | +| CELERY | [CeleryConfig](#celeryconfig) | Celery 設定 | +| POSTGRES | [PostgresConfig](#postgresconfig) | Postgres 設定 | #### プロパティ これらは `@computed_field`{lang=python} デコレータを使用し、他の属性値から算出される値で属性としてアクセスされる。 @@ -178,6 +180,115 @@ class MapCoreConfig(pydantic.BaseModel): | timeout | int | リクエストタイムアウト秒数 | +## RepositoriesConfig +リポジトリに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class RepositoriesConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ----------- | --------------------------------------------------------- | -------------------------- | +| id_patterns | [RepositoryIdPatternsConfig](#repositoryidpatternsconfig) | リポジトリ ID パターン設定 | + + +## RepositoryIdPatternsConfig +リポジトリ ID パターンに関するサーバー設定値を管理するクラス。[`RepositoriesConfig`](#repositoriesconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class RepositoryIdPatternsConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ----------------------- | ------------------------- | +| sp_connector | [HasRepoId](#hasrepoid) | SP コネクタ ID のパターン | + + +## GroupsConfig +グループに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class GroupsConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ----------- | ----------------------------------------------- | ------------------------ | +| id_patterns | [GroupIdPatternsConfig](#groupidpatternsconfig) | グループ ID パターン設定 | + + +## GroupIdPatternsConfig +グループ ID パターンに関するサーバー設定値を管理するクラス。[`GroupsConfig`](#groupsconfig) でネストした設定値として使用される。 + +#### シグネチャ +```python[config.py] +class GroupIdPatternsConfig(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: 設定値の型安全管理に使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ---------------- | --------------------------------------------------- | ---------------------------------------- | +| system_admin | [HasRepoId](#hasrepoid) | システム管理者グループ ID のパターン | +| repository_admin | [HasRepoId](#hasrepoid) | リポジトリ管理者グループ ID のパターン | +| community_admin | [HasRepoId](#hasrepoid) | コミュニティ管理者グループ ID のパターン | +| contributor | [HasRepoId](#hasrepoid) | 投稿ユーザーグループ ID のパターン | +| general_user | [HasRepoId](#hasrepoid) | 一般ユーザーグループ ID のパターン | +| user_defined | [HasRepoAndUserDefinedId](#hasrepoanduserdefinedid) | ユーザー定義グループ ID のパターンリスト | + + +## HasRepoId +リポジトリ ID を含むパターンを定義する文字列を示す型エイリアス。 +正規表現 `r".*\{repository_id\}.*"`{lang=python} にマッチする文字列である。 + +#### シグネチャ +```python[config.py] +type HasRepoId = ... +``` + + +## HasRepoAndUserDefinedId +リポジトリ ID とユーザー定義 ID を含むパターンを定義する文字列を示す型エイリアス。 +正規表現 `r".*\{repository_id\}.*\{user_defined_id\}.*"`{lang=python} にマッチする文字列である。 + +#### シグネチャ +```python[config.py] +type HasRepoAndUserDefinedId = ... +``` + + ## CeleryConfig Celery アプリケーションに関するサーバー設定値を管理するクラス。[`RuntimeConfig`](#runtimeconfig) でネストした設定値として使用される。 From d1a49424764c8c89968695cc91e7b6b324f05aab Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 05:48:18 +0000 Subject: [PATCH 020/173] docs: add authentication and token management --- .../01.detailed/03.entities/07.auth.md | 41 +++++ .../content/01.detailed/04.api/06.callback.md | 54 ++++++ docs/content/01.detailed/04.api/07.schema.md | 26 +++ .../01.detailed/05.services/01.token.md | 165 +++++++++++++++++ .../05.services/06.service_settings.md | 75 +++++++- .../content/01.detailed/06.clients/01.auth.md | 172 ++++++++++++++++++ 6 files changed, 524 insertions(+), 9 deletions(-) create mode 100644 docs/content/01.detailed/04.api/06.callback.md create mode 100644 docs/content/01.detailed/04.api/07.schema.md create mode 100644 docs/content/01.detailed/05.services/01.token.md create mode 100644 docs/content/01.detailed/06.clients/01.auth.md diff --git a/docs/content/01.detailed/03.entities/07.auth.md b/docs/content/01.detailed/03.entities/07.auth.md index d4ad9922..1f435464 100644 --- a/docs/content/01.detailed/03.entities/07.auth.md +++ b/docs/content/01.detailed/03.entities/07.auth.md @@ -7,7 +7,48 @@ description: 認証情報に関するエンティティモデルを提供する クライアント認証情報を表すモデル定義。 リクエストおよびレスポンス、およびデータベース保存において JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 +#### シグネチャ +```python[auth.py] +class ClientCredentials(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | ------------------------ | +| client_id | str | クライアント ID | +| client_secret | str | クライアントシークレット | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | + ## OAuthToken アクセストークン情報を表すモデル定義。 リクエストおよびレスポンス、およびデータベース保存において JSON と Python オブジェクト間のシリアライズ・デシリアライズ機能を提供する。 + +#### シグネチャ +```python[auth.py] +class OAuthToken(pydantic.BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------- | ------------------- | ------------------------ | +| access_token | str | アクセストークン | +| token_type | str | トークンタイプ | +| expires_in | int | トークンの有効期限(秒) | +| refresh_token | str \| None | リフレッシュトークン | +| scope | str \| None | スコープ | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | diff --git a/docs/content/01.detailed/04.api/06.callback.md b/docs/content/01.detailed/04.api/06.callback.md new file mode 100644 index 00000000..b527c2b3 --- /dev/null +++ b/docs/content/01.detailed/04.api/06.callback.md @@ -0,0 +1,54 @@ +--- +title: api.callback +description: コールバックでアクセスされる API エンドポイントを提供する。 +--- + +## bp +コールバック関連の API を提供する Blueprint インスタンス。 +名前は `"callback"`、URL プレフィックスは [`router:create_api_blueprint`](./02.router.md#create_api_blueprint) によって `"/api/callback"` に設定される。 + +#### シグネチャ +```python[callback.py] +bp: Blueprint +``` + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 + + +## auth_code +コールバックで認可コードを受け取るエンドポイントを提供するコントローラ。 + +#### シグネチャ +```python[callback.py] +@bp.get("/auth-code") +@validate() +def auth_code(query: OAuthTokenQuery) -> werkzeug.wrappers.Response: +``` + +#### エンドポイント +**GET** /api/callback/auth-code + +#### 依存するライブラリ +- **Flask**: Blueprint を使用する。 + +#### デコレータ +- `@bp.get`{lang=python}: GET リクエストを処理するエンドポイントを定義する。 +- `@validate`{lang=python}: モデルクラスを使用してリクエストとレスポンスのバリデーションを行う。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ----- | ------------------------------------------------- | -------------------------------- | +| query | [OAuthTokenQuery](./07.schema.md#oauthtokenquery) | 認可コードを含むクエリパラメータ | + +#### 戻り値 + +| 型 | 説明 | +| -------------------------- | ---------------------- | +| werkzeug.wrappers.Response | リダイレクトレスポンス | + +#### 処理内容 +1. [`service.token.issue_oauth_token`](./01.token.md#issue_oauth_token) を呼び出し、引数 `query` に含まれる認可コード `code` を使用してアクセストークンの発行を試みる。 +2. [`DatabaseError`](../02.foundation/07.exc.md#databaseerror) 、[`CredentialsError`](../02.foundation/07.exc.md#credentialserror) や [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) が送出された場合は、エラーログを記録する。 +3. アクセストークンの発行の成否に合わせたメッセージを含むリダイレクトレスポンスを生成して戻り値として返す。 diff --git a/docs/content/01.detailed/04.api/07.schema.md b/docs/content/01.detailed/04.api/07.schema.md new file mode 100644 index 00000000..3f87b02c --- /dev/null +++ b/docs/content/01.detailed/04.api/07.schema.md @@ -0,0 +1,26 @@ +--- +title: api.schema +description: API のリクエストおよびレスポンスで使用されるスキーマモデルを提供する。 +--- + +## OAuthTokenQuery +OAuth 2.0 の認可コードを受け取るためのクエリパラメータモデル。 + +#### シグネチャ +```python[callback.py] +class OAuthTokenQuery(BaseModel): +``` + +#### 依存するライブラリ +- **Pydantic**: モデル定義および JSON シリアライズ・デシリアライズに使用。 + +#### 継承元 +`pydantic.BaseModel` + +#### クラス属性 + +| 名前 | 型 | 説明 | +| ------------ | ------------------- | --------------------- | +| code | str | 認可コード | +| state | str | 状態パラメータ | +| model_config | pydantic.ConfigDict | Pydantic モデルの設定 | diff --git a/docs/content/01.detailed/05.services/01.token.md b/docs/content/01.detailed/05.services/01.token.md new file mode 100644 index 00000000..31a045cd --- /dev/null +++ b/docs/content/01.detailed/05.services/01.token.md @@ -0,0 +1,165 @@ +--- +title: services.token +description: 当システムのアクセストークン管理に関するビジネスロジックを提供する。 +--- + +## get_oauth_token +データベースから当システムに紐づく SP のアクセストークン文字列を取得する機能を提供する。 + +#### シグネチャ +```python[token.py] +def get_oauth_token() -> str: +``` + +#### 戻り値 +| 型 | 説明 | +| --- | ---------------------- | +| str | アクセストークン文字列 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------------------------------- | ------------------------------------------------------ | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | アクセストークンが存在しない、または読み取れない場合。 | + +#### 処理内容 +1. [`service_settings.get_oauth_token`](./06.service_settings.md#get_oauth_token) を呼び出し、データベースからアクセストークン情報を取得する。 +2. レコードが存在しない場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 +3. 取得したアクセストークン情報からアクセストークン文字列を戻り値として返す。 + + +## get_client_secret +データベースから当システムに紐づく SP のクライアントシークレット文字列を取得する機能を提供する。 + +#### シグネチャ +```python[token.py] +def get_client_secret() -> str: +``` + +#### 戻り値 +| 型 | 説明 | +| --- | ------------------------------ | +| str | クライアントシークレット文字列 | + +#### エラー + +| 型 | 説明 | +| --------------------------------------------------------------- | ---------------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CredentialsError](../02.foundation/07.exc.md#credentialserror) | クライアント認証情報が存在しない、または読み取れない場合。 | + +#### 処理内容 +1. [`service_settings.get_client_credentials`](./06.service_settings.md#get_client_credentials) を呼び出し、データベースからクライアント認証情報を取得する。 +2. レコードが存在しない場合は [`CredentialsError`](../02.foundation/07.exc.md#credentialserror) を送出する。 +3. 取得したクライアント認証情報からクライアントシークレット文字列を戻り値として返す。 + + +## prepare_issuing_url +当システムに紐づく SP のアクセストークン発行用 URL を準備する機能を提供する。 + +#### シグネチャ +```python[token.py] +def prepare_issuing_url() -> str: +``` + +#### 戻り値 + +| 型 | 説明 | +| --- | -------------------------- | +| str | アクセストークン発行用 URL | + +#### エラー + +| 型 | 説明 | +| ----------------------------------------------------------------- | ----------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CertificatesError](../02.foundation/07.exc.md#certificateserror) | SP の証明書情報の取得あるいは新規発行に失敗した場合。 | + +#### 処理内容 +1. サーバー設定 [`config.SP`](../02.foundation/02.config.md#spconfig) から `entity_id` を取得する。 +2. [`service_settings.get_client_credentials`](./06.service_settings.md#get_client_credentials) を呼び出し、データベースからクライアント認証情報を取得する。 +3. クライアント認証情報が存在しない場合、[`clients.auth.issue_client_credentials`](../06.clients/01.auth.md#issue_client_credentials) + を呼び出し、mAP Core Authorization Server からクライアント認証情報を発行・取得する。 +4. 取得したクライアント認証情報を [`service_settings.save_client_credentials`](./06.service_settings.md#save_client_credentials) を呼び出し、データベースに保存する。 +5. リダイレクト URL を生成する。エンドポイント [`api.callback.auth_code`](../04.api/06.callback.md#auth_code) + に対応するスキーマとホスト名を含む完全な URL を `flask.url_for` を使用して取得する。 +6. アクセストークン発行用 URL を生成する。 + - ベース URL として、サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `base_url` を使用する。 + - エンドポイントは [`MAP_OAUTH_AUTHORIZE_ENDPOINT`](../02.foundation/03.const.md#map_oauth_authorize_endpoint) を使用する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------- | -------------------------------------------- | + | response_type | `"code"` | + | client_id | ステップ 2 または 3 で取得したクライアントID | + | redirect_uri | ステップ 5 で生成した URL | + + +## issue_access_token +当システムに紐づく SP のアクセストークンを mAP Core Authorization Server から発行・取得する機能を提供する。 + +#### シグネチャ +```python[token.py] +def issue_access_token(code: str) -> str: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ---- | --- | ---------------- | +| code | str | 認可コード文字列 | + +#### 戻り値 + +| 型 | 説明 | +| --- | ---------------------- | +| str | アクセストークン文字列 | + +#### エラー + +| 型 | 説明 | +| --------------------------------------------------------------- | ---------------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CredentialsError](../02.foundation/07.exc.md#credentialserror) | クライアント認証情報が存在しない、または読み取れない場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | アクセストークンの発行に失敗した場合。 | + +#### 処理内容 +1. [`service_settings.get_client_credentials`](./06.service_settings.md#get_client_credentials) を呼び出し、データベースからクライアント認証情報を取得する。 +2. クライアント認証情報が存在しない場合は [`CredentialsError`](../02.foundation/07.exc.md#credentialserror) を送出する。 +3. [`clients.auth.issue_oauth_token`](../06.clients/01.auth.md#issue_oauth_token) を呼び出し、引数 `code` を使用してアクセストークンの発行・取得を試みる。 +4. アクセストークンの発行に失敗した場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 +5. [`service_settings.save_oauth_token`](./06.service_settings.md#save_oauth_token) を呼び出し、取得したアクセストークン情報をデータベースに保存する。 +6. 取得したアクセストークン文字列を戻り値として返す。 + + +## refresh_access_token +当システムに紐づく SP のアクセストークンをリフレッシュする機能を提供する。 + +#### シグネチャ +```python[token.py] +def refresh_access_token() -> str: +``` + +#### 戻り値 + +| 型 | 説明 | +| --- | -------------------------------------- | +| str | リフレッシュ後のアクセストークン文字列 | + +#### エラー + +| 型 | 説明 | +| --------------------------------------------------------------- | ---------------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [CredentialsError](../02.foundation/07.exc.md#credentialserror) | クライアント認証情報が存在しない、または読み取れない場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | アクセストークンのリフレッシュに失敗した場合。 | + +#### 処理内容 +1. [`service_settings.get_oauth_token`](./06.service_settings.md#get_oauth_token) を呼び出し、データベースからアクセストークン情報を取得する。 +2. アクセストークン情報が存在しない場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 +3. [`service_settings.get_client_credentials`](./06.service_settings.md#get_client_credentials) を呼び出し、データベースからクライアント認証情報を取得する。 +4. クライアント認証情報が存在しない場合は [`CredentialsError`](../02.foundation/07.exc.md#credentialserror) を送出する。 +5. [`clients.auth.refresh_oauth_token`](../06.clients/01.auth.md#refresh_oauth_token) を呼び出し、アクセストークンのリフレッシュを試みる。 +6. アクセストークンのリフレッシュに失敗した場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 +7. [`service_settings.save_oauth_token`](./06.service_settings.md#save_oauth_token) を呼び出し、取得したアクセストークン情報をデータベースに保存する。 +8. 取得したアクセストークン文字列を戻り値として返す。 diff --git a/docs/content/01.detailed/05.services/06.service_settings.md b/docs/content/01.detailed/05.services/06.service_settings.md index 8e8482b3..9736188f 100644 --- a/docs/content/01.detailed/05.services/06.service_settings.md +++ b/docs/content/01.detailed/05.services/06.service_settings.md @@ -19,17 +19,17 @@ def get_client_credentials() -> ClientCredentials | None: #### エラー -| エラー名 | 説明 | +| 型 | 説明 | | --------------------------------------------------------------- | ---------------------------------------------------------- | | [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | | [CredentialsError](../02.foundation/07.exc.md#credentialserror) | 取得された設定からクライアント認証情報を読み取れない場合。 | #### 処理内容 -1. データベースからサービス設定情報を `client_credentials` キーで取得する。 +1. データベースからサービス設定情報を `"client_credentials"` キーで取得する。 2. レコードが存在しない場合は `None` を戻り値として返す。 -3. データベース操作中に予期しないエラーが発生した場合は [DatabaseError](../02.foundation/07.exc.md#databaseerror) を送出する。 -4. 取得した設定情報を [ClientCredentials](../03.entities/07.auth.md#clientcredentials) モデルにデシリアライズして戻り値として返す。 -5. デシリアライズに失敗した場合は [CredentialsError](../02.foundation/07.exc.md#credentialserror) を送出する。 +3. データベース操作中に予期しないエラーが発生した場合は [`DatabaseError`](../02.foundation/07.exc.md#databaseerror) を送出する。 +4. 取得した設定情報を [`ClientCredentials`](../03.entities/07.auth.md#clientcredentials) モデルにデシリアライズして戻り値として返す。 +5. デシリアライズに失敗した場合は [`CredentialsError`](../02.foundation/07.exc.md#credentialserror) を送出する。 ## save_client_credentials @@ -48,13 +48,70 @@ def save_client_credentials(credentials: ClientCredentials) -> None: #### エラー -| エラー名 | 説明 | +| 型 | 説明 | | --------------------------------------------------------------- | -------------------------------------------------- | | [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | | [CredentialsError](../02.foundation/07.exc.md#credentialserror) | クライアント認証情報のシリアライズに失敗した場合。 | #### 処理内容 1. 引数 `credentials` を JSON データにシリアライズする。 -2. シリアライズに失敗した場合は [CredentialsError](../02.foundation/07.exc.md#credentialserror) を送出する。 -3. データベースにサービス設定情報を `client_credentials` キーで保存する。 -4. データベース操作中に予期しないエラーが発生した場合は [DatabaseError](../02.foundation/07.exc.md#databaseerror) を送出する。 +2. シリアライズに失敗した場合は [`CredentialsError`](../02.foundation/07.exc.md#credentialserror) を送出する。 +3. データベースにサービス設定情報を `"client_credentials"` キーで保存する。 +4. データベース操作中に予期しないエラーが発生した場合は [`DatabaseError`](../02.foundation/07.exc.md#databaseerror) を送出する。 + + +## get_oauth_token +データベースから当システムに紐づく SP のアクセストークン情報を取得する機能を提供する。 + +#### シグネチャ +```python[service_settings.py] +def get_oauth_token() -> OAuthToken | None: +``` + +#### 戻り値 + +| 型 | 説明 | +| ------------------------------------------------ | ------------------------------------------------ | +| [OAuthToken](../03.entities/07.auth.md#oauthtoken) \| None | アクセストークン情報。存在しない場合は None を返す。 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------------------------------- | ------------------------------------------------------ | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | | 取得された設定からアクセストークン情報を読み取れない場合。 | + +#### 処理内容 +1. データベースからサービス設定情報を `"oauth_token"` キーで取得する。 +2. レコードが存在しない場合は `None` を戻り値として返す。 +3. データベース操作中に予期しないエラーが発生した場合は [`DatabaseError`](../02.foundation/07.exc.md#databaseerror) を送出する。 +4. 取得した設定情報を [`OAuthToken`](../03.entities/07.auth.md#oauthtoken) モデルにデシリアライズして戻り値として返す。 +5. デシリアライズに失敗した場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 + + +## save_oauth_token +当システムに紐づく SP のアクセストークン情報をデータベースに保存する機能を提供する。 + +#### シグネチャ +```python[service_settings.py] +def save_oauth_token(token: OAuthToken) -> None: +``` + +#### 引数 + +| 名前 | 型 | 説明 | +| ----- | ---------------------------------------------------- | -------------- | +| token | [OAuthToken](../03.entities/07.auth.md#oauthtoken) | アクセストークン情報 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------------------------------- | -------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | アクセストークン情報のシリアライズに失敗した場合。 | + +#### 処理内容 +1. 引数 `token` を JSON データにシリアライズする。 +2. シリアライズに失敗した場合は [`OAuthTokenError`](../02.foundation/07.exc.md#oauthtokenerror) を送出する。 +3. データベースにサービス設定情報を `"oauth_token"` キーで保存する。 +4. データベース操作中に予期しないエラーが発生した場合は [`DatabaseError`](../02.foundation/07.exc.md#databaseerror) を送出する。 diff --git a/docs/content/01.detailed/06.clients/01.auth.md b/docs/content/01.detailed/06.clients/01.auth.md new file mode 100644 index 00000000..a535b762 --- /dev/null +++ b/docs/content/01.detailed/06.clients/01.auth.md @@ -0,0 +1,172 @@ +--- +title: clients.auth +description: mAP Core Authorization Server の認証を行うクライアント機能を提供する。 +--- + +## issue_client_credentials +SP のクライアント認証情報を mAP Core Authorization Server から発行・取得する機能を提供する。 + +#### 対応エンドポイント +**POST** [/oauth/sslauth/issue.php](../02.foundation/03.const.md#map_oauth_issue_endpoint) + +#### シグネチャ +```python[auth.py] +def issue_client_credentials(entity_id: str, certs: SpCerts) -> ClientCredentials: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用する。 +- **flask**: リダイレクト URL の生成に使用する。 +- **pydantic**: レスポンスのバリデーションおよびデシリアライズに使用。 + +#### 引数 + +| 名前 | 型 | 説明 | +| ---------------------------- | ------- | --------------------------- | +| entity_id | str | SP エンティティ ID | +| certs | SpCerts | SP の証明書情報オブジェクト | +|  ⤷   crt | str | SP 証明書のファイルパス | +|  ⤷   key | str | SP 秘密鍵のファイルパス | + +- 第二引数 `certs` の型は `Protocol` で定義され、`crt` および `key` 属性を持つオブジェクトを受け取る。 + これに合致するオブジェクトとして、[`config.SP`](../02.foundation/02.config.md#spconfig) がある。 + +#### 戻り値 + +| 型 | 説明 | +| ---------------------------------------------------------------- | -------------------- | +| [ClientCredentials](../03.entities/07.auth.md#clientcredentials) | クライアント認証情報 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------ | ----------------------------------------------- | +| requests.exceptions.RequestException | HTTP リクエストに失敗した場合。 | +| requests.exceptions.HTTPError | レスポンスのステータスコードが 200 以外の場合。 | + +#### 処理内容 +1. リダイレクト URL を生成する。エンドポイント [`api.callback.auth_code`](../04.api/06.callback.md#auth_code) + に対応するスキーマとホスト名を含む完全な URL を `flask.url_for` を使用して取得する。 +2. mAP Core Authorization Server に対してリクエストを送信する。 + - エンドポイント [`MAP_OAUTH_ISSUE_ENDPOINT`](../02.foundation/03.const.md#map_oauth_issue_endpoint) に対して、POST リクエストを送信する。 + - クエリパラメータとして以下を付与する。 + | 名前 | 値 | + | ------------ | ------------------------- | + | entityid | 引数 `entity_id` | + | redirect_uri | ステップ 1 で生成した URL | + - クライアント証明書として、引数 `certs` の `crt` および `key` で指定されたファイルを使用する。 + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 +3. レスポンスのステータスコードが 200 以外の場合は `requests.exceptions.HTTPError` を送出する。 +4. レスポンスボディを [`ClientCredentials`](../03.entities/07.auth.md#clientcredentials) モデルにデシリアライズして戻り値として返す。 + + +## issue_oauth_token +認可コードを使用して SP のアクセストークンを mAP Core Authorization Server から発行・取得する機能を提供する。 + +#### 対応エンドポイント +**POST** [/oauth/token.php](../02.foundation/03.const.md#map_oauth_token_endpoint) + +#### シグネチャ +```python[auth.py] +def issue_oauth_token(code: str, credentials: ClientCreds) -> OAuthToken: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用する。 +- **flask**: リダイレクト URL の生成に使用する。 +- **pydantic**: レスポンスのバリデーションおよびデシリアライズに使用。 + +#### 引数 + +| 名前 | 型 | 説明 | +| -------------------------------------- | ----------- | -------------------------------- | +| code | str | 認可コード | +| credentials | ClientCreds | クライアント認証情報オブジェクト | +|  ⤷   client_id | str | クライアントID | +|  ⤷   client_secret | str | クライアントシークレット | + +- 第二引数 `credentials` の型は `Protocol` で定義され、`client_id` および `client_secret` 属性を持つオブジェクトを受け取る。 + これに合致するオブジェクトとして、[`ClientCredentials`](../03.entities/07.auth.md#clientcredentials) がある。 + +#### 戻り値 + +| 型 | 説明 | +| -------------------------------------------------- | ------------------------------ | +| [OAuthToken](../03.entities/07.auth.md#oauthtoken) | 発行されたアクセストークン情報 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------ | ----------------------------------------------- | +| requests.exceptions.RequestException | HTTP リクエストに失敗した場合。 | +| requests.exceptions.HTTPError | レスポンスのステータスコードが 200 以外の場合。 | + +#### 処理内容 +1. リダイレクト URL を生成する。エンドポイント [`api.callback.auth_code`](../04.api/06.callback.md#auth_code) + に対応するスキーマとホスト名を含む完全な URL を `flask.url_for` を使用して取得する。 +2. mAP Core Authorization Server に対してリクエストを送信する。 + - エンドポイント [`MAP_OAUTH_TOKEN_ENDPOINT`](../02.foundation/03.const.md#map_oauth_token_endpoint) に対して、POST リクエストを送信する。 + - リクエストボディに以下のフォームデータを含める。 + | 名前 | 値 | + | ------------ | ------------------------- | + | grant_type | `"authorization_code"` | + | code | 引数 `code` | + | redirect_uri | ステップ 1 で生成した URL | + - ベーシック認証ヘッダに、引数 `credentials` の `client_id` および `client_secret` を使用する。 + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 +3. レスポンスのステータスコードが 200 以外の場合は `requests.exceptions.HTTPError` を送出する。 +4. レスポンスボディを [`OAuthToken`](../03.entities/07.auth.md#oauthtoken) モデルにデシリアライズして戻り値として返す。 + + +## refresh_oauth_token +リフレッシュトークンを使用して SP のアクセストークンを mAP Core Authorization Server から更新・取得する機能を提供する。 + +#### 対応エンドポイント +**POST** [/oauth/token.php](../02.foundation/03.const.md#map_oauth_token_endpoint) + +#### シグネチャ +```python[auth.py] +def refresh_oauth_token(refresh_token: str, credentials: ClientCreds) -> OAuthToken: +``` + +#### 依存するライブラリ +- **requests**: HTTP リクエストの送信に使用する。 +- **pydantic**: レスポンスのバリデーションおよびデシリアライズに使用。 + +#### 引数 + +| 名前 | 型 | 説明 | +| -------------------------------------- | ----------- | -------------------------------- | +| refresh_token | str | リフレッシュトークン | +| credentials | ClientCreds | クライアント認証情報オブジェクト | +|  ⤷   client_id | str | クライアントID | +|  ⤷   client_secret | str | クライアントシークレット | + +- 第二引数 `credentials` の型は `Protocol` で定義され、`client_id` および `client_secret` 属性を持つオブジェクトを受け取る。 + これに合致するオブジェクトとして、[`ClientCredentials`](../03.entities/07.auth.md#clientcredentials) がある。 + +#### 戻り値 + +| 型 | 説明 | +| -------------------------------------------------- | ------------------------------ | +| [OAuthToken](../03.entities/07.auth.md#oauthtoken) | 発行されたアクセストークン情報 | + +#### エラー + +| 型 | 説明 | +| ------------------------------------ | ----------------------------------------------- | +| requests.exceptions.RequestException | HTTP リクエストに失敗した場合。 | +| requests.exceptions.HTTPError | レスポンスのステータスコードが 200 以外の場合。 | + +#### 処理内容 +1. mAP Core Authorization Server に対してリクエストを送信する。 + - エンドポイント [`MAP_OAUTH_TOKEN_ENDPOINT`](../02.foundation/03.const.md#map_oauth_token_endpoint) に対して、POST リクエストを送信する。 + - リクエストボディに以下のフォームデータを含める。 + | 名前 | 値 | + | ------------- | -------------------- | + | grant_type | `"refresh_token"` | + | refresh_token | 引数 `refresh_token` | + - ベーシック認証ヘッダに、引数 `credentials` の `client_id` および `client_secret` を使用する。 + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 +2. レスポンスのステータスコードが 200 以外の場合は `requests.exceptions.HTTPError` を送出する。 +3. レスポンスボディを [`OAuthToken`](../03.entities/07.auth.md#oauthtoken) モデルにデシリアライズして戻り値として返す。 From 862c3edc2fa4d66e51b642a5d5c33a4fcc690cc1 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 05:56:38 +0000 Subject: [PATCH 021/173] docs: add placeholder for services --- .../01.detailed/05.services/02.repositories.md | 4 ++++ .../content/01.detailed/05.services/03.groups.md | 4 ++++ docs/content/01.detailed/05.services/04.users.md | 4 ++++ docs/content/01.detailed/05.services/05.bulk.md | 4 ++++ .../05.services/06.service_settings.md | 16 ++++++++-------- 5 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 docs/content/01.detailed/05.services/02.repositories.md create mode 100644 docs/content/01.detailed/05.services/03.groups.md create mode 100644 docs/content/01.detailed/05.services/04.users.md create mode 100644 docs/content/01.detailed/05.services/05.bulk.md diff --git a/docs/content/01.detailed/05.services/02.repositories.md b/docs/content/01.detailed/05.services/02.repositories.md new file mode 100644 index 00000000..77ce2284 --- /dev/null +++ b/docs/content/01.detailed/05.services/02.repositories.md @@ -0,0 +1,4 @@ +--- +title: services.repositories +description: 当システムのリポジトリ管理に関するビジネスロジックを提供する。 +--- diff --git a/docs/content/01.detailed/05.services/03.groups.md b/docs/content/01.detailed/05.services/03.groups.md new file mode 100644 index 00000000..c227db73 --- /dev/null +++ b/docs/content/01.detailed/05.services/03.groups.md @@ -0,0 +1,4 @@ +--- +title: services.groups +description: 当システムのグループ管理に関するビジネスロジックを提供する。 +--- diff --git a/docs/content/01.detailed/05.services/04.users.md b/docs/content/01.detailed/05.services/04.users.md new file mode 100644 index 00000000..68366e7e --- /dev/null +++ b/docs/content/01.detailed/05.services/04.users.md @@ -0,0 +1,4 @@ +--- +title: services.users +description: 当システムのユーザー管理に関するビジネスロジックを提供する。 +--- diff --git a/docs/content/01.detailed/05.services/05.bulk.md b/docs/content/01.detailed/05.services/05.bulk.md new file mode 100644 index 00000000..1ef3018e --- /dev/null +++ b/docs/content/01.detailed/05.services/05.bulk.md @@ -0,0 +1,4 @@ +--- +title: services.bulk +description: 当システムのリソースをまたぐ一括処理に関するビジネスロジックを提供する。 +--- diff --git a/docs/content/01.detailed/05.services/06.service_settings.md b/docs/content/01.detailed/05.services/06.service_settings.md index 9736188f..3c336941 100644 --- a/docs/content/01.detailed/05.services/06.service_settings.md +++ b/docs/content/01.detailed/05.services/06.service_settings.md @@ -70,16 +70,16 @@ def get_oauth_token() -> OAuthToken | None: #### 戻り値 -| 型 | 説明 | -| ------------------------------------------------ | ------------------------------------------------ | +| 型 | 説明 | +| ---------------------------------------------------------- | ---------------------------------------------------- | | [OAuthToken](../03.entities/07.auth.md#oauthtoken) \| None | アクセストークン情報。存在しない場合は None を返す。 | #### エラー -| 型 | 説明 | -| ------------------------------------------------------------- | ------------------------------------------------------ | -| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | -| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | | 取得された設定からアクセストークン情報を読み取れない場合。 | +| 型 | 説明 | +| ------------------------------------------------------------- | ---------------------------------------------------------- | +| [DatabaseError](../02.foundation/07.exc.md#databaseerror) | データベース操作で予期しないエラーが発生した場合。 | +| [OAuthTokenError](../02.foundation/07.exc.md#oauthtokenerror) | 取得された設定からアクセストークン情報を読み取れない場合。 | #### 処理内容 1. データベースからサービス設定情報を `"oauth_token"` キーで取得する。 @@ -99,8 +99,8 @@ def save_oauth_token(token: OAuthToken) -> None: #### 引数 -| 名前 | 型 | 説明 | -| ----- | ---------------------------------------------------- | -------------- | +| 名前 | 型 | 説明 | +| ----- | -------------------------------------------------- | -------------------- | | token | [OAuthToken](../03.entities/07.auth.md#oauthtoken) | アクセストークン情報 | #### エラー From 6175dcc80bcd743d4ed21e9a7f7e4c708b946cc5 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Fri, 16 Jan 2026 07:32:13 +0000 Subject: [PATCH 022/173] docs: fix format and wording in dd --- docs/content/01.detailed/04.api/05.users.md | 10 ++-- .../01.detailed/06.clients/04.users.md | 50 +++++++++---------- docs/content/01.detailed/index.md | 10 ++-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/content/01.detailed/04.api/05.users.md b/docs/content/01.detailed/04.api/05.users.md index 6bae16b5..e54a423a 100644 --- a/docs/content/01.detailed/04.api/05.users.md +++ b/docs/content/01.detailed/04.api/05.users.md @@ -61,7 +61,7 @@ def get(query: UsersQuery) -> tuple[UsersResult | ErrorResponse, int]: 1. リクエストコンテキストからログインユーザー情報を取得する。 2. `build_user_search_criteria` を呼び出し、引数 `query` とログインユーザーのロールに基づいた `UserSearchCriteria` オブジェクトを構築する。 検索条件にロールの権限を越える条件が含まれている場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 403 とともに返す。 -3. `UserSearchCriteria` オブジェクトをもとに、`services.users:search` を呼び出してユーザーの検索結果を取得する。 +3. `UserSearchCriteria` オブジェクトをもとに、`services.users:search` を呼び出し、ユーザーの検索結果を取得する。 4. 検索処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 5. 検索結果を `UsersResult` オブジェクトに格納し、ステータスコード 200 とともに返す。 @@ -116,7 +116,7 @@ def post(body: UserDetail) -> tuple[UserDetail, int, dict[str, str]] | tuple[Err - システム管理者の場合、すべてのユーザーを作成できる。 - リポジトリ管理者の場合、ログインユーザーが管理するリポジトリに所属するユーザーのみ作成できる。 すべての所属リポジトリがログインユーザーが管理するリポジトリに含まれていなければ、ステータスコード 403 とともに返す。 -4. 引数 `body` をもとに、`services.users:create` を呼び出してユーザーを作成する。 +4. 引数 `body` をもとに、`services.users:create` を呼び出し、ユーザーを作成する。 5. ユーザー作成処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 6. 作成されたユーザー情報を `UserDetail` オブジェクトとして、ステータスコード 201 および `Location` ヘッダーとともに返す。 `Location` ヘッダーには作成されたユーザーの情報取得 API の絶対 URL を設定する。 @@ -165,7 +165,7 @@ def id_get(user_id: str) -> tuple[UserDetail | ErrorResponse, int]: **処理内容** 1. リクエストコンテキストからログインユーザー情報を取得する。 -2. 引数 `user_id` をもとに、`services.users:get_by_id` を呼び出してユーザー情報を取得する。 +2. 引数 `user_id` をもとに、`services.users:get_by_id` を呼び出し、ユーザー情報を取得する。 3. ユーザーが存在しなかった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 4. 取得に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 5. 取得されたユーザー情報とログインユーザーのロールに基づき、ユーザー情報へのアクセスできるかを確認する。 @@ -224,7 +224,7 @@ def id_put(user_id: str, body: UserDetail) -> tuple[UserDetail | ErrorResponse, - システム管理者の場合、すべてのユーザーを更新できる。 - リポジトリ管理者の場合、ログインユーザーが管理するリポジトリに所属するユーザーのみ更新できる。 すべての所属リポジトリがログインユーザーの管理するリポジトリに含まれていない場合、ステータスコード 403 とともに返す。 -3. 引数 `body` をもとに、`services.users:update` を呼び出してユーザー情報を更新する。 +3. 引数 `body` をもとに、`services.users:update` を呼び出し、ユーザー情報を更新する。 4. ユーザーが存在しない場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 5. 更新内容に競合があった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 409 とともに返す。 6. ユーザー更新処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 @@ -272,7 +272,7 @@ def id_delete(user_id: str) -> tuple[ Literal[""] | ErrorResponse, int]: | tuple\[ErrorResponse, Literal\[500]] | ユーザー削除処理に失敗した場合のエラーレスポンス | #### 処理内容 -1. 引数 `user_id` をもとに、`services.users:delete` を呼び出してユーザーを削除する。 +1. 引数 `user_id` をもとに、`services.users:delete` を呼び出し、ユーザーを削除する。 2. ユーザーが存在しなかった場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 404 とともに返す。 3. ユーザー削除処理に失敗した場合、`ErrorResponse` オブジェクトを作成し、ステータスコード 500 とともに返す。 4. 正常にユーザーが削除された場合、空文字列とステータスコード 204 とともに返す。 diff --git a/docs/content/01.detailed/06.clients/04.users.md b/docs/content/01.detailed/06.clients/04.users.md index 6af429ec..ddc1f1dc 100644 --- a/docs/content/01.detailed/06.clients/04.users.md +++ b/docs/content/01.detailed/06.clients/04.users.md @@ -7,7 +7,7 @@ description: mAP Core API V2 の User Resource API を操作するクライア ユーザー ID に基づいて mAP Core API からユーザー情報を取得する機能を提供する。 #### 対応エンドポイント -**GET** /api/v2/Users/{id} +**GET** [/api/v2/Users](../02.foundation/03.const.md#map_users_endpoint)/{id} #### シグネチャ ```python[users.py] @@ -67,7 +67,7 @@ def get_by_id( | ------------- | --------------------------------------------------------- | | Content-Type | `application/json`( **requests** により自動設定される) | | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | - - サーバー設定値に従い、タイムアウト値を設定する。 + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 - ステータスコードが 200 の場合、レスポンスボディを [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 @@ -81,7 +81,7 @@ def get_by_id( EPPN に基づいて mAP Core API からユーザー情報を取得する機能を提供する。 #### 対応エンドポイント -**GET** /api/v2/Existeppn/{eppn} +**GET** [/api/v2/Existeppn](../02.foundation/03.const.md#map_exist_eppn_endpoint)/{eppn} #### シグネチャ ```python[users.py] @@ -142,7 +142,7 @@ def get_by_eppn( | ------------- | --------------------------------------------------------- | | Content-Type | `application/json`( **requests** により自動設定される) | | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | - - サーバー設定値に従い、タイムアウト値を設定する。 + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 - ステータスコードが 200 の場合、レスポンスボディを [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 @@ -156,7 +156,7 @@ def get_by_eppn( mAP Core API に対してユーザー情報を登録する機能を提供する。 #### 対応エンドポイント -**POST** /api/v2/Users +**POST** [/api/v2/Users](../02.foundation/03.const.md#map_users_endpoint) #### シグネチャ ```python[users.py] @@ -211,12 +211,12 @@ def post( | Content-Type | `application/json`( **requests** により自動設定される) | | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 - | 名前 | 値 | - | ---------------------------------- | -------------------- | - | request | 認証情報オブジェクト | - |     time_stamp | タイムスタンプ | - |     signature | 署名 | - - サーバー設定値に従い、タイムアウト値を設定する。 + | 名前 | 値 | + | ----------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |  ⤷   time_stamp | タイムスタンプ | + |  ⤷   signature | 署名 | + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 - ステータスコードが 201 の場合、レスポンスボディを [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 @@ -230,7 +230,7 @@ def post( ユーザー ID に基づいて mAP Core API 上のユーザー情報を更新する機能を提供する。(実装未定) #### 対応エンドポイント -**PUT** /api/v2/Users/{id} +**PUT** [/api/v2/Users](../02.foundation/03.const.md#map_users_endpoint)/{id} #### シグネチャ ```python[users.py] @@ -286,12 +286,12 @@ def put_by_id( | Content-Type | `application/json`( **requests** により自動設定される) | | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 - | 名前 | 値 | - | ---------------------------------- | -------------------- | - | request | 認証情報オブジェクト | - |     time_stamp | タイムスタンプ | - |     signature | 署名 | - - サーバー設定値に従い、タイムアウト値を設定する。 + | 名前 | 値 | + | ----------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |  ⤷   time_stamp | タイムスタンプ | + |  ⤷   signature | 署名 | + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 5. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 - ステータスコードが 200 の場合、レスポンスボディを [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 @@ -305,7 +305,7 @@ def put_by_id( ユーザー ID に基づいて mAP Core API 上のユーザー情報を部分更新する機能を提供する。 #### 対応エンドポイント -**PATCH** /api/v2/Users/{id} +**PATCH** [/api/v2/Users](../02.foundation/03.const.md#map_users_endpoint)/{id} #### シグネチャ ```python[users.py] @@ -364,12 +364,12 @@ def patch_by_id( | Content-Type | `application/json`( **requests** により自動設定される) | | Authorization | アクセストークン。 `"Bearer "` の形式で付与 | - リクエストボディに、引数 `user` をシリアライズし、以下のフィールドを追加した JSON を設定する。 - | 名前 | 値 | - | ---------------------------------- | -------------------- | - | request | 認証情報オブジェクト | - |     time_stamp | タイムスタンプ | - |     signature | 署名 | - - サーバー設定値に従い、タイムアウト値を設定する。 + | 名前 | 値 | + | ----------------------------------- | -------------------- | + | request | 認証情報オブジェクト | + |  ⤷   time_stamp | タイムスタンプ | + |  ⤷   signature | 署名 | + - サーバー設定値 [`config.MAP_CORE`](../02.foundation/02.config.md#mapcoreconfig) の `timeout` に従い、タイムアウトを設定する。 6. レスポンスを解析し、HTTPステータスコードに応じた処理を行う。 - ステータスコードが 200 の場合、レスポンスボディを [`MapUser`](../03.entities/03.map_user.md#mapuser) のインスタンスにデシリアライズする。 diff --git a/docs/content/01.detailed/index.md b/docs/content/01.detailed/index.md index 2a816b01..3f03114a 100644 --- a/docs/content/01.detailed/index.md +++ b/docs/content/01.detailed/index.md @@ -22,9 +22,9 @@ headline: JAIRO Cloud Groups Manager ## 主要な依存ライブラリ -- **Flask**: Webアプリケーションフレームワーク。REST APIやルーティング、リクエスト管理を提供する。 -- **SQLAlchemy**: ORM。DB接続・モデル定義・クエリ発行を担当する。 +- **Flask**: Web アプリケーションフレームワーク。REST API やルーティング、リクエスト管理を提供する。 +- **SQLAlchemy**: ORM。データベース接続・モデル定義・クエリ発行を担当する。 - **Celery**: 分散タスクキュー。非同期処理(例:グループ作成・更新のバックグラウンド処理)を実現する。 -- **Pydantic**: JSONおよびPythonオブジェクト間の型安全なシリアライズ・デシリアライズを行う。 -- **Redis**: Celeryのバックエンド、セッション管理、キャッシュストアとして利用する。 -- **amqp (RabbitMQ)**: Celeryのメッセージブローカーとして使用する。 +- **Pydantic**: JSON および Python オブジェクト間の型安全なシリアライズ・デシリアライズを行う。 +- **Redis**: Celery のバックエンド、セッション管理、キャッシュストアとして利用する。 +- **amqp (RabbitMQ)**: Celery のメッセージブローカーとして使用する。 From fc2b1cc68fd90eff8a5282e52ea131250a6ffbdf Mon Sep 17 00:00:00 2001 From: ryoya-hayase Date: Tue, 27 Jan 2026 01:20:18 +0000 Subject: [PATCH 023/173] add cache database update functionality --- configs/app.config.ts | 10 + configs/server.config.toml | 21 ++ nuxt.config.ts | 4 + pyproject.toml | 4 + src/app/components/list/ListRepositories.vue | 106 ++++++++ src/app/composables/cacheGroups.ts | 74 ++++++ src/app/i18n/locales/en.json | 35 ++- src/app/i18n/locales/ja.json | 35 ++- src/app/pages/cache-groups/index.vue | 258 ++++++++++++++++++- src/app/types/cacheGroups.ts | 38 +++ src/app/types/search.ts | 9 +- src/app/utils/search.ts | 15 +- src/server/api/cache_groups.py | 124 +++++++++ src/server/api/router.py | 4 +- src/server/api/schemas.py | 39 +++ src/server/config.py | 54 ++++ src/server/entities/cache.py | 79 ++++++ src/server/ext.py | 2 + src/server/services/cache_groups.py | 255 ++++++++++++++++++ uv.lock | 107 ++++++++ 20 files changed, 1267 insertions(+), 6 deletions(-) create mode 100644 src/app/components/list/ListRepositories.vue create mode 100644 src/app/composables/cacheGroups.ts create mode 100644 src/app/types/cacheGroups.ts create mode 100644 src/server/api/cache_groups.py create mode 100644 src/server/entities/cache.py create mode 100644 src/server/services/cache_groups.py diff --git a/configs/app.config.ts b/configs/app.config.ts index 0ffabd74..8d175a83 100644 --- a/configs/app.config.ts +++ b/configs/app.config.ts @@ -36,6 +36,7 @@ const table = { repositories: [20, 50, 100] as number[], groups: [20, 50, 100] as number[], users: [20, 50, 100] as number[], + cacheGroups: [20, 50, 100] as number[], }, } @@ -123,6 +124,13 @@ const wayf = { ] as { name: string, entityID: string }[], } +const groupCache = { + /** + * Time (in milliseconds) to wait before showing loading indicator when updating cache + */ + loadingWaitTime: 3000 as number, +} + export default { /** Server hostname of this application */ serverName, @@ -134,4 +142,6 @@ export default { table, /** WAYF (Embedded DS) configuration */ wayf, + /** Group cache configuration */ + groupCache, } diff --git a/configs/server.config.toml b/configs/server.config.toml index a3bfe67f..51afeb7a 100644 --- a/configs/server.config.toml +++ b/configs/server.config.toml @@ -159,3 +159,24 @@ port = 26379 [rabbitmq] # Hostname or IP address of the RabbitMQ server for Celery broker. url = "amqp://guest:guest@rabbitmq:5672//" + + +[cache_groups] +# Redis key pattern for caching group data. +cache_redis_key = "{prefix}cache" + +# Redis key pattern for caching GakuNin group data. +gakunin_redis_key = "{fqdn}_gakunin_groups" + +# Map groups API endpoint. +map_groups_api_endpoint = "https://sample.gakunin.jp/api/groups/" + +# If toml_path and either directory_path or fqdn_list_file are defined, toml_path takes precedence. +# Schema for cache database configuration. +toml_path = "cache_db_config.toml" + +# Path to the directory containing institution TLS files. +directory_path = "./cache_db/tls" + +# Path to the file containing FQDN list. +fqdn_list_file = "fqdn_list.toml" diff --git a/nuxt.config.ts b/nuxt.config.ts index 2e34a912..0bfaf079 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -9,6 +9,10 @@ export default defineNuxtConfig({ '@nuxt/test-utils', ], + imports: { + dirs: ['~/types'], + }, + devtools: { enabled: true, }, diff --git a/pyproject.toml b/pyproject.toml index 840b53b0..a8d675d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ dependencies = [ "pydantic[email]>=2.12.5", "requests>=2.32.5", "sqlalchemy-utils>=0.42.1", + "weko-group-cache-db", ] [dependency-groups] @@ -88,3 +89,6 @@ skip-magic-trailing-comma = false [tool.pyright] extraPaths = ["src/server"] typeCheckingMode = "standard" + +[tool.uv.sources] +weko-group-cache-db = { git = "https://github.com/ivis-weko3-dev/weko-group-cache-db.git", rev = "develop" } diff --git a/src/app/components/list/ListRepositories.vue b/src/app/components/list/ListRepositories.vue new file mode 100644 index 00000000..71f8cd0a --- /dev/null +++ b/src/app/components/list/ListRepositories.vue @@ -0,0 +1,106 @@ + + + diff --git a/src/app/composables/cacheGroups.ts b/src/app/composables/cacheGroups.ts new file mode 100644 index 00000000..0c3eca96 --- /dev/null +++ b/src/app/composables/cacheGroups.ts @@ -0,0 +1,74 @@ +/** + * Composable for managing cache groups. + */ +import type { TableColumn } from '@nuxt/ui' + +const useCacheGroups = () => { + const route = useRoute() + const router = useRouter() + + const { t: $t } = useI18n() + + /** Reactive query object */ + const query = computed(() => normalizeCacheGroupsQuery(route.query)) + /** Update query parameters and push to router */ + const updateQuery = (newQuery: Partial) => { + router.push({ + query: { + ...route.query, + ...newQuery, + }, + }) + } + + const searchTerm = ref(query.value.q) + const filter = ref(query.value.f) + const pageSize = ref(query.value.l) + + const searchIdentityKey = computed(() => { + const { p, l, ...filters } = query.value + return JSON.stringify(filters) + }) + + const selectedMap = useState>( + `selection:${searchIdentityKey.value}`, () => ({}), + ) + + /** Column names with translations */ + const columnNames = { + id: '#', + displayName: $t('cache-groups.table.column.repository-name'), + serviceURL: $t('cache-groups.table.column.repository-url'), + updated: $t('cache-groups.table.column.repository-updated-at'), + } + + type CacheGroupsTableColumn = TableColumn + const columns = computed(() => [ + { + accessorKey: 'name', + header: columnNames.displayName, + }, + { + accessorKey: 'url', + header: columnNames.serviceURL, + }, + { + accessorKey: 'updated', + header: columnNames.updated, + }, + ]) + + return { + query, + updateQuery, + criteria: { + searchTerm, + filter, + pageSize, + }, + selectedMap, + columns, + } +} + +export { useCacheGroups } diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index f6ccdbc7..3a533512 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -1,12 +1,38 @@ { "button": { + "cancel": "Cancel", "create-new": "Create new", "reload": "Reload", + "update": "Update", "upload-users": "Upload" }, "cache-groups": { + "button": { + "update-all-repositories": "Update all repositories", + "update-selected-repositories": "Update selected repositories" + }, + "confirm-update-all-repositories": "Do you want to update all repositories ({count})?", + "confirm-update-selected-repositories": "Would you like to update the following {count} repositories?", + "count": "{current} / {total} items", "description": "You can manage the cache status of group information used by WEKO3.", - "title": "Caching Groups" + "filter-placeholder": "Cache status", + "search-placeholder": "search...", + "select": { + "cache": "With cash", + "no-cache": "No cache" + }, + "table": { + "column": { + "repository-name": "Repository name", + "repository-status": "Update status", + "repository-updated-at": "Cache update date and time", + "repository-url": "Repository URL" + } + }, + "title": "Caching Groups", + "update-completed": "Update completed ({count} items)", + "update-error": "Cache update processing is in progress.", + "updating": "Updating..." }, "footer": { "privacy-policy": "Privacy policy", @@ -29,6 +55,10 @@ }, "repositories": { "description": "You can check information about the repositories you manage.", + "list": { + "no-repositories-description": "Please change your search criteria and try again.", + "no-repositories-title": "Repository not found" + }, "title": "Repositories" }, "table": { @@ -37,6 +67,9 @@ }, "actions-label": "Action", "display-columns-label": "Columns", + "display-count-label": "Number of items displayed:", + "display-info-text": "Displaying {start}~{end} items (all {total} items)", + "display-info-text-empty": "Displaying 0 items (all 0 items)", "page-size-label": "Page size:" }, "user": { diff --git a/src/app/i18n/locales/ja.json b/src/app/i18n/locales/ja.json index 0b5267cd..219fbcc5 100644 --- a/src/app/i18n/locales/ja.json +++ b/src/app/i18n/locales/ja.json @@ -1,12 +1,38 @@ { "button": { + "cancel": "キャンセル", "create-new": "新規作成", "reload": "再読み込み", + "update": "更新", "upload-users": "アップロード" }, "cache-groups": { + "button": { + "update-all-repositories": "全リポジトリを更新", + "update-selected-repositories": "選択したリポジトリを更新" + }, + "confirm-update-all-repositories": "全リポジトリ({count}件)の更新を行いますか?", + "confirm-update-selected-repositories": "以下の{count}リポジトリを更新しますか?", + "count": "{current} / {total} 件", "description": "WEKO3が利用するグループ情報のキャッシュ状況を管理できます。", - "title": "グループキャッシュ" + "filter-placeholder": "キャッシュ状態", + "search-placeholder": "検索...", + "select": { + "cache": "キャッシュあり", + "no-cache": "キャッシュなし" + }, + "table": { + "column": { + "repository-name": "リポジトリ名", + "repository-status": "更新状況", + "repository-updated-at": "キャッシュ更新日時", + "repository-url": "リポジトリURL" + } + }, + "title": "グループキャッシュ", + "update-completed": "更新完了({count}件)", + "update-error": "キャッシュ更新処理が実行中です。", + "updating": "更新中..." }, "footer": { "privacy-policy": "プライバシーポリシー", @@ -29,6 +55,10 @@ }, "repositories": { "description": "あなたが管理しているリポジトリの情報を確認できます。", + "list": { + "no-repositories-description": "検索条件を変更して、再度お試しください。", + "no-repositories-title": "リポジトリが見つかりません" + }, "title": "リポジトリ" }, "table": { @@ -37,6 +67,9 @@ }, "actions-label": "アクション", "display-columns-label": "表示項目", + "display-count-label": "表示件数:", + "display-info-text": "{start}~{end}件を表示(全{total}件)", + "display-info-text-empty": "0件を表示(全0件)", "page-size-label": "表示件数:" }, "user": { diff --git a/src/app/pages/cache-groups/index.vue b/src/app/pages/cache-groups/index.vue index f57787e7..36f3fa62 100644 --- a/src/app/pages/cache-groups/index.vue +++ b/src/app/pages/cache-groups/index.vue @@ -1,11 +1,267 @@ diff --git a/src/app/types/cacheGroups.ts b/src/app/types/cacheGroups.ts new file mode 100644 index 00000000..1b33e0fd --- /dev/null +++ b/src/app/types/cacheGroups.ts @@ -0,0 +1,38 @@ +/** + * Types related to cache groups + */ + +/** Cache group summary information */ +interface CacheGroupsSummary { + id: string + name: string + url: string + updated: string +} + +/** Search result for cache groups */ +interface CacheGroupSearchResult { + total: number + pageSize: number + offset: number + resources: CacheGroupsSummary[] +} + +/** Cache groups update result */ +interface CacheGroupsUpdateResult { + type: string + fqdn: string + status: string + code?: string + repository_cached?: CacheGroupsSummary[] +} + +/** Detail information of a cache groups update task */ +interface TaskDetail { + results: CacheGroupsUpdateResult[] + current: string + done: number + total: number +} + +export type { CacheGroupsSummary, CacheGroupSearchResult, CacheGroupsUpdateResult, TaskDetail } diff --git a/src/app/types/search.ts b/src/app/types/search.ts index 993a8b5b..ed90b006 100644 --- a/src/app/types/search.ts +++ b/src/app/types/search.ts @@ -17,6 +17,13 @@ interface UsersSearchQuery { l?: number } +interface CacheGroupsSearchQuery { + q?: string + f?: string[] + p?: number + l?: number +} + type UsersSortableKeys = 'id' | 'userName' | 'emails' | 'eppns' | 'lastModified' type SortOrder = 'asc' | 'desc' @@ -39,7 +46,7 @@ type GroupsSearchResult = SearchResult type UsersSearchResult = SearchResult export type { - UsersSearchQuery, UsersSortableKeys, + UsersSearchQuery, CacheGroupsSearchQuery, UsersSortableKeys, SortOrder, SearchResult, UsersSearchResult, GroupsSearchResult, RepositoriesSearchResult, } diff --git a/src/app/utils/search.ts b/src/app/utils/search.ts index b9d40e6c..e1b9d3e4 100644 --- a/src/app/utils/search.ts +++ b/src/app/utils/search.ts @@ -37,4 +37,17 @@ const normalizeUsersQuery = (query: LocationQuery): UsersSearchQuery => { } } -export { normalizeUsersQuery } +/** + * Normalize location query to cache groups search query + */ +const normalizeCacheGroupsQuery = (query: LocationQuery): CacheGroupsSearchQuery => { + const { table: { pageSize } } = useAppConfig() + return { + q: query.q ? pickSingle(query.q) : undefined, + f: query.f ? toArray(query.f) : undefined, + p: Number(query.p) || 1, + l: Number(query.l) || pageSize.cacheGroups?.[0], + } +} + +export { normalizeUsersQuery, normalizeCacheGroupsQuery } diff --git a/src/server/api/cache_groups.py b/src/server/api/cache_groups.py new file mode 100644 index 00000000..96d873ce --- /dev/null +++ b/src/server/api/cache_groups.py @@ -0,0 +1,124 @@ +# +# Copyright (C) 2025 National Institute of Informatics. +# +"""API router for cache group endpoints.""" + +from ast import literal_eval + +from flask import Blueprint +from flask_login import login_required +from flask_pydantic import validate + +from server.api.helper import roles_required +from server.api.schemas import CacheQuery, CacheRequest, RepositoriesQuery +from server.config import config +from server.datastore import app_cache +from server.entities.cache import CacheResult, RepositoryCache, TaskDetail +from server.entities.search_request import SearchResult +from server.services.cache_groups import get_repository_cache, update +from server.services.repositories import search + + +bp = Blueprint("cache-groups", __name__) + + +@bp.get("/") +@login_required +@roles_required("system_admin") +@validate(response_by_alias=True) +def get(query: CacheQuery) -> SearchResult[RepositoryCache]: + """Retrieve repository cache entries based on the provided query. + + Args: + query (CacheQuery): Query parameters for filtering and pagination. + + Returns: + list[RepositoryCache]: List of repository cache entries. + """ + repository_query = RepositoriesQuery( + q=query.q, + k="id", + d="asc", + p=query.p if not query.f else -1, + l=query.l, + ) + + repositories = search(repository_query) + + return get_repository_cache(repositories, query) + + +@bp.post("/") +@login_required +@roles_required("system_admin") +@validate() +def post(body: CacheRequest) -> str: + """Update cache groups for the specified FQDNs. + + Args: + body (CacheRequest): Request body containing FQDNs and operation. + + Returns: + str: Success message. + """ + fqdn_list = body.fqdn_list or [] + update(fqdn_list, body.op) + return "Success" + + +@bp.get("/task") +@login_required +@roles_required("system_admin") +@validate() +def get_task_status() -> TaskDetail: + """Get the status of the cache update task. + + Returns: + TaskDetail: Details of the cache update task. + """ + cache_key = config.CACHE_GROUPS.cache_redis_key.format( + prefix=config.REDIS.key_prefix + ) + task_status = app_cache.hgetall(cache_key) + decode_task_status = {k.decode(): v.decode() for k, v in task_status.items()} # pyright: ignore[reportAttributeAccessIssue] + results = {} + current = "" + done = 0 + total = 0 + for key in decode_task_status: # noqa: PLC0206 + if key == "current": + current = decode_task_status[key] # pyright: ignore[reportIndexIssue] + elif key == "done": + done = int(decode_task_status[key]) # pyright: ignore[reportIndexIssue] + elif key == "total": + total = int(decode_task_status[key]) # pyright: ignore[reportIndexIssue] + else: + result = literal_eval(decode_task_status[key]) # pyright: ignore[reportIndexIssue] + fqdn = key.split("_")[0] + repository_query = RepositoriesQuery( + q=f"/{fqdn}/", + k="id", + d="asc", + p=-1, + l=1, + ) + repository = search(repository_query).resources[0] + results[fqdn] = CacheResult( + type="cache", + fqdn=fqdn, + status=result["status"], + code=result.get("code"), + repository_cached=RepositoryCache( + id=repository.id, + name=repository.display_name, # pyright: ignore[reportArgumentType] + url=str(repository.service_url), + updated=result.get("updated"), + ), + ) + if total > 0 and total == done: + # Clear the task status when the task is completed. + app_cache.delete(cache_key) + + return TaskDetail( + results=list(results.values()), current=current, done=done, total=total + ) diff --git a/src/server/api/router.py b/src/server/api/router.py index c0621df2..31937ea1 100644 --- a/src/server/api/router.py +++ b/src/server/api/router.py @@ -23,7 +23,9 @@ def create_api_blueprint() -> Blueprint: module = import_module(f"{__package__}.{module_name}") if hasattr(module, "bp") and isinstance(module.bp, Blueprint): bp_api.register_blueprint( - module.bp, url_prefix=f"/{module_name}", strict_slashes=False + module.bp, + url_prefix=f"/{module_name.replace('_', '-')}", + strict_slashes=False, ) return bp_api diff --git a/src/server/api/schemas.py b/src/server/api/schemas.py index 729511bd..66c4cc29 100644 --- a/src/server/api/schemas.py +++ b/src/server/api/schemas.py @@ -12,8 +12,16 @@ from datetime import date from pydantic import BaseModel, ConfigDict +from pydantic.alias_generators import to_camel +camel_case_config = ConfigDict( + validate_assignment=True, + alias_generator=to_camel, + validate_by_name=True, + validate_by_alias=True, +) + ignore_extra_config = ConfigDict( extra="ignore", validate_assignment=True, @@ -167,3 +175,34 @@ class UsersQuery(BaseModel): model_config = ignore_extra_config """Configure to ignore extra fields.""" + + +class CacheQuery(BaseModel): + """Schema for cache query parameters.""" + + q: t.Annotated[str | None, "term"] = None + """Search term for querying cache entries.""" + + f: t.Annotated[list[str] | None, "filter"] = None + """Filter expression for querying cache entries.""" + + p: t.Annotated[int | None, "page"] = None + """Page number for pagination.""" + + l: t.Annotated[int | None, "per"] = None + """Number of items per page for pagination.""" + + +type CacheOperation = t.Literal["all", "id-specified"] + + +class CacheRequest(BaseModel): + """Schema for cache update request.""" + + fqdn_list: list[str] | None = None + """List of fully qualified domain names to update in the cache.""" + + op: CacheOperation + """Operation type: 'all' to update all, 'id-specified' to update specified FQDNs.""" + + model_config = camel_case_config diff --git a/src/server/config.py b/src/server/config.py index c89972c5..eba11a41 100644 --- a/src/server/config.py +++ b/src/server/config.py @@ -81,6 +81,38 @@ class RuntimeConfig(BaseSettings): RABBITMQ: RabbitmqConfig """RabbitMQ configuration values.""" + CACHE_GROUPS: CacheGroupsConfig + """Cache groups task configuration values.""" + + @computed_field + @property + def CACHE_DB(self) -> dict[str, t.Any]: + """Cache database configuration values.""" + return { + "CACHE_KEY_SUFFIX": self.CACHE_GROUPS.gakunin_redis_key, + "MAP_GROUPS_API_ENDPOINT": self.CACHE_GROUPS.map_groups_api_endpoint, + "REDIS_TYPE": "redis" + if self.REDIS.cache_type == "RedisCache" + else "sentinel", + "REDIS_HOST": self.REDIS.single.base_url.replace("redis://", "").split(":")[ + 0 + ] + if self.REDIS.cache_type == "RedisCache" + else "", + "REDIS_PORT": int( + self.REDIS.single.base_url.replace("redis://", "").split(":")[1] + ) + if self.REDIS.cache_type == "RedisCache" + else 0, + "REDIS_DB_INDEX": self.REDIS.database.group_cache, + "REDIS_SENINEL_MASTER: ": self.REDIS.sentinel.master_name + if self.REDIS.cache_type == "RedisSentinelCache" + else "", + "SENTINELS": self.REDIS.sentinel.sentinels + if self.REDIS.cache_type == "RedisSentinelCache" + else [], + } + @computed_field @property def SQLALCHEMY_DATABASE_URI(self) -> URL: @@ -393,6 +425,28 @@ class RabbitmqConfig(BaseModel): """Hostname or IP address of the RabbitMQ server for Celery broker.""" +class CacheGroupsConfig(BaseModel): + """Schema for cache groups configuration.""" + + cache_redis_key: str + """Redis key for cache groups.""" + + gakunin_redis_key: str + """Redis key pattern for caching GakuNin group data.""" + + map_groups_api_endpoint: str + """Map groups API endpoint.""" + + toml_path: str + """Path to the TOML file with cache database configuration.""" + + directory_path: str + """Path to the directory containing institution TLS files.""" + + fqdn_list_file: str + """Path to the file containing FQDN list.""" + + type HasRepoId = t.Annotated[str, StringConstraints(pattern=HAS_REPO_ID_PATTERN)] """Pattern for role-based group IDs. diff --git a/src/server/entities/cache.py b/src/server/entities/cache.py new file mode 100644 index 00000000..849330f1 --- /dev/null +++ b/src/server/entities/cache.py @@ -0,0 +1,79 @@ +# +# Copyright (C) 2025 National Institute of Informatics. +# + +"""Models for updating cache db entities.""" + +import typing as t + +from datetime import datetime + +from pydantic import BaseModel + +from .common import camel_case_config, forbid_extra_config + + +class RepositoryCache(BaseModel): + """Model for repository cache entity.""" + + id: str + """The unique identifier for the repository.""" + + name: str + """The name of the repository.""" + + url: str + """The URL of the repository.""" + + updated: datetime | None = None + """The update timestamp of the repository cache entry.""" + + model_config = camel_case_config | forbid_extra_config + """Configure to use camelCase aliasing and forbid extra fields.""" + + +class TaskDetail(BaseModel): + """Model for task detail entity.""" + + results: list[Result] + """The list of results from the task.""" + + current: str + """Identifier of the object currently being processed.""" + + done: int + """The number of completed items.""" + + total: int + """The total number of items to process.""" + + model_config = camel_case_config | forbid_extra_config + """Configure to use camelCase aliasing and forbid extra fields.""" + + +type TaskStatus = t.Literal["success", "failed"] + + +class CacheResult(BaseModel): + """Model for cache update result.""" + + type: t.Literal["cache"] + """The type of the result, always "cache".""" + + fqdn: str + """The fully qualified domain name of the cached repository.""" + + status: TaskStatus + """The status of the cache update task.""" + + code: str | None = None + """The result code of the cache update task.""" + + repository_cached: RepositoryCache | None = None + """The cached repository information.""" + + model_config = camel_case_config | forbid_extra_config + """Configure to use camelCase aliasing and forbid extra fields.""" + + +Result = CacheResult diff --git a/src/server/ext.py b/src/server/ext.py index 2e86e5fd..94792171 100644 --- a/src/server/ext.py +++ b/src/server/ext.py @@ -6,6 +6,7 @@ import typing as t +from weko_group_cache_db.config import setup_config as setup_weko_group_cache_db_config from .api.router import create_api_blueprint from .cli.base import register_cli_commands from .config import RuntimeConfig, setup_config @@ -74,6 +75,7 @@ def init_config(self, app: Flask) -> None: app.config.from_mapping(self.config.for_flask) app.config.from_prefixed_env() + setup_weko_group_cache_db_config(self.config.CACHE_DB) def init_db_app(self, app: Flask) -> None: # noqa: PLR6301 """Initialize the database for the this extension. diff --git a/src/server/services/cache_groups.py b/src/server/services/cache_groups.py new file mode 100644 index 00000000..88509366 --- /dev/null +++ b/src/server/services/cache_groups.py @@ -0,0 +1,255 @@ +# +# Copyright (C) 2025 National Institute of Informatics. +# + +"""Service module for managing cache groups.""" + +import typing as t + +from datetime import datetime + +from celery import shared_task +from weko_group_cache_db.groups import fetch_all, fetch_one +from weko_group_cache_db.signal import update_count_signal, update_result_signal + +from server.api.schemas import CacheOperation, CacheQuery +from server.config import config +from server.datastore import app_cache, group_cache +from server.entities.search_request import SearchResult + + +if t.TYPE_CHECKING: + from server.entities.cache import RepositoryCache + from server.entities.summaries import RepositorySummary + + +def get_repository_cache( + repositories: SearchResult[RepositorySummary], query: CacheQuery +) -> SearchResult[RepositoryCache]: + """Retrieve repository cache entries based on the provided query. + + Args: + repositories (SearchResult[RepositorySummary]): List of repository summaries. + query (CacheQuery): Query parameters for filtering and pagination. + + Returns: + SearchResult[RepositoryCache]: List of repository cache entries. + """ + + def _get_repository_redis_key(fqdn: str) -> str: + replaced_fqdn = fqdn.replace("-", "_").replace(".", "_") + return config.CACHE_GROUPS.gakunin_redis_key.format(fqdn=replaced_fqdn) + + repository_cache_list = [] + cursor = 0 + redis_key_list = [] + while True: + cursor, keys = group_cache.scan( + cursor=cursor, + match=config.CACHE_GROUPS.gakunin_redis_key.format(fqdn="*"), + count=100, + ) # pyright: ignore[reportGeneralTypeIssues] + redis_key_list.extend([key.decode("utf-8") for key in keys]) + if cursor == 0: + break + + # filter by cache existence + if query.f: + exist_cache_repository_list = [] + non_exist_cache_repository_list = [] + start_index = (query.p - 1) * query.l if query.p and query.l else 0 + end_index = start_index + query.l if query.l else len(repositories.resources) + + for repository in repositories.resources: + repository_redis_key = _get_repository_redis_key( + repository.service_url.host # pyright: ignore[reportOptionalMemberAccess, reportArgumentType] + ) + repository_updated = None + if repository_redis_key in redis_key_list: + repository_updated = group_cache.hget( + repository_redis_key, "updated_at" + ) + + repo_cache = RepositoryCache( + id=repository.id, + name=repository.display_name, # pyright: ignore[reportArgumentType], + url=str(repository.service_url), + updated=datetime.fromisoformat(repository_updated), # pyright: ignore[reportArgumentType] + ) + exist_cache_repository_list.append(repo_cache) + else: + repo_cache = RepositoryCache( + id=repository.id, + name=repository.display_name, # pyright: ignore[reportArgumentType], + url=str(repository.service_url), + updated=None, + ) + non_exist_cache_repository_list.append(repo_cache) + if query.f == "cache": + target_repositories = exist_cache_repository_list[start_index:end_index] + return SearchResult( + resources=target_repositories, + total=len(exist_cache_repository_list), + page_size=query.l or len(target_repositories), + offset=start_index + 1, + ) + target_repositories = non_exist_cache_repository_list[start_index:end_index] + return SearchResult( + resources=target_repositories, + total=len(non_exist_cache_repository_list), + page_size=query.l or len(target_repositories), + offset=start_index + 1, + ) + repository_cache_list = [] + for repository in repositories.resources: + repository_redis_key = _get_repository_redis_key( + repository.service_url.host # pyright: ignore[reportOptionalMemberAccess, reportArgumentType] + ) + repository_updated = None + if repository_redis_key in redis_key_list: + repository_updated = group_cache.hget(repository_redis_key, "updated_at") + + repo_cache = RepositoryCache( + id=repository.id, + name=repository.display_name, # pyright: ignore[reportArgumentType], + url=str(repository.service_url), + updated=datetime.fromisoformat(repository_updated) # pyright: ignore[reportArgumentType] + if repository_updated + else None, + ) + repository_cache_list.append(repo_cache) + + return SearchResult( + resources=repository_cache_list, + total=repositories.total, + page_size=query.l or repositories.page_size, + offset=(query.p - 1) * query.l + 1 if query.p and query.l else 1, + ) + + +def update(fqdn_list: list[str], op: CacheOperation) -> None: + """Update cache groups based on the operation type. + + Args: + fqdn_list (list[str]): List of fully qualified domain names. + op (CacheOperation): Operation type, either 'all' or 'id-specified'. + + Raises: + RuntimeError: If a cache update task is already running. + """ + if check_updating_cache_is_running(): + err = "A cache update task is already running." + raise RuntimeError(err) + + update_task.apply_async(fqdn_list, op) # pyright: ignore[reportArgumentType] + + +@shared_task() +def update_task(fqdn_list: list[str], op: CacheOperation) -> None: + """Celery task to update cache groups. + + Args: + fqdn_list (list[str]): List of fully qualified domain names. + op (CacheOperation): Operation type, either 'all' or 'id-specified'. + """ + + def _receive_update_count(_: object, **kwargs: dict[str, t.Any]) -> None: + """Receive update count signal and update task status in Redis. + + Args: + _: The sender of the signal. + **kwargs: Additional keyword arguments containing task details. + """ + update_dict = { + "total": kwargs.get("total", 0), + "done": kwargs.get("done", 0), + "current": kwargs.get("current", ""), + } + app_cache.hset(cache_key, mapping=update_dict) + + def _receive_update_result(_: object, **kwargs: dict[str, t.Any]) -> None: + """Receive update result signal and update task result in Redis. + + Args: + _: The sender of the signal. + **kwargs: Additional keyword arguments containing task details. + """ + task_result = { + "status": kwargs.get("status", ""), + "code": kwargs.get("code", ""), + "updated": kwargs.get("updated", ""), + } + update_dict = {str(kwargs.get("task_name", "")): str(task_result)} + app_cache.hset(cache_key, mapping=update_dict) + + cache_key = config.CACHE_GROUPS.cache_redis_key.format( + prefix=config.REDIS.key_prefix + ) + + update_count_signal.connect(_receive_update_count) + update_result_signal.connect(_receive_update_result) + + if op == "all": + update_run_task() + else: + update_one_task(fqdn_list) + + +def update_run_task() -> None: + """Task to update all cache groups.""" + cache_key = config.CACHE_GROUPS.cache_redis_key.format( + prefix=config.REDIS.key_prefix + ) + run_task_init_data = {"total": "", "done": 0, "current": ""} + app_cache.hset(cache_key, mapping=run_task_init_data) + if config.CACHE_GROUPS.toml_path: + fetch_all(toml_path=config.CACHE_GROUPS.toml_path) + else: + fetch_all( + directory_path=config.CACHE_GROUPS.directory_path, + fqdn_list_file=config.CACHE_GROUPS.fqdn_list_file, + ) + + +def update_one_task(fqdn_list: list[str]) -> None: + """Task to update cache groups for specified FQDNs. + + Args: + fqdn_list (list[str]): List of fully qualified domain names to update. + """ + cache_key = config.CACHE_GROUPS.cache_redis_key.format( + prefix=config.REDIS.key_prefix + ) + one_task_init_data = {"total": len(fqdn_list), "done": 0, "current": ""} + app_cache.hset(cache_key, mapping=one_task_init_data) + for fqdn in fqdn_list: + try: + done_str = app_cache.hget(cache_key, "done") + done = int(done_str) if done_str is not None else 0 # pyright: ignore[reportArgumentType] + except TypeError: + done = 0 + if config.CACHE_GROUPS.toml_path: + fetch_one(fqdn, toml_path=config.CACHE_GROUPS.toml_path) + else: + fetch_one( + fqdn, + directory_path=config.CACHE_GROUPS.directory_path, + fqdn_list_file=config.CACHE_GROUPS.fqdn_list_file, + ) + task_result = { + "done": done + 1, + "current": fqdn, + } + app_cache.hset(cache_key, mapping=task_result) + + +def check_updating_cache_is_running() -> bool: + """Check if a cache update task is currently running. + + Returns: + bool: True if a cache update task is running, False otherwise. + """ + cache_key = config.CACHE_GROUPS.cache_redis_key.format( + prefix=config.REDIS.key_prefix + ) + return bool(app_cache.exists(cache_key)) diff --git a/uv.lock b/uv.lock index 9a11971e..64e189f0 100644 --- a/uv.lock +++ b/uv.lock @@ -23,6 +23,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, ] +[[package]] +name = "backoff" +version = "2.2.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/47/d7/5bbeb12c44d7c4f2fb5b56abce497eb5ed9f34d85701de869acedd602619/backoff-2.2.1.tar.gz", hash = "sha256:03f829f5bb1923180821643f8753b0502c3b682293992485b0eef2807afa5cba", size = 17001, upload-time = "2022-10-05T19:19:32.061Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/73/b6e24bd22e6720ca8ee9a85a0c4a2971af8497d8f3193fa05390cbd46e09/backoff-2.2.1-py3-none-any.whl", hash = "sha256:63579f9a0628e06278f7e47b7d7d5b6ce20dc65c5e96a6f3ca99a6adca0396e8", size = 15148, upload-time = "2022-10-05T19:19:30.546Z" }, +] + [[package]] name = "billiard" version = "4.2.4" @@ -301,6 +310,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl", hash = "sha256:771a87f49d9defaf64091e6e6fe9c18d4833f140bd19464795bc32d966ca37ea", size = 71008, upload-time = "2025-10-12T14:55:18.883Z" }, ] +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + [[package]] name = "iniconfig" version = "2.3.0" @@ -334,6 +356,7 @@ dependencies = [ { name = "pydantic-settings" }, { name = "requests" }, { name = "sqlalchemy-utils" }, + { name = "weko-group-cache-db" }, ] [package.dev-dependencies] @@ -355,6 +378,7 @@ requires-dist = [ { name = "pydantic-settings", specifier = ">=2.12.0" }, { name = "requests", specifier = ">=2.32.5" }, { name = "sqlalchemy-utils", specifier = ">=0.42.1" }, + { name = "weko-group-cache-db", git = "https://github.com/ivis-weko3-dev/weko-group-cache-db.git?rev=develop" }, ] [package.metadata.requires-dev] @@ -396,6 +420,18 @@ redis = [ { name = "redis" }, ] +[[package]] +name = "markdown-it-py" +version = "4.0.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mdurl" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5b/f5/4ec618ed16cc4f8fb3b701563655a69816155e79e24a17b651541804721d/markdown_it_py-4.0.0.tar.gz", hash = "sha256:cb0a2b4aa34f932c007117b194e945bd74e0ec24133ceb5bac59009cda1cb9f3", size = 73070, upload-time = "2025-08-11T12:57:52.854Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" @@ -426,6 +462,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" }, ] +[[package]] +name = "mdurl" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, +] + +[[package]] +name = "more-itertools" +version = "10.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ea/5d/38b681d3fce7a266dd9ab73c66959406d565b3e85f21d5e66e1181d93721/more_itertools-10.8.0.tar.gz", hash = "sha256:f638ddf8a1a0d134181275fb5d58b086ead7c6a72429ad725c67503f13ba30bd", size = 137431, upload-time = "2025-09-02T15:23:11.018Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl", hash = "sha256:52d4362373dcf7c52546bc4af9a86ee7c4579df9a8dc268be0a2f949d376cc9b", size = 69667, upload-time = "2025-09-02T15:23:09.635Z" }, +] + [[package]] name = "packaging" version = "25.0" @@ -660,6 +714,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl", hash = "sha256:2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6", size = 64738, upload-time = "2025-08-18T20:46:00.542Z" }, ] +[[package]] +name = "rich" +version = "14.3.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "markdown-it-py" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a1/84/4831f881aa6ff3c976f6d6809b58cdfa350593ffc0dc3c58f5f6586780fb/rich-14.3.1.tar.gz", hash = "sha256:b8c5f568a3a749f9290ec6bddedf835cec33696bfc1e48bcfecb276c7386e4b8", size = 230125, upload-time = "2026-01-24T21:40:44.847Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/87/2a/a1810c8627b9ec8c57ec5ec325d306701ae7be50235e8fd81266e002a3cc/rich-14.3.1-py3-none-any.whl", hash = "sha256:da750b1aebbff0b372557426fb3f35ba56de8ef954b3190315eb64076d6fb54e", size = 309952, upload-time = "2026-01-24T21:40:42.969Z" }, +] + +[[package]] +name = "rich-click" +version = "1.9.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "click" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, + { name = "rich" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4b/50/1497dbc52297d6759451bf5a991e9b2d0a122a5d33ac8cd057f81cb9910a/rich_click-1.9.6.tar.gz", hash = "sha256:463bd3dbef54a812282bfa93dde80c471bce359823fc1301be368eab63391cb2", size = 74777, upload-time = "2026-01-22T02:43:58.374Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/87/508930def644be9fb86fec63520151921061c152289b98798017a498d678/rich_click-1.9.6-py3-none-any.whl", hash = "sha256:e78d71e3f73a55548e573ccfd964e18503936e2e736a4a1f74c6c29479a2a054", size = 71430, upload-time = "2026-01-22T02:43:56.939Z" }, +] + [[package]] name = "six" version = "1.17.0" @@ -702,6 +783,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/7c/25/7400c18c3ee97914cc99c90007795c00a4ec5b60c853b49db7ba24d11179/sqlalchemy_utils-0.42.1-py3-none-any.whl", hash = "sha256:243cfe1b3a1dae3c74118ae633f1d1e0ed8c787387bc33e556e37c990594ac80", size = 91761, upload-time = "2025-12-13T03:14:15.014Z" }, ] +[[package]] +name = "typeguard" +version = "4.4.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c7/68/71c1a15b5f65f40e91b65da23b8224dad41349894535a97f63a52e462196/typeguard-4.4.4.tar.gz", hash = "sha256:3a7fd2dffb705d4d0efaed4306a704c89b9dee850b688f060a8b1615a79e5f74", size = 75203, upload-time = "2025-06-18T09:56:07.624Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1b/a9/e3aee762739c1d7528da1c3e06d518503f8b6c439c35549b53735ba52ead/typeguard-4.4.4-py3-none-any.whl", hash = "sha256:b5f562281b6bfa1f5492470464730ef001646128b180769880468bd84b68b09e", size = 34874, upload-time = "2025-06-18T09:56:05.999Z" }, +] + [[package]] name = "typing-extensions" version = "4.15.0" @@ -771,6 +864,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/af/b5/123f13c975e9f27ab9c0770f514345bd406d0e8d3b7a0723af9d43f710af/wcwidth-0.2.14-py2.py3-none-any.whl", hash = "sha256:a7bb560c8aee30f9957e5f9895805edd20602f2d7f720186dfd906e82b4982e1", size = 37286, upload-time = "2025-09-22T16:29:51.641Z" }, ] +[[package]] +name = "weko-group-cache-db" +version = "1.0.0rc4" +source = { git = "https://github.com/ivis-weko3-dev/weko-group-cache-db.git?rev=develop#4b41ff4d972487a6812499f43d10d956b6f72721" } +dependencies = [ + { name = "backoff" }, + { name = "inflect" }, + { name = "pydantic-settings" }, + { name = "redis" }, + { name = "requests" }, + { name = "rich-click" }, + { name = "werkzeug" }, +] + [[package]] name = "werkzeug" version = "3.1.5" From 551eeacbde9e3c5697b99d167185881d2a0347be Mon Sep 17 00:00:00 2001 From: ivis-ishioka Date: Tue, 3 Feb 2026 00:32:27 +0000 Subject: [PATCH 024/173] test: add tests for clients utils --- tests/unit/clients/test_utils.py | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 tests/unit/clients/test_utils.py diff --git a/tests/unit/clients/test_utils.py b/tests/unit/clients/test_utils.py new file mode 100644 index 00000000..540d5042 --- /dev/null +++ b/tests/unit/clients/test_utils.py @@ -0,0 +1,45 @@ +import re +import typing as t + +from server.clients import utils + + +if t.TYPE_CHECKING: + import pytest + + +def test_get_time_stamp_returns_int_timestamp_string(monkeypatch: pytest.MonkeyPatch): + # A fixed input should always make a fixed output. + # (For example, when the time is 1700000000.987654, aa() should return "1700000000".) + + fixed_time = 1700000000.987654 + expecter_timestamp = "1700000000" + monkeypatch.setattr(utils.time, "time", lambda: fixed_time) + result = utils.get_time_stamp() + assert result == expecter_timestamp + + +def test_compute_signature_matches_expected_hash(): + # A known input should make a known SHA-256 hash. + cs = "secret" + at = "token" + ts = "1700000000" + expected = utils.hashlib.sha256(f"{cs}{at}{ts}".encode()).hexdigest() + result = utils.compute_signature(cs, at, ts) + assert result == expected + + +def test_compute_signature_returns_sha256_hex_format(): + # The return value should be a SHA-256 hash: + expected_length = 64 + out = utils.compute_signature("a", "b", "c") + assert isinstance(out, str) + assert len(out) == expected_length + assert re.fullmatch(r"[0-9a-f]{64}", out) + + +def test__compute_signature_changes_when_input_changes(): + # If one character in the input changes, the output hash should also change + base = utils.compute_signature("s", "t", "1") + changed = utils.compute_signature("s", "t", "2") + assert base != changed From c2b9b312ba7fed5564838e48b1f738c53d2a8e7d Mon Sep 17 00:00:00 2001 From: ivis-ishioka Date: Tue, 3 Feb 2026 02:27:47 +0000 Subject: [PATCH 025/173] fix: get_highest_role may return None - if the role list is empty, return None instead of ValueError --- src/server/services/utils/affiliations.py | 5 ++++- src/server/services/utils/roles.py | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/server/services/utils/affiliations.py b/src/server/services/utils/affiliations.py index 71de57c4..bdb21cfa 100644 --- a/src/server/services/utils/affiliations.py +++ b/src/server/services/utils/affiliations.py @@ -46,7 +46,10 @@ def detect_affiliations(group_ids: list[str]) -> Affiliations: return Affiliations( roles=[ - _RoleGroup(repository_id=repo_id, role=get_highest_role(roles)) + _RoleGroup( + repository_id=repo_id, + role=t.cast("USER_ROLES", get_highest_role(roles)), + ) for repo_id, roles in aggregated.items() ], groups=[aff for aff in detect_affiliations if aff.type == "group"], diff --git a/src/server/services/utils/roles.py b/src/server/services/utils/roles.py index 333aa4c8..031abe37 100644 --- a/src/server/services/utils/roles.py +++ b/src/server/services/utils/roles.py @@ -7,7 +7,7 @@ from server.const import USER_ROLES -def get_highest_role(roles_list: list[USER_ROLES]) -> USER_ROLES: +def get_highest_role(roles_list: list[USER_ROLES]) -> USER_ROLES | None: """Get the highest role from a list of roles. Args: @@ -16,6 +16,9 @@ def get_highest_role(roles_list: list[USER_ROLES]) -> USER_ROLES: Returns: str: The highest role based on predefined hierarchy. """ + if not roles_list: + return None + role_order = list(USER_ROLES) return min(roles_list, key=role_order.index) From 5ca80697a6c7736d093452c85ea44f02a48ce25d Mon Sep 17 00:00:00 2001 From: ivis-ishioka Date: Tue, 3 Feb 2026 02:29:48 +0000 Subject: [PATCH 026/173] test: add tests for role utils --- tests/unit/services/test_utils_roles.py | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 tests/unit/services/test_utils_roles.py diff --git a/tests/unit/services/test_utils_roles.py b/tests/unit/services/test_utils_roles.py new file mode 100644 index 00000000..18f247d7 --- /dev/null +++ b/tests/unit/services/test_utils_roles.py @@ -0,0 +1,63 @@ +import pytest + +from server.const import USER_ROLES +from server.services.utils.roles import get_highest_role + + +@pytest.mark.parametrize( + ("roles", "expected"), + [ + ( + [USER_ROLES.SYSTEM_ADMIN, USER_ROLES.REPOSITORY_ADMIN, USER_ROLES.COMMUNITY_ADMIN], + USER_ROLES.SYSTEM_ADMIN, + ), + ( + [USER_ROLES.COMMUNITY_ADMIN, USER_ROLES.REPOSITORY_ADMIN, USER_ROLES.SYSTEM_ADMIN], + USER_ROLES.SYSTEM_ADMIN, + ), + ( + [USER_ROLES.REPOSITORY_ADMIN, USER_ROLES.COMMUNITY_ADMIN, USER_ROLES.GENERAL_USER], + USER_ROLES.REPOSITORY_ADMIN, + ), + ( + [USER_ROLES.COMMUNITY_ADMIN, USER_ROLES.CONTRIBUTOR, USER_ROLES.GENERAL_USER], + USER_ROLES.COMMUNITY_ADMIN, + ), + ( + [USER_ROLES.CONTRIBUTOR, USER_ROLES.GENERAL_USER], + USER_ROLES.CONTRIBUTOR, + ), + ( + [USER_ROLES.GENERAL_USER, USER_ROLES.GENERAL_USER], + USER_ROLES.GENERAL_USER, + ), + ( + [USER_ROLES.REPOSITORY_ADMIN], + USER_ROLES.REPOSITORY_ADMIN, + ), + ( + [USER_ROLES.REPOSITORY_ADMIN, USER_ROLES.REPOSITORY_ADMIN, USER_ROLES.REPOSITORY_ADMIN], + USER_ROLES.REPOSITORY_ADMIN, + ), + ([], None), + ], + ids=[ + "original_order", + "inverse_order", + "repository_admin", + "community_admin", + "contributor", + "general_user", + "single", + "duplicate", + "empty_list", + ], +) +def test_get_highest_role(roles, expected): + actual = get_highest_role(roles) + assert actual == expected + + +def test_get_highest_role_invalid_raises(): + with pytest.raises(ValueError, match="not in list"): + get_highest_role(["UNKNOWN"]) # pyright: ignore[reportArgumentType] From 92d1554d2ad0abe5c387fd20e21efdfeeec71669 Mon Sep 17 00:00:00 2001 From: ivis-kuroda Date: Tue, 3 Feb 2026 06:53:46 +0000 Subject: [PATCH 027/173] fix: turn off prerendering - memory usage was enormous during static generation. --- nuxt.config.ts | 7 ++----- src/app/pages/repositories/new.vue | 17 +++++++++-------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/nuxt.config.ts b/nuxt.config.ts index 331a2800..256cf4f6 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -3,12 +3,12 @@ export default defineNuxtConfig({ modules: [ '@nuxt/eslint', '@nuxt/ui', - '@nuxt/content', '@pinia/nuxt', '@nuxtjs/i18n', '@nuxt/test-utils', '@vueuse/nuxt', ], + ssr: false, imports: { dirs: ['~/types'], @@ -23,10 +23,6 @@ export default defineNuxtConfig({ srcDir: 'src/app/', - routeRules: { - '/': { prerender: true }, - }, - compatibilityDate: '2026-01-10', eslint: { @@ -45,6 +41,7 @@ export default defineNuxtConfig({ detectBrowserLanguage: { fallbackLocale: 'ja', }, + langDir: 'locales', restructureDir: 'src/app/i18n', }, }) diff --git a/src/app/pages/repositories/new.vue b/src/app/pages/repositories/new.vue index c0c449ba..e3d5f6fd 100644 --- a/src/app/pages/repositories/new.vue +++ b/src/app/pages/repositories/new.vue @@ -2,14 +2,6 @@ import { FetchError } from 'ofetch' const { currentUser } = useAuth() - -if (!currentUser.value?.isSystemAdmin) { - showError({ - status: 403, - statusText: $t('repository.error.forbidden'), - }) -} - const { stateAsCreate: state } = useRepositoryForm() const onSubmit = async (data: RepositoryCreatePayload) => { @@ -60,6 +52,15 @@ const onSubmit = async (data: RepositoryCreatePayload) => { } } } + +onMounted(() => { + if (!currentUser.value?.isSystemAdmin) { + showError({ + status: 403, + statusText: $t('repository.error.forbidden'), + }) + } +})