Skip to content

Commit cf9075b

Browse files
authored
Merge pull request #39 from janog-netcon/improve-vmdb-api
vmdb-apiをいろいろ改良した
2 parents 1fe3dce + 3d42240 commit cf9075b

File tree

3 files changed

+100
-49
lines changed

3 files changed

+100
-49
lines changed

vmdb-api/controller.go

Lines changed: 70 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88
"time"
99

10+
"github.com/go-chi/chi/v5"
1011
"github.com/google/uuid"
1112
)
1213

@@ -20,6 +21,33 @@ func (c *Controller) hello(w http.ResponseWriter, r *http.Request) {
2021
w.Write([]byte(GREETING))
2122
}
2223

24+
type answerResponse struct {
25+
ID uuid.UUID `json:"id"`
26+
ProblemID uuid.UUID `json:"problem_id"`
27+
ProblemCode string `json:"problem_code"`
28+
TeamID uuid.UUID `json:"team_id"`
29+
Body string `json:"body"`
30+
CreatedAt time.Time `json:"created_at"`
31+
UpdatedAt time.Time `json:"updated_at"`
32+
}
33+
34+
func newAnswerResponseFrom(answer Answer, problem Problem) answerResponse {
35+
bodies := []string{}
36+
for _, b := range answer.Bodies {
37+
bodies = append(bodies, b...)
38+
}
39+
40+
return answerResponse{
41+
ID: answer.ID,
42+
ProblemID: answer.ProblemID,
43+
ProblemCode: problem.Code,
44+
TeamID: answer.TeamID,
45+
CreatedAt: answer.CreatedAt,
46+
UpdatedAt: answer.UpdatedAt,
47+
Body: strings.Join(bodies, "\n"),
48+
}
49+
}
50+
2351
type listProblemEnvironmentsResponse []ProblemEnvironment
2452

2553
func (c *Controller) listProblemEnvironments(w http.ResponseWriter, r *http.Request) {
@@ -65,8 +93,8 @@ func (c *Controller) getAnswerID(w http.ResponseWriter, r *http.Request) {
6593

6694
latestAnswer, err := c.repo.findLatestAnswerFor(
6795
ctx,
68-
problemEnvironment.ProblemID.String(),
69-
problemEnvironment.TeamID.String(),
96+
problemEnvironment.ProblemID,
97+
problemEnvironment.TeamID,
7098
)
7199
if err != nil {
72100
slog.ErrorContext(ctx, "failed to find latest Answer", "error", err)
@@ -90,19 +118,9 @@ func (c *Controller) getAnswerID(w http.ResponseWriter, r *http.Request) {
90118
}
91119
}
92120

93-
type listLatestUnconfirmedAnswersForLocalProblemResponse []listLatestUnconfirmedAnswersForLocalProblemResponseItem
94-
95-
type listLatestUnconfirmedAnswersForLocalProblemResponseItem struct {
96-
ID uuid.UUID `json:"id"`
97-
ProblemID uuid.UUID `json:"problem_id"`
98-
ProblemCode string `json:"problem_code"`
99-
TeamID uuid.UUID `json:"team_id"`
100-
CreatedAt time.Time `json:"created_at"`
101-
UpdatedAt time.Time `json:"updated_at"`
102-
Body string `json:"body"`
103-
}
121+
type listUnconfirmedAnswersForLocalProblemResponse []answerResponse
104122

105-
func (c *Controller) listLatestUnconfirmedAnswersForLocalProblem(w http.ResponseWriter, r *http.Request) {
123+
func (c *Controller) listUnscoredAnswersForLocalProblem(w http.ResponseWriter, r *http.Request) {
106124
ctx := r.Context()
107125

108126
config, err := c.repo.findConfigBy(ctx, "local_problem_codes")
@@ -119,51 +137,63 @@ func (c *Controller) listLatestUnconfirmedAnswersForLocalProblem(w http.Response
119137
return
120138
}
121139

122-
response := listLatestUnconfirmedAnswersForLocalProblemResponse{}
140+
response := listUnconfirmedAnswersForLocalProblemResponse{}
123141
for _, code := range strings.Split(value, ",") {
124142
code = strings.TrimSpace(code)
125143

126-
problem, err := c.repo.findProblemBy(ctx, code)
144+
problem, err := c.repo.findProblemByCode(ctx, code)
127145
if err != nil {
128146
slog.WarnContext(ctx, "failed to find Problem", "error", err, "code", code)
129147
continue
130148
}
131149

132-
answers, err := c.repo.listLatestUnscoredAnswersFor(ctx, problem.ID)
150+
answers, err := c.repo.listUnscoredAnswersFor(ctx, problem.ID)
133151
if err != nil {
134152
slog.ErrorContext(ctx, "failed to list latest unconfirmed Answers", "error", err)
135153
w.WriteHeader(http.StatusInternalServerError)
136154
return
137155
}
138156

139-
latestAnswers := map[uuid.UUID]Answer{}
140157
for _, answer := range answers {
141-
if _, ok := latestAnswers[answer.TeamID]; !ok {
142-
latestAnswers[answer.TeamID] = answer
143-
}
144-
if answer.CreatedAt.After(latestAnswers[answer.TeamID].CreatedAt) {
145-
latestAnswers[answer.TeamID] = answer
146-
}
158+
response = append(response, newAnswerResponseFrom(answer, *problem))
147159
}
160+
}
148161

149-
for _, answer := range latestAnswers {
150-
bodies := []string{}
151-
for _, b := range answer.Bodies {
152-
bodies = append(bodies, b...)
153-
}
154-
155-
response = append(response, listLatestUnconfirmedAnswersForLocalProblemResponseItem{
156-
ID: answer.ID,
157-
ProblemID: answer.ProblemID,
158-
ProblemCode: problem.Code,
159-
TeamID: answer.TeamID,
160-
CreatedAt: answer.CreatedAt,
161-
UpdatedAt: answer.UpdatedAt,
162-
Body: strings.Join(bodies, "\n"),
163-
})
164-
}
162+
if err := renderJSON(w, http.StatusOK, response); err != nil {
163+
slog.ErrorContext(ctx, "failed to render JSON", "error", err)
164+
w.WriteHeader(http.StatusInternalServerError)
165+
}
166+
}
167+
168+
type getAnswerInformationResponse answerResponse
169+
170+
func (c *Controller) getAnswerInformation(w http.ResponseWriter, r *http.Request) {
171+
ctx := r.Context()
172+
173+
answerIDStr := chi.URLParam(r, "answerID")
174+
answerID, err := uuid.Parse(answerIDStr)
175+
if err != nil {
176+
slog.WarnContext(ctx, "invalid query parameters", "answer_id", answerIDStr)
177+
w.WriteHeader(http.StatusBadRequest)
178+
return
179+
}
180+
181+
answer, err := c.repo.findAnswerBy(ctx, answerID)
182+
if err != nil {
183+
slog.WarnContext(ctx, "failed to find Answer", "error", err)
184+
w.WriteHeader(http.StatusNotFound)
185+
return
165186
}
166187

188+
problem, err := c.repo.findProblemBy(ctx, answer.ProblemID)
189+
if err != nil {
190+
slog.ErrorContext(ctx, "failed to find Problem", "error", err)
191+
w.WriteHeader(http.StatusInternalServerError)
192+
return
193+
}
194+
195+
response := getAnswerInformationResponse(newAnswerResponseFrom(*answer, *problem))
196+
167197
if err := renderJSON(w, http.StatusOK, response); err != nil {
168198
slog.ErrorContext(ctx, "failed to render JSON", "error", err)
169199
w.WriteHeader(http.StatusInternalServerError)

vmdb-api/main.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,10 +75,13 @@ func (c *Command) RunE(cmd *cobra.Command, _ []string) error {
7575
r.Use(middleware.Heartbeat("/healthz"))
7676
r.Use(middleware.Recoverer)
7777

78+
// 後方互換性のために、そのままのI/Fで提供する
7879
r.Get("/", controller.hello)
7980
r.Get("/problem-environments", controller.listProblemEnvironments)
8081
r.Get("/answer-id", controller.getAnswerID)
81-
r.Get("/local-problem-answers", controller.listLatestUnconfirmedAnswersForLocalProblem)
82+
83+
r.Get("/local-problem-answers", controller.listUnscoredAnswersForLocalProblem)
84+
r.Get("/answers/{answerID}", controller.getAnswerInformation)
8285

8386
server := Server{
8487
ListenAddr: c.listenAddr,

vmdb-api/repository.go

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ import (
99
"github.com/uptrace/bun"
1010
)
1111

12-
// ignoredTeams is a list of teams that should be ignored by exporter.
13-
var ignoredTeams = []string{"staff", "team99", "audience"}
14-
1512
type Repository struct {
1613
db *bun.DB
1714
}
@@ -50,7 +47,18 @@ func (r *Repository) findProblemEnvironmentBy(ctx context.Context, name string)
5047
return &result, nil
5148
}
5249

53-
func (r *Repository) findProblemBy(ctx context.Context, code string) (*Problem, error) {
50+
func (r *Repository) findProblemBy(ctx context.Context, problemID uuid.UUID) (*Problem, error) {
51+
var result Problem
52+
err := r.db.NewSelect().Model(&result).
53+
Where("id = ?", problemID).
54+
Scan(ctx)
55+
if err != nil {
56+
return nil, err
57+
}
58+
return &result, nil
59+
}
60+
61+
func (r *Repository) findProblemByCode(ctx context.Context, code string) (*Problem, error) {
5462
var result Problem
5563
err := r.db.NewSelect().Model(&result).
5664
Where("code = ?", code).
@@ -61,22 +69,32 @@ func (r *Repository) findProblemBy(ctx context.Context, code string) (*Problem,
6169
return &result, nil
6270
}
6371

64-
func (r *Repository) listLatestUnscoredAnswersFor(ctx context.Context, problemID uuid.UUID) ([]Answer, error) {
72+
func (r *Repository) findAnswerBy(ctx context.Context, answerID uuid.UUID) (*Answer, error) {
73+
var result Answer
74+
err := r.db.NewSelect().Model(&result).
75+
Where("id = ?", answerID).
76+
Scan(ctx)
77+
if err != nil {
78+
return nil, err
79+
}
80+
return &result, nil
81+
}
82+
83+
func (r *Repository) listUnscoredAnswersFor(ctx context.Context, problemID uuid.UUID) ([]Answer, error) {
6584
var result []Answer
6685
err := r.db.NewSelect().ColumnExpr("answers.*").
6786
Table("answers").
6887
Join("INNER JOIN scores").JoinOn("answers.id = scores.answer_id").
69-
Where("point IS NULL").
7088
Where("problem_id = ?", problemID).
71-
Order("created_at DESC").
89+
Where("point IS NULL").
7290
Scan(ctx, &result)
7391
if err != nil {
7492
return nil, err
7593
}
7694
return result, nil
7795
}
7896

79-
func (r *Repository) findLatestAnswerFor(ctx context.Context, problemID, teamID string) (*Answer, error) {
97+
func (r *Repository) findLatestAnswerFor(ctx context.Context, problemID uuid.UUID, teamID uuid.UUID) (*Answer, error) {
8098
var result Answer
8199
err := r.db.NewSelect().Model(&result).
82100
Where("team_id = ?", teamID).

0 commit comments

Comments
 (0)