Nexom 1.0.4__py3-none-any.whl → 1.0.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nexom/app/__init__.py +1 -1
- nexom/app/auth.py +184 -71
- nexom/app/db.py +34 -6
- nexom/app/path.py +1 -1
- nexom/app/response.py +12 -3
- nexom/app/template.py +21 -17
- nexom/assets/app/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/app/pages/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/app/pages/_templates.py +2 -2
- nexom/assets/app/router.py +2 -2
- nexom/assets/app/static/github.png +0 -0
- nexom/assets/app/static/style.css +626 -29
- nexom/assets/app/templates/default.html +7 -3
- nexom/assets/app/templates/document.html +122 -166
- nexom/assets/app/templates/footer.html +3 -3
- nexom/assets/app/templates/header.html +9 -3
- nexom/assets/auth/__pycache__/__init__.cpython-313.pyc +0 -0
- nexom/assets/auth_page/login.html +180 -40
- nexom/assets/auth_page/signup.html +259 -44
- nexom/buildTools/build.py +1 -1
- nexom/core/error.py +125 -32
- nexom/templates/auth.py +89 -23
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/METADATA +2 -2
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/RECORD +28 -27
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/WHEEL +0 -0
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/entry_points.txt +0 -0
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/licenses/LICENSE +0 -0
- {nexom-1.0.4.dist-info → nexom-1.0.5.dist-info}/top_level.txt +0 -0
|
@@ -1,169 +1,125 @@
|
|
|
1
1
|
<Extends base />
|
|
2
2
|
<Insert main>
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
<div class="note">
|
|
127
|
-
<strong>設計方針:</strong> テンプレートにロジックを入れず、「HTML と差し込み」に寄せることで、コード側をシンプルに保つ思想です。
|
|
128
|
-
</div>
|
|
129
|
-
</section>
|
|
130
|
-
|
|
131
|
-
<section id="cli">
|
|
132
|
-
<h2>CLI</h2>
|
|
133
|
-
|
|
134
|
-
<h3>動作確認</h3>
|
|
135
|
-
<pre><code>python -m nexom test</code></pre>
|
|
136
|
-
|
|
137
|
-
<h3>サーバープロジェクト生成</h3>
|
|
138
|
-
<pre><code>python -m nexom build-server myapp</code></pre>
|
|
139
|
-
|
|
140
|
-
<p>生成されるプロジェクトには以下が含まれます(構成はバージョンにより変わる場合があります)。</p>
|
|
141
|
-
<ul>
|
|
142
|
-
<li>最小のルーティング定義</li>
|
|
143
|
-
<li>テンプレート一式</li>
|
|
144
|
-
<li>静的ファイル用ディレクトリ</li>
|
|
145
|
-
<li>gunicorn / config の雛形</li>
|
|
146
|
-
</ul>
|
|
147
|
-
</section>
|
|
148
|
-
|
|
149
|
-
<section id="tips">
|
|
150
|
-
<h2>運用のコツ</h2>
|
|
151
|
-
|
|
152
|
-
<h3>「短く書く層」と「脱出口」を分ける</h3>
|
|
153
|
-
<ul>
|
|
154
|
-
<li>普段: <code>HtmlResponse</code> / <code>JsonResponse</code> を使う</li>
|
|
155
|
-
<li>詰めたい: <code>Response</code> で headers / content-type を直接制御</li>
|
|
156
|
-
</ul>
|
|
157
|
-
|
|
158
|
-
<h3>ルーティングは “薄く”</h3>
|
|
159
|
-
<p>handler 内で複雑化しそうなら、関数を分けて「処理の塊」を小さくするのが Nexom と相性良いです。</p>
|
|
160
|
-
|
|
161
|
-
<h3>テストのおすすめ</h3>
|
|
162
|
-
<p>公開運用を想定するなら、最低限この3つのテストを用意すると安心です。</p>
|
|
163
|
-
<ul>
|
|
164
|
-
<li>Path の引数抽出</li>
|
|
165
|
-
<li>Static の traversal ブロック</li>
|
|
166
|
-
<li>Response の Content-Type / charset の期待値</li>
|
|
167
|
-
</ul>
|
|
168
|
-
</section>
|
|
3
|
+
<div class="article-container">
|
|
4
|
+
<h1 id="nexom">README</h1>
|
|
5
|
+
<p>Lightweight Python Web Framework (WSGI)</p>
|
|
6
|
+
<p>Nexomは短いコードで最低限動作し、シンプルで理解のしやすい設計・構造を目指しています。<br>
|
|
7
|
+
また細かい仕様も変更でき、多様な処理に対応します。</p>
|
|
8
|
+
<h2 id="はじめる">はじめる </h2>
|
|
9
|
+
<p>最初のサーバーを起動するには、3つの手順が必要です。</p>
|
|
10
|
+
<ol>
|
|
11
|
+
<li>プロジェクトディレクトリを作成</li>
|
|
12
|
+
<li>nexomをpipでインストール、プロジェクトのビルド</li>
|
|
13
|
+
<li>起動</li>
|
|
14
|
+
</ol>
|
|
15
|
+
<h3 id="1プロジェクトディレクトリの作成">1.プロジェクトディレクトリの作成 </h3>
|
|
16
|
+
<p><strong>準備</strong></p>
|
|
17
|
+
<p>用意していない場合はディレクトリを作成し、仮想環境も準備してください</p>
|
|
18
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>mkdir banana_project
|
|
19
|
+
cd banana_project
|
|
20
|
+
|
|
21
|
+
python -m venv venv
|
|
22
|
+
source venv/bin/activate
|
|
23
|
+
</code></pre><h3 id="2-pipでインストール-サーバーのビルド">2. pipでインストール、サーバーのビルド </h3>
|
|
24
|
+
<p><strong>インストール</strong></p>
|
|
25
|
+
<p>nexomをインストールします。</p>
|
|
26
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>pip install
|
|
27
|
+
</code></pre><p><strong>プロジェクトのビルド</strong></p>
|
|
28
|
+
<p>プロジェクトディレクトリ上で、以下のコマンドを実行してください(名前は自由)<br>
|
|
29
|
+
もしNginxもしくはApacheを使用する場合 --gateway オプションにどちらか入力してください</p>
|
|
30
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>$ python -m nexom start-project
|
|
31
|
+
</code></pre><p>以下の構成でプロジェクトが生成されます。</p>
|
|
32
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>banana_project/
|
|
33
|
+
├─ app/
|
|
34
|
+
│ ├─ pages/
|
|
35
|
+
│ │ ├─ __init__.py
|
|
36
|
+
│ │ ├─ _templates.py
|
|
37
|
+
│ │ └─ * pages *
|
|
38
|
+
│ ├─ static/
|
|
39
|
+
│ │ └─ * static items *
|
|
40
|
+
│ ├─ templates/
|
|
41
|
+
│ │ └─ * html files *
|
|
42
|
+
│ ├─ __init__.py
|
|
43
|
+
│ ├─ config.py
|
|
44
|
+
│ ├─ gunicorn.conf.py
|
|
45
|
+
│ ├─ router.py
|
|
46
|
+
│ └─ wsgi.py
|
|
47
|
+
├─ auth/
|
|
48
|
+
│ ├─ __init__.py
|
|
49
|
+
│ ├─ config.py
|
|
50
|
+
│ ├─ gunicorn.conf.py
|
|
51
|
+
│ └─ wsgi.py
|
|
52
|
+
└─ data/
|
|
53
|
+
├─ log/
|
|
54
|
+
│ └─ * app logs *
|
|
55
|
+
├─ db/
|
|
56
|
+
│ └─ * app db *
|
|
57
|
+
└─ gateway/
|
|
58
|
+
├─ app.nginx.conf
|
|
59
|
+
└─ app.apache.conf
|
|
60
|
+
</code></pre><h3 id="3起動">3.起動 </h3>
|
|
61
|
+
<p>以下のコマンドを起動します。</p>
|
|
62
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>$ python -m nexom run
|
|
63
|
+
</code></pre><p>ブラウザからアクセスできるようになります。<br>
|
|
64
|
+
デフォルトのポートは8080です。</p>
|
|
65
|
+
<p><a href="https://localhost:8080">https://localhost:8080</a></p>
|
|
66
|
+
<p>ポートなどの設定は <code>config.py</code> から変更してください。</p>
|
|
67
|
+
<h2 id="nginx等使用して外部公開する">Nginx等使用して外部公開する </h2>
|
|
68
|
+
<p>gatewayディレクトリにある設定を読み込んでください</p>
|
|
69
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>http {
|
|
70
|
+
include /home/ubuntu/banana_project/gateway/*.conf;
|
|
71
|
+
}
|
|
72
|
+
</code></pre><h2 id="systemdに登録して自動起動する">Systemdに登録して自動起動する </h2>
|
|
73
|
+
<p><strong>Ubuntuの場合</strong></p>
|
|
74
|
+
<ol>
|
|
75
|
+
<li><code>/etc/systemd/system</code> に、 <code>banana_sample.service</code> を作成します。</li>
|
|
76
|
+
<li><code>banana_sample.service</code> に以下を書き込みます。(これは一例です。環境に合わせて設定してください。)</li>
|
|
77
|
+
</ol>
|
|
78
|
+
<p>サーバーのディレクトリが <code>/home/ubuntu/banana_project</code> にある場合</p>
|
|
79
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>[Unit]
|
|
80
|
+
Description=Nexom Web Freamework
|
|
81
|
+
After=network.target
|
|
82
|
+
|
|
83
|
+
[Service]
|
|
84
|
+
User=www-data
|
|
85
|
+
Group=www-data
|
|
86
|
+
WorkingDirectory=/home/ubuntu/banana_project
|
|
87
|
+
Environment="PYTHONPATH=/home/ubuntu/banana_project"
|
|
88
|
+
ExecStart=/home/ubuntu/banana_project/venv/bin/gunicorn sample.wsgi:app --config sample/gunicorn.conf.py
|
|
89
|
+
Restart=always
|
|
90
|
+
RestartSec=3
|
|
91
|
+
[Install]
|
|
92
|
+
WantedBy=multi-user.target
|
|
93
|
+
</code></pre><p>以下のコマンドを実行します</p>
|
|
94
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>sudo systemd daemon-reload
|
|
95
|
+
sudo systemd enable banana_sample
|
|
96
|
+
sudo systemd start banana_sample
|
|
97
|
+
</code></pre><h3 id="テンプレートユニットを活用して複数のアプリを効率的に管理">テンプレートユニットを活用して複数のアプリを効率的に管理 </h3>
|
|
98
|
+
<p>テンプレートユニットを活用し .service ファイルを一枚にまとめられます。</p>
|
|
99
|
+
<p><code>/etc/systemd/system/banana-project@.service</code></p>
|
|
100
|
+
<pre data-role="codeBlock" data-info="" class="language-text"><code>[Unit]
|
|
101
|
+
Description=Nexom Web Server (%i)
|
|
102
|
+
After=network.target
|
|
103
|
+
|
|
104
|
+
[Service]
|
|
105
|
+
User=www-data
|
|
106
|
+
Group=www-data
|
|
107
|
+
WorkingDirectory=/home/ubuntu/banana_project
|
|
108
|
+
Environment="PYTHONPATH=/home/ubuntu/banana_project"
|
|
109
|
+
ExecStart=/home/ubuntu/banana_project/venv/bin/gunicorn %iwsgi:app --config %i/gunicorn.conf.py
|
|
110
|
+
Restart=always
|
|
111
|
+
RestartSec=3
|
|
112
|
+
[Install]
|
|
113
|
+
WantedBy=multi-user.target
|
|
114
|
+
</code></pre><pre data-role="codeBlock" data-info="" class="language-text"><code>sudo systemd daemon-reload
|
|
115
|
+
|
|
116
|
+
sudo systemd enable banana-project@banana1
|
|
117
|
+
sudo systemd enable banana-project@banana2
|
|
118
|
+
sudo systemd enable banana-project@banana3
|
|
119
|
+
|
|
120
|
+
sudo systemd start banana-project@banana1
|
|
121
|
+
sudo systemd start banana-project@banana2
|
|
122
|
+
sudo systemd start banana-project@banana3
|
|
123
|
+
</code></pre><p>2026 1/27</p>
|
|
124
|
+
</div>
|
|
169
125
|
</Insert>
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
<footer>
|
|
2
|
-
<div class="footer-text"
|
|
3
|
-
</
|
|
1
|
+
<div class="footer-container">
|
|
2
|
+
<div class="footer-text">© AidaTouri 2026</div>
|
|
3
|
+
</div>
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
-
<header>
|
|
2
|
-
<div class="header-
|
|
3
|
-
|
|
1
|
+
<div class="header-wrapper">
|
|
2
|
+
<div class="header-container">
|
|
3
|
+
<a href="/" class="header-logo">Nexom</a>
|
|
4
|
+
<a href="https://github.com/ait913/Nexom" class="header-link", title="to GitHub Repository">
|
|
5
|
+
<img src="/static/github.png" alt="GitHub Link">
|
|
6
|
+
</a>
|
|
7
|
+
</div>
|
|
8
|
+
<div class="header-speacer"></div>
|
|
9
|
+
</div>
|
|
Binary file
|
|
@@ -5,59 +5,188 @@
|
|
|
5
5
|
<meta charset="utf-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
|
7
7
|
<title>Nexom Login</title>
|
|
8
|
+
|
|
9
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
10
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
11
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Noto+Sans+JP:wght@400;600;700&display=swap" rel="stylesheet">
|
|
12
|
+
|
|
8
13
|
<style>
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
:root{
|
|
15
|
+
--bg:#0b0d12;
|
|
16
|
+
--card:#0f141c;
|
|
17
|
+
--line:rgba(255,255,255,.10);
|
|
18
|
+
--text:rgba(255,255,255,.92);
|
|
19
|
+
--muted:rgba(255,255,255,.62);
|
|
20
|
+
--danger:#ff5a6a;
|
|
21
|
+
--btn:#ffffff;
|
|
22
|
+
--btnText:#0b0d12;
|
|
23
|
+
--shadow:0 20px 60px rgba(0,0,0,.45);
|
|
24
|
+
}
|
|
25
|
+
*{ box-sizing:border-box; }
|
|
26
|
+
html,body{ height:100%; }
|
|
27
|
+
body{
|
|
28
|
+
margin:0;
|
|
29
|
+
background: radial-gradient(1200px 600px at 20% 10%, rgba(106,140,255,.18), transparent 60%),
|
|
30
|
+
radial-gradient(900px 500px at 85% 30%, rgba(39,211,155,.12), transparent 55%),
|
|
31
|
+
var(--bg);
|
|
32
|
+
color:var(--text);
|
|
33
|
+
font-family: Inter, "Noto Sans JP", "Hiragino Sans", "ヒラギノ角ゴシック", "Yu Gothic", "游ゴシック", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
34
|
+
overflow:hidden; /* スクロール禁止 */
|
|
35
|
+
}
|
|
36
|
+
.wrap{
|
|
37
|
+
height:100%;
|
|
38
|
+
display:grid;
|
|
39
|
+
place-items:center;
|
|
40
|
+
padding:24px;
|
|
41
|
+
}
|
|
42
|
+
.card{
|
|
43
|
+
width:min(560px, 100%);
|
|
44
|
+
background: linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,255,255,.03));
|
|
45
|
+
border:1px solid var(--line);
|
|
46
|
+
border-radius:18px;
|
|
47
|
+
box-shadow: var(--shadow);
|
|
48
|
+
padding:18px;
|
|
49
|
+
backdrop-filter: blur(10px);
|
|
50
|
+
}
|
|
51
|
+
h1{
|
|
52
|
+
margin:0 0 10px;
|
|
53
|
+
font-size:18px;
|
|
54
|
+
letter-spacing:.2px;
|
|
55
|
+
}
|
|
56
|
+
.sub{
|
|
57
|
+
margin:0 0 14px;
|
|
58
|
+
color:var(--muted);
|
|
59
|
+
font-size:12px;
|
|
60
|
+
line-height:1.5;
|
|
61
|
+
}
|
|
62
|
+
label{
|
|
63
|
+
display:block;
|
|
64
|
+
font-size:11px;
|
|
65
|
+
color:var(--muted);
|
|
66
|
+
margin:0 0 6px;
|
|
67
|
+
}
|
|
68
|
+
input{
|
|
69
|
+
width:100%;
|
|
70
|
+
height:40px;
|
|
71
|
+
padding:0 12px;
|
|
72
|
+
border-radius:12px;
|
|
73
|
+
border:1px solid var(--line);
|
|
74
|
+
background: rgba(0,0,0,.25);
|
|
75
|
+
color:var(--text);
|
|
76
|
+
outline:none;
|
|
77
|
+
font-size:13px;
|
|
78
|
+
}
|
|
79
|
+
input:focus{
|
|
80
|
+
border-color: rgba(255,255,255,.18);
|
|
81
|
+
box-shadow: 0 0 0 3px rgba(106,140,255,.12);
|
|
82
|
+
}
|
|
83
|
+
.row{
|
|
84
|
+
display:flex;
|
|
85
|
+
gap:10px;
|
|
86
|
+
align-items:center;
|
|
87
|
+
justify-content:space-between;
|
|
88
|
+
margin-top:12px;
|
|
89
|
+
}
|
|
90
|
+
button{
|
|
91
|
+
height:40px;
|
|
92
|
+
padding:0 14px;
|
|
93
|
+
border-radius:12px;
|
|
94
|
+
border:1px solid rgba(255,255,255,.08);
|
|
95
|
+
background: var(--btn);
|
|
96
|
+
color: var(--btnText);
|
|
97
|
+
font-weight:800;
|
|
98
|
+
cursor:pointer;
|
|
99
|
+
white-space:nowrap;
|
|
100
|
+
}
|
|
101
|
+
button:disabled{ opacity:.55; cursor:not-allowed; }
|
|
102
|
+
.link{
|
|
103
|
+
color:var(--muted);
|
|
104
|
+
font-size:12px;
|
|
105
|
+
text-decoration:none;
|
|
106
|
+
border-bottom:1px dotted rgba(255,255,255,.25);
|
|
107
|
+
}
|
|
108
|
+
.link:hover{ color:var(--text); }
|
|
109
|
+
|
|
110
|
+
.msg{
|
|
111
|
+
display:none;
|
|
112
|
+
margin-top:10px;
|
|
113
|
+
padding:10px 12px;
|
|
114
|
+
border-radius:12px;
|
|
115
|
+
border:1px solid var(--line);
|
|
116
|
+
font-size:12px;
|
|
117
|
+
line-height:1.5;
|
|
118
|
+
white-space:pre-wrap;
|
|
119
|
+
background: rgba(0,0,0,.22);
|
|
120
|
+
}
|
|
121
|
+
.msg.is-error{ display:block; border-color: rgba(255,90,106,.35); }
|
|
22
122
|
</style>
|
|
23
123
|
</head>
|
|
124
|
+
|
|
24
125
|
<body>
|
|
25
126
|
<div class="wrap">
|
|
26
|
-
<h1 style="margin:0 0 12px;font-size:20px;">Login</h1>
|
|
27
|
-
|
|
28
127
|
<section class="card">
|
|
29
|
-
<
|
|
30
|
-
|
|
128
|
+
<h1>Login</h1>
|
|
129
|
+
<p class="sub">アカウントにログイン</p>
|
|
130
|
+
|
|
131
|
+
<form id="form">
|
|
132
|
+
<label for="user_id">ユーザーID</label>
|
|
31
133
|
<input id="user_id" name="user_id" autocomplete="username" required />
|
|
32
134
|
|
|
33
|
-
<label for="password"
|
|
135
|
+
<label for="password" style="margin-top:10px;">パスワード</label>
|
|
34
136
|
<input id="password" name="password" type="password" autocomplete="current-password" required />
|
|
35
137
|
|
|
36
|
-
<button type="submit">login</button>
|
|
37
|
-
|
|
38
138
|
<div class="row">
|
|
39
|
-
<
|
|
40
|
-
<
|
|
139
|
+
<a class="link" href="./signup">まだアカウントを持っていない場合</a>
|
|
140
|
+
<button id="btn" type="submit">ログイン</button>
|
|
41
141
|
</div>
|
|
42
|
-
</form>
|
|
43
142
|
|
|
44
|
-
|
|
143
|
+
<div id="msg" class="msg"></div>
|
|
144
|
+
</form>
|
|
45
145
|
</section>
|
|
46
146
|
</div>
|
|
47
147
|
|
|
48
148
|
<script>
|
|
49
|
-
const
|
|
149
|
+
const AUTH_BASE = ""
|
|
150
|
+
const PAGE_PATH = "/{{ page_path }}"
|
|
151
|
+
|
|
152
|
+
const error_code_for_message = {
|
|
153
|
+
// ===== Auth errors =====
|
|
154
|
+
"A01": "フォームの入力内容に不足があります。",
|
|
155
|
+
"A02": "このユーザーIDはすでに使用されています。",
|
|
156
|
+
"A03": "ユーザーIDまたはパスワードが正しくありません。",
|
|
157
|
+
"A04": "このユーザーは無効化されています。",
|
|
158
|
+
"A05": "認証情報が見つかりません。再度ログインしてください。",
|
|
159
|
+
"A06": "認証情報が不正です。再度ログインしてください。",
|
|
160
|
+
"A07": "セッションの有効期限が切れています。再度ログインしてください。",
|
|
161
|
+
"A08": "このセッションは無効化されています。再度ログインしてください。",
|
|
162
|
+
"A09": "認証サービスに接続できません。しばらく時間をおいて再度お試しください。",
|
|
50
163
|
|
|
51
|
-
|
|
52
|
-
|
|
164
|
+
// fallback
|
|
165
|
+
"DEFAULT": "エラーが発生しました。もう一度お試しください。"
|
|
166
|
+
};
|
|
167
|
+
function getErrorMessage(code) {
|
|
168
|
+
if (typeof code !== "string") {
|
|
169
|
+
return error_code_for_message.DEFAULT;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return error_code_for_message[code] ?? error_code_for_message.DEFAULT;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const form = document.getElementById("form");
|
|
176
|
+
const btn = document.getElementById("btn");
|
|
177
|
+
const msg = document.getElementById("msg");
|
|
178
|
+
|
|
179
|
+
function clearMsg(){
|
|
180
|
+
msg.className = "msg";
|
|
181
|
+
msg.textContent = "";
|
|
53
182
|
}
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
|
|
183
|
+
function setError(text){
|
|
184
|
+
msg.className = "msg is-error";
|
|
185
|
+
msg.textContent = getErrorMessage(text);
|
|
57
186
|
}
|
|
58
187
|
|
|
59
|
-
async function postJSON(
|
|
60
|
-
const res = await fetch(
|
|
188
|
+
async function postJSON(path, bodyObj){
|
|
189
|
+
const res = await fetch(path, {
|
|
61
190
|
method: "POST",
|
|
62
191
|
headers: {
|
|
63
192
|
"Content-Type": "application/json; charset=utf-8",
|
|
@@ -69,26 +198,37 @@
|
|
|
69
198
|
const text = await res.text();
|
|
70
199
|
let data = {};
|
|
71
200
|
try { data = text ? JSON.parse(text) : {}; } catch { data = { raw: text }; }
|
|
72
|
-
|
|
73
201
|
return { ok: res.ok, status: res.status, data };
|
|
74
202
|
}
|
|
75
203
|
|
|
76
|
-
|
|
204
|
+
form.addEventListener("submit", async (e) => {
|
|
77
205
|
e.preventDefault();
|
|
206
|
+
clearMsg();
|
|
78
207
|
|
|
79
208
|
const user_id = document.getElementById("user_id").value.trim();
|
|
80
209
|
const password = document.getElementById("password").value;
|
|
81
210
|
|
|
82
|
-
if (!user_id || !password)
|
|
83
|
-
|
|
211
|
+
if (!user_id || !password){
|
|
212
|
+
setError("T: missing fields");
|
|
84
213
|
return;
|
|
85
214
|
}
|
|
86
215
|
|
|
87
|
-
|
|
88
|
-
|
|
216
|
+
btn.disabled = true;
|
|
217
|
+
try{
|
|
218
|
+
const r = await postJSON(PAGE_PATH, { user_id, password });
|
|
219
|
+
|
|
220
|
+
if (!r.ok || !r.data || r.data.ok !== true){
|
|
221
|
+
const code = (r.data && r.data.error) ? String(r.data.error) : "UnknownError";
|
|
222
|
+
setError(code);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
89
225
|
|
|
90
|
-
|
|
91
|
-
|
|
226
|
+
location.href = "/";
|
|
227
|
+
}catch{
|
|
228
|
+
setError("NetworkError");
|
|
229
|
+
}finally{
|
|
230
|
+
btn.disabled = false;
|
|
231
|
+
}
|
|
92
232
|
});
|
|
93
233
|
</script>
|
|
94
234
|
</body>
|