Skip to content

Commit c010cd4

Browse files
authored
Merge pull request #34 from ForteFibre/feature/email
feat(email): EMAIL-001 メールテンプレート管理機能の実装
2 parents 3f88a76 + 7a5be09 commit c010cd4

41 files changed

Lines changed: 1316 additions & 78 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/agents/impl.agent.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tools:
1010
"todo",
1111
"ms-vscode.vscode-websearchforcopilot/websearch",
1212
]
13-
model: "GPT-5.3-Codex"
13+
model: "GPT-5.4"
1414
---
1515

1616
与えられた実行計画に従って、実装を行ってください。TDD に倣って、以下のステップで実施します。
@@ -20,11 +20,12 @@ model: "GPT-5.3-Codex"
2020
1. 関連するドキュメントやコード、Issueの内容を確認する
2121
2. 網羅的なテストコードを使い作成する
2222
3. 開発ポリシーに従って #tool:edit などを使い実装する。変更はツールを利用し、 #tool:execute を使ったsedなどは使用しない。
23-
4. テストを #tool:execute などを使い実行し、成功を確認する
24-
5. 成功したらリファクタリングを行う
25-
6. リファクタリング後もテストが成功することを確認する
26-
7. 必要に応じてドキュメントを更新する
27-
8. 実装内容を説明する
23+
4. ある程度の編集粒度で、Gitにコミットする
24+
5. テストを #tool:execute などを使い実行し、成功を確認する
25+
6. 成功したらリファクタリングを行う
26+
7. リファクタリング後もテストが成功することを確認する
27+
8. 内容について、特筆すべき情報がある場合ドキュメントを作成・更新する
28+
9. 実装内容を説明する
2829

2930
## 注意事項
3031

.github/agents/issue.agent.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ tools:
1010
"web",
1111
"ms-vscode.vscode-websearchforcopilot/websearch",
1212
]
13-
model: "GPT-5.3-Codex"
13+
model: "GPT-5.4"
1414
---
1515

1616
あなたは、ユーザーが入力する要望 (issue, bug report, feature request など) をもとに、イシューを管理するエージェントです。以下のステップに基づき、要件と仕様の解像度を高めながら、イシューを管理してください。
@@ -22,18 +22,17 @@ model: "GPT-5.3-Codex"
2222
3. 現在のローカル レポジトリ状況を確認する
2323
4. 現在の GitHub Issues の状況を確認する
2424
5. #tool:ms-vscode.vscode-websearchforcopilot/websearch でウェブ検索を行い、要件および要件に必要な周辺知識の理解を深める
25-
6. 要件と調査結果に基づき、十分な情報を含めたIssue を作成/更新する
25+
6. 要件と調査結果に基づき、十分な情報を含めたIssue を1つ以上作成/更新する
2626
7. 作成された Issue に対して批判的にレビューを行う
2727
8. レビュー内容に基づき、Issue を改善する
28-
9. `gh`を使用して Issue を作成し、ユーザーに作成した Issue とその内容を報告する
28+
9. `gh`を使用して Issue を作成し、ユーザーに作成したIssueリストとその内容を報告する
29+
30+
## 注意事項
31+
32+
- Issue の作成においては、巨大な要件で1つのIssueを作成するのではなく、必要に応じて複数の Issue に分割することを検討してください
33+
- 既存の Issue と重複する内容がないか確認してください。重複する内容がある場合は、既存の Issue を更新する形で対応してください
2934

3035
## ツール
3136

3237
- #tool:ms-vscode.vscode-websearchforcopilot/websearch: ウェブ検索
3338
- `gh`: GitHub リポジトリの操作
34-
35-
## ドキュメント
36-
37-
- `docs/`
38-
- `README.md`
39-
- `CONTRIBUTING.md`

.github/agents/orchestrator.agent.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,14 @@ model: "GPT-5.3-Codex"
1919

2020
## 手順 (#tool:todo)
2121

22-
要件に応じて、以下のステップを要件を満たすまで繰り返してください。
23-
24-
1. #tool:agent/runSubagent で issue エージェントを呼び出し、イシューを作成する
25-
2. #tool:agent/runSubagent で plan エージェントを呼び出し、実装計画を立てる
26-
3. #tool:agent/runSubagent で impl エージェントを呼び出し、実装を行う
27-
4. #tool:agent/runSubagent で review エージェントを呼び出し、コードレビューと修正を行う
28-
5. 残っている要件に応じてステップ 3 と 4 を繰り返す
29-
6. #tool:agent/runSubagent で pr エージェントを呼び出し、プルリクエストを作成する
30-
7. 実装内容とプルリクエストのリンクをユーザーに通知する
22+
1. #tool:agent/runSubagent で issue エージェントを呼び出し、イシューを1つ以上作成する
23+
2. 作成したイシュー1つずつについて、以下を行い、実装を進める。エージェントにはissueのIDを渡して、イシューの内容を伝える。
24+
- #tool:agent/runSubagent で plan エージェントを呼び出し、実装計画を立てる
25+
- #tool:agent/runSubagent で impl エージェントを呼び出し、実装を行う
26+
- #tool:agent/runSubagent で review エージェントを呼び出し、コードレビューと修正を行う
27+
- レビューにてプルリクエストが作成OKとなるまで、上記の実装とレビューのサイクルを回す
28+
- #tool:agent/runSubagent で pr エージェントを呼び出し、プルリクエストを作成する
29+
3. 実装内容とプルリクエストのリンクをユーザーに通知する
3130

3231
## 注意事項
3332

.github/agents/plan.agent.md

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ tools:
99
"web",
1010
"ms-vscode.vscode-websearchforcopilot/websearch",
1111
]
12-
model: "GPT-5.3-Codex"
12+
model: "GPT-5.4"
1313
---
1414

1515
与えられたイシューの実装計画を立ててください。
@@ -18,9 +18,10 @@ model: "GPT-5.3-Codex"
1818

1919
1. 現在のレポジトリ状況を確認し、リモートとの同期を行う
2020
2. 指定されたイシューの内容を確認する。イシューが存在しない場合は、処理を中止しユーザーに通知する。
21-
3. レポジトリ (コード、ドキュメント) を確認する
21+
3. レポジトリ (仕様、コード、ドキュメント) を確認する
2222
4. ウェブ検索で情報を収集する
23-
5. 実装計画をユーザーに提示する
23+
5. 技術的制約により仕様を変更する場合、仕様ファイル(`spec-*.md`)に反映する
24+
6. 実装計画をユーザーに提示する
2425

2526
## ツール
2627

@@ -29,9 +30,7 @@ model: "GPT-5.3-Codex"
2930

3031
## ドキュメント
3132

32-
- `docs/`
33-
- `README.md`
34-
- `CONTRIBUTING.md`
33+
- `spec-*.md`
3534

3635
## ブランチ戦略
3736

.github/agents/pr.agent.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ tools:
99
"web",
1010
"ms-vscode.vscode-websearchforcopilot/websearch",
1111
]
12-
model: "GPT-5.3-Codex"
12+
model: "Claude Sonnet 4.6"
1313
---
1414

1515
与えられたイシューと実装に対する、プルリクエストを作成してください。

.github/agents/review.agent.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
description: 実装内容をレビューし、建設的なフィードバックを提供します。
2+
description: Issueベースで実装内容をレビューし、建設的なフィードバックを提供します。
33
tools:
44
[
55
"execute",
@@ -9,20 +9,22 @@ tools:
99
"web",
1010
"ms-vscode.vscode-websearchforcopilot/websearch",
1111
]
12-
model: "GPT-5.3-Codex"
12+
model: "GPT-5.4"
1313
---
1414

1515
実装内容をレビューしてください。批判的に評価を行い、発言についての中立的なレビューを提供してください。新たな情報を検索、分析することを推奨します。あくまでレビューの提供までがあなたの役割です。
1616

1717
## 手順 (#tool:todo)
1818

19+
1. 与えられた課題が何であるかをIssueから理解する
1920
1. 網羅的に情報を収集する
2021
- レポジトリの分析
2122
- ドキュメント群の分析
2223
- ウェブ検索 (#tool:ms-vscode.vscode-websearchforcopilot/websearch) によるベストプラクティス、pitfalls、代替案の調査
2324
- 要件の確認と理解
24-
2. 収集した情報をもとに、実装内容を批判的に評価する (正確性、完全性、一貫性、正当性、妥当性、関連性、明確性、客観性、バイアスの有無、可読性、保守性、テストが十分であるかなどの観点)
25-
3. 改善点や懸念点があれば指摘し、アクションプランを示す
25+
- spec-*.mdに含まれる仕様の確認
26+
1. 収集した情報をもとに、実装内容を批判的に評価する (正確性、完全性、一貫性、正当性、妥当性、関連性、明確性、客観性、バイアスの有無、可読性、保守性、テストが十分であるかなどの観点)
27+
1. 改善点や懸念点があれば指摘し、アクションプランを示す
2628

2729
## ツール
2830

apps/backend/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,13 @@
2525
"better-auth": "^1.5.6",
2626
"drizzle-orm": "^0.45.2",
2727
"hono": "^4.12.9",
28+
"nodemailer": "^8.0.5",
2829
"pg": "^8.13.3",
2930
"zod": "^4.3.6"
3031
},
3132
"devDependencies": {
3233
"@types/node": "^25.5.0",
34+
"@types/nodemailer": "^8.0.0",
3335
"@types/pg": "^8.20.0",
3436
"drizzle-kit": "^0.31.10",
3537
"tsx": "^4.19.3"

apps/backend/src/__tests__/app.issue11.integration.test.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1211,10 +1211,13 @@ describe('issue #11 api integration', () => {
12111211
expect(approveRes.status).toBe(200);
12121212
expect(mockSendEmail).toHaveBeenCalledWith({
12131213
to: 'owner@approve.example',
1214-
subject: 'Approve University の代表者招待',
1215-
html: expect.stringMatching(
1216-
/^: invitation:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
1217-
),
1214+
template: 'university-owner-invitation-link',
1215+
payload: {
1216+
universityName: 'Approve University',
1217+
invitationLink: expect.stringMatching(
1218+
/^http:\/\/localhost:3000\/invite\/[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
1219+
),
1220+
},
12181221
});
12191222
const approveJson = (await approveRes.json()) as {
12201223
data: { status: string; createdOrganizationId: string };

apps/backend/src/auth.ts

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { organization } from 'better-auth/plugins';
44
import { db } from './db/index.js';
55
import * as schema from './db/schema.js';
66
import { env } from './lib/config.js';
7+
import { buildInvitationLink } from './lib/invitation-link.js';
78
import { verifyPassword } from './lib/password.js';
89
import { emailService } from './services/email/index.js';
910

@@ -27,18 +28,50 @@ export const auth = betterAuth({
2728
trustedOrigins: env.CORS_ALLOWED_ORIGINS,
2829
emailAndPassword: {
2930
enabled: true,
31+
requireEmailVerification: true,
32+
autoSignIn: false,
33+
async sendResetPassword({ user, url }) {
34+
await emailService.sendEmail({
35+
to: user.email,
36+
template: 'password-reset',
37+
payload: {
38+
userName: user.name,
39+
resetLink: url,
40+
},
41+
});
42+
},
43+
revokeSessionsOnPasswordReset: true,
3044
password: {
3145
verify: verifyPassword,
3246
},
3347
},
48+
emailVerification: {
49+
sendOnSignUp: true,
50+
sendOnSignIn: true,
51+
autoSignInAfterVerification: true,
52+
async sendVerificationEmail({ user, url }) {
53+
await emailService.sendEmail({
54+
to: user.email,
55+
template: 'email-verification',
56+
payload: {
57+
userName: user.name,
58+
verificationLink: url,
59+
},
60+
});
61+
},
62+
},
3463
plugins: [
3564
organization({
3665
async sendInvitationEmail(data) {
37-
const inviteLink = `${env.APP_URL}/invite/${data.id}`;
66+
const inviteLink = buildInvitationLink(data.id);
3867
await emailService.sendEmail({
3968
to: data.email,
40-
subject: `${data.organization.name} への招待`,
41-
html: `${data.inviter.user.name} さんが ${data.organization.name} へ招待しました: ${inviteLink}`,
69+
template: 'organization-invitation',
70+
payload: {
71+
organizationName: data.organization.name,
72+
inviterName: data.inviter.user.name,
73+
inviteLink,
74+
},
4275
});
4376
},
4477
}),

apps/backend/src/lib/config.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
type Env = {
1+
export type Env = {
22
DATABASE_URL: string;
33
PORT: number;
44
BETTER_AUTH_SECRET: string;
@@ -12,9 +12,16 @@ type Env = {
1212
S3_BUCKET_RULES: string;
1313
S3_BUCKET_SUBMISSIONS: string;
1414
S3_FORCE_PATH_STYLE: boolean;
15-
EMAIL_PROVIDER: 'console' | 'sendgrid';
15+
EMAIL_PROVIDER: 'console' | 'sendgrid' | 'smtp';
1616
SENDGRID_API_KEY?: string;
1717
SENDGRID_FROM: string;
18+
SMTP_HOST?: string;
19+
SMTP_PORT: number;
20+
SMTP_USER?: string;
21+
SMTP_PASS?: string;
22+
SMTP_FROM?: string;
23+
SMTP_SECURE: boolean;
24+
SMTP_REQUIRE_TLS: boolean;
1825
};
1926

2027
const toBool = (value: string | undefined, fallback: boolean): boolean => {
@@ -51,7 +58,17 @@ export const env: Env = {
5158
S3_BUCKET_RULES: process.env.S3_BUCKET_RULES ?? 'robocon-rules',
5259
S3_BUCKET_SUBMISSIONS: process.env.S3_BUCKET_SUBMISSIONS ?? 'robocon-submissions',
5360
S3_FORCE_PATH_STYLE: toBool(process.env.S3_FORCE_PATH_STYLE, true),
54-
EMAIL_PROVIDER: process.env.EMAIL_PROVIDER === 'sendgrid' ? 'sendgrid' : 'console',
61+
EMAIL_PROVIDER:
62+
process.env.EMAIL_PROVIDER === 'sendgrid' || process.env.EMAIL_PROVIDER === 'smtp'
63+
? process.env.EMAIL_PROVIDER
64+
: 'console',
5565
SENDGRID_API_KEY: process.env.SENDGRID_API_KEY,
5666
SENDGRID_FROM: process.env.SENDGRID_FROM ?? 'noreply@example.com',
67+
SMTP_HOST: process.env.SMTP_HOST,
68+
SMTP_PORT: Number(process.env.SMTP_PORT ?? 587),
69+
SMTP_USER: process.env.SMTP_USER,
70+
SMTP_PASS: process.env.SMTP_PASS,
71+
SMTP_FROM: process.env.SMTP_FROM,
72+
SMTP_SECURE: toBool(process.env.SMTP_SECURE, false),
73+
SMTP_REQUIRE_TLS: toBool(process.env.SMTP_REQUIRE_TLS, false),
5774
};

0 commit comments

Comments
 (0)