diff --git a/internal/httpapi/api.go b/internal/httpapi/api.go index 3bd542b..9ed2ddb 100644 --- a/internal/httpapi/api.go +++ b/internal/httpapi/api.go @@ -819,7 +819,23 @@ func (a *API) dailyReportReminders(w http.ResponseWriter, r *http.Request, p *au func (a *API) monthlyReports(w http.ResponseWriter, r *http.Request, p *auth.Principal, st *store.Store) { switch r.Method { case http.MethodGet: - items, next, err := st.MonthlyReports(r.Context(), p.UserID, r.URL.Query().Get("cursor"), limit(r, 50, 100)) + fromMonth := "" + if rawMonth := strings.TrimSpace(r.URL.Query().Get("from_month")); rawMonth != "" { + if !validReportMonth(rawMonth) { + writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "from_month"}) + return + } + fromMonth = rawMonth + } + toMonth := "" + if rawMonth := strings.TrimSpace(r.URL.Query().Get("to_month")); rawMonth != "" { + if !validReportMonth(rawMonth) { + writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "to_month"}) + return + } + toMonth = rawMonth + } + items, next, err := st.MonthlyReports(r.Context(), p.UserID, fromMonth, toMonth, r.URL.Query().Get("cursor"), limit(r, 50, 100)) if err != nil { writeStoreError(w, r, err) return @@ -832,7 +848,12 @@ func (a *API) monthlyReports(w http.ResponseWriter, r *http.Request, p *auth.Pri if !decodeBody(w, r, &body) { return } - report, err := st.CreateMonthlyDraft(r.Context(), p.UserID, "mr_"+randomToken(12), strings.TrimSpace(body.Month)) + month := strings.TrimSpace(body.Month) + if !validReportMonth(month) { + writeError(w, r, http.StatusBadRequest, 30000, reasonMalformedRequest, "malformed request", map[string]any{"field": "month"}) + return + } + report, err := st.CreateMonthlyDraft(r.Context(), p.UserID, "mr_"+randomToken(12), month) if err != nil { writeStoreError(w, r, err) return @@ -1091,6 +1112,11 @@ func parseDate(value string) (time.Time, error) { return time.ParseInLocation("2006-01-02", strings.TrimSpace(value), time.Local) } +func validReportMonth(value string) bool { + _, err := time.ParseInLocation("2006-01", strings.TrimSpace(value), time.Local) + return err == nil +} + func parseTime(value string) (time.Time, error) { value = strings.TrimSpace(value) if value == "" { diff --git a/internal/store/mysql.go b/internal/store/mysql.go index 25c8e0d..ea3aa48 100644 --- a/internal/store/mysql.go +++ b/internal/store/mysql.go @@ -935,10 +935,18 @@ func (s *Store) DailyReportStats(ctx context.Context, date time.Time, department return stats, nil } -func (s *Store) MonthlyReports(ctx context.Context, userID int64, cursor string, limit int) ([]MonthlyReport, string, error) { +func (s *Store) MonthlyReports(ctx context.Context, userID int64, fromMonth, toMonth, cursor string, limit int) ([]MonthlyReport, string, error) { var items []MonthlyReport query := `SELECT report_id,author_id,report_month,work_summary,next_plan,attachments,status,read_state,version,submitted_at,created_at,updated_at FROM im_monthly_reports WHERE tenant_id=? AND author_id=?` args := []any{s.tenant(), userID} + if fromMonth != "" { + query += ` AND report_month>=?` + args = append(args, fromMonth) + } + if toMonth != "" { + query += ` AND report_month<=?` + args = append(args, toMonth) + } if cursor != "" { query += ` AND report_id>?` args = append(args, cursor)