Skip to content

Commit 7a4cdcf

Browse files
newt239claude
andcommitted
feat: メール一括送信APIでユーザーID指定による送信をサポート
addressesに加えてuserIdsでも送信先を指定可能にし、ユーザーIDから学籍番号を取得して大学メールアドレスを自動生成する機能を追加。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent bde266a commit 7a4cdcf

File tree

7 files changed

+206
-117
lines changed

7 files changed

+206
-117
lines changed

document/bundle.gen.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3211,19 +3211,25 @@ components:
32113211
ja: 備考
32123212
ReqPostMail:
32133213
required:
3214-
- addresses
32153214
- subject
32163215
- body
32173216
properties:
32183217
addresses:
32193218
type: array
3220-
minItems: 1
32213219
items:
32223220
type: string
32233221
format: email
32243222
x-oapi-codegen-extra-tags:
3225-
validate: required
3223+
validate: 'omitempty,dive,email'
32263224
ja: 送信先アドレス
3225+
userIds:
3226+
type: array
3227+
items:
3228+
type: string
3229+
format: uuid
3230+
x-oapi-codegen-extra-tags:
3231+
validate: 'omitempty,dive,uuid'
3232+
ja: 送信先ユーザーID
32273233
subject:
32283234
type: string
32293235
x-oapi-codegen-extra-tags:
@@ -3236,7 +3242,6 @@ components:
32363242
ja: 本文
32373243
sendToAdmin:
32383244
type: boolean
3239-
default: false
32403245
x-oapi-codegen-extra-tags:
32413246
ja: 管理者用アドレスにも送信
32423247
ResPostMail:

document/schemas/req_post_mail.yml

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
required:
2-
- addresses
32
- subject
43
- body
54
properties:
65
addresses:
76
type: array
8-
minItems: 1
97
items:
108
type: string
119
format: email
1210
x-oapi-codegen-extra-tags:
13-
validate: required
11+
validate: omitempty,dive,email
1412
ja: 送信先アドレス
13+
userIds:
14+
type: array
15+
items:
16+
type: string
17+
format: uuid
18+
x-oapi-codegen-extra-tags:
19+
validate: omitempty,dive,uuid
20+
ja: 送信先ユーザーID
1521
subject:
1622
type: string
1723
x-oapi-codegen-extra-tags:
@@ -24,6 +30,5 @@ properties:
2430
ja: 本文
2531
sendToAdmin:
2632
type: boolean
27-
default: false
2833
x-oapi-codegen-extra-tags:
2934
ja: 管理者用アドレスにも送信

pkg/api/models.gen.go

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/api/spec.gen.go

Lines changed: 97 additions & 97 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
SELECT BIN_TO_UUID(users.id) AS user_id, user_profiles.student_number FROM users LEFT JOIN user_profiles ON users.id = user_profiles.user_id WHERE BIN_TO_UUID(users.id) IN /*userIds*/('00000000-0000-0000-0000-000000000000');

pkg/mail/post_mail.go

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package mail
22

33
import (
4+
"fmt"
45
"net/http"
56

67
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/admin"
78
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/api"
89
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/api/response"
910
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/db"
1011
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/env"
12+
"github.com/SIT-DigiCre/digicore_v3_backend/pkg/user"
1113
"github.com/labstack/echo/v4"
1214
"github.com/sirupsen/logrus"
1315
)
@@ -28,10 +30,62 @@ func PostMail(ctx echo.Context, dbClient db.Client, requestBody api.ReqPostMail)
2830
}
2931
}
3032

31-
// OpenAPIのEmail型をstringに変換
32-
addresses := make([]string, 0, len(requestBody.Addresses)+1)
33-
for _, address := range requestBody.Addresses {
34-
addresses = append(addresses, string(address))
33+
// addressesとuserIdsの両方が空の場合はエラー
34+
addressCount := 0
35+
if requestBody.Addresses != nil {
36+
addressCount = len(*requestBody.Addresses)
37+
}
38+
userIdCount := 0
39+
if requestBody.UserIds != nil {
40+
userIdCount = len(*requestBody.UserIds)
41+
}
42+
if addressCount == 0 && userIdCount == 0 {
43+
return api.ResPostMail{}, &response.Error{
44+
Code: http.StatusBadRequest,
45+
Level: "Info",
46+
Message: "送信先アドレスまたは送信先ユーザーIDのいずれかを指定してください",
47+
Log: "both addresses and userIds are empty",
48+
}
49+
}
50+
51+
addresses := make([]string, 0, addressCount+userIdCount+1)
52+
failures := []struct {
53+
Address string `json:"address"`
54+
Error string `json:"error"`
55+
}{}
56+
57+
// メールアドレスを追加
58+
if requestBody.Addresses != nil {
59+
for _, addr := range *requestBody.Addresses {
60+
addresses = append(addresses, string(addr))
61+
}
62+
}
63+
64+
// ユーザーIDから学籍番号を一括取得してメールアドレスを生成
65+
if requestBody.UserIds != nil && len(*requestBody.UserIds) > 0 {
66+
userIds := make([]string, len(*requestBody.UserIds))
67+
for i, uid := range *requestBody.UserIds {
68+
userIds[i] = uid.String()
69+
}
70+
studentNumbers, respErr := user.GetStudentNumbersFromUserIds(dbClient, userIds)
71+
if respErr != nil {
72+
return api.ResPostMail{}, respErr
73+
}
74+
for _, uid := range userIds {
75+
sn, ok := studentNumbers[uid]
76+
if !ok || sn == "" {
77+
logrus.Warnf("ユーザーID %s の学籍番号が見つかりません", uid)
78+
failures = append(failures, struct {
79+
Address string `json:"address"`
80+
Error string `json:"error"`
81+
}{
82+
Address: fmt.Sprintf("user_id:%s", uid),
83+
Error: "学籍番号が見つかりません",
84+
})
85+
continue
86+
}
87+
addresses = append(addresses, fmt.Sprintf("%s@shibaura-it.ac.jp", sn))
88+
}
3589
}
3690

3791
if requestBody.SendToAdmin != nil && *requestBody.SendToAdmin {
@@ -41,10 +95,6 @@ func PostMail(ctx echo.Context, dbClient db.Client, requestBody api.ReqPostMail)
4195
}
4296

4397
successCount := 0
44-
failures := []struct {
45-
Address string `json:"address"`
46-
Error string `json:"error"`
47-
}{}
4898

4999
for _, address := range addresses {
50100
err := sendEmail(address, requestBody.Subject, requestBody.Body)

pkg/user/users.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package user
22

33
import (
4+
"database/sql"
45
"fmt"
56
"net/http"
67

@@ -64,6 +65,32 @@ type introduction struct {
6465
Introduction string `db:"introduction"`
6566
}
6667

68+
func GetStudentNumbersFromUserIds(dbClient db.Client, userIds []string) (map[string]string, *response.Error) {
69+
if len(userIds) == 0 {
70+
return map[string]string{}, nil
71+
}
72+
params := struct {
73+
UserIds []string `twowaysql:"userIds"`
74+
}{
75+
UserIds: userIds,
76+
}
77+
rows := []struct {
78+
UserId string `db:"user_id"`
79+
StudentNumber sql.NullString `db:"student_number"`
80+
}{}
81+
err := dbClient.Select(&rows, "sql/user/select_student_numbers_from_user_ids.sql", &params)
82+
if err != nil {
83+
return nil, &response.Error{Code: http.StatusInternalServerError, Level: "Error", Message: "ユーザー情報の取得に失敗しました", Log: err.Error()}
84+
}
85+
result := make(map[string]string, len(rows))
86+
for _, row := range rows {
87+
if row.StudentNumber.Valid {
88+
result[row.UserId] = row.StudentNumber.String
89+
}
90+
}
91+
return result, nil
92+
}
93+
6794
func GetUserIntroductionFromUserId(dbClient db.Client, userId string) (introduction, *response.Error) {
6895
params := struct {
6996
UserId string `twowaysql:"userId"`

0 commit comments

Comments
 (0)