Merge pull request #3 from neatflowcv/daily
implement list daily weathers
This commit is contained in:
@@ -7,5 +7,4 @@ linters:
|
|||||||
- tagliatelle
|
- tagliatelle
|
||||||
- wsl # deprecated
|
- wsl # deprecated
|
||||||
- varnamelen
|
- varnamelen
|
||||||
build-tags:
|
- prealloc
|
||||||
- integration
|
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -29,6 +29,10 @@ test:
|
|||||||
.PHONY: validate
|
.PHONY: validate
|
||||||
validate: fix test
|
validate: fix test
|
||||||
|
|
||||||
|
.PHONY: generate
|
||||||
|
generate:
|
||||||
|
make -C api generate
|
||||||
|
|
||||||
.PHONY: cover
|
.PHONY: cover
|
||||||
cover:
|
cover:
|
||||||
go test ./... --coverpkg ./... -coverprofile=c.out
|
go test ./... --coverpkg ./... -coverprofile=c.out
|
||||||
|
|||||||
4
api/Makefile
Normal file
4
api/Makefile
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.PHONY: generate
|
||||||
|
generate:
|
||||||
|
bunx tsp compile .
|
||||||
|
oapi-codegen -package=api -generate=chi-server,models,strict-server -o gen.go tsp-output/schema/openapi.yaml
|
||||||
431
api/gen.go
Normal file
431
api/gen.go
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
// Package api provides primitives to interact with the openapi HTTP API.
|
||||||
|
//
|
||||||
|
// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.1 DO NOT EDIT.
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/oapi-codegen/runtime"
|
||||||
|
strictnethttp "github.com/oapi-codegen/runtime/strictmiddleware/nethttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Defines values for WeatherCondition.
|
||||||
|
const (
|
||||||
|
CLEAR WeatherCondition = "CLEAR"
|
||||||
|
CLOUDY WeatherCondition = "CLOUDY"
|
||||||
|
RAIN WeatherCondition = "RAIN"
|
||||||
|
RAINSNOW WeatherCondition = "RAIN_SNOW"
|
||||||
|
SNOW WeatherCondition = "SNOW"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Celsius 섭씨 온도(℃)
|
||||||
|
type Celsius = float32
|
||||||
|
|
||||||
|
// DailyForecast defines model for DailyForecast.
|
||||||
|
type DailyForecast struct {
|
||||||
|
// Condition 하루 전체를 대표하는 날씨 상태
|
||||||
|
Condition WeatherCondition `json:"condition"`
|
||||||
|
|
||||||
|
// Date 해당 날짜
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
|
||||||
|
// High 하루 동안의 예상 최고 기온(℃)
|
||||||
|
High Celsius `json:"high"`
|
||||||
|
|
||||||
|
// Low 하루 동안의 예상 최저 기온(℃)
|
||||||
|
Low Celsius `json:"low"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DailyForecastList defines model for DailyForecastList.
|
||||||
|
type DailyForecastList struct {
|
||||||
|
Items []DailyForecast `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Error defines model for Error.
|
||||||
|
type Error struct {
|
||||||
|
// ErrorCode 에러 코드
|
||||||
|
ErrorCode int32 `json:"errorCode"`
|
||||||
|
|
||||||
|
// ErrorMessage 에러 메시지
|
||||||
|
ErrorMessage string `json:"errorMessage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HourlyForecast defines model for HourlyForecast.
|
||||||
|
type HourlyForecast struct {
|
||||||
|
// Condition 1시간 동안의 예상 날씨 상태
|
||||||
|
Condition WeatherCondition `json:"condition"`
|
||||||
|
|
||||||
|
// Temperature 1시간 동안의 예상 기온(℃)
|
||||||
|
Temperature Celsius `json:"temperature"`
|
||||||
|
|
||||||
|
// Time 예상 시간
|
||||||
|
Time time.Time `json:"time"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HourlyForecastList defines model for HourlyForecastList.
|
||||||
|
type HourlyForecastList struct {
|
||||||
|
Items []HourlyForecast `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WeatherCondition defines model for WeatherCondition.
|
||||||
|
type WeatherCondition string
|
||||||
|
|
||||||
|
// ForecastsListDailyParams defines parameters for ForecastsListDaily.
|
||||||
|
type ForecastsListDailyParams struct {
|
||||||
|
// Timezone 타임존 (기본값: Asia/Seoul)
|
||||||
|
Timezone *string `form:"timezone,omitempty" json:"timezone,omitempty"`
|
||||||
|
|
||||||
|
// Now 현재 시간 (기본값: 현재 시간)
|
||||||
|
Now *time.Time `form:"now,omitempty" json:"now,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInterface represents all server handlers.
|
||||||
|
type ServerInterface interface {
|
||||||
|
|
||||||
|
// (GET /forecasts/daily)
|
||||||
|
ForecastsListDaily(w http.ResponseWriter, r *http.Request, params ForecastsListDailyParams)
|
||||||
|
|
||||||
|
// (GET /forecasts/hourly)
|
||||||
|
ForecastsListHourly(w http.ResponseWriter, r *http.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unimplemented server implementation that returns http.StatusNotImplemented for each endpoint.
|
||||||
|
|
||||||
|
type Unimplemented struct{}
|
||||||
|
|
||||||
|
// (GET /forecasts/daily)
|
||||||
|
func (_ Unimplemented) ForecastsListDaily(w http.ResponseWriter, r *http.Request, params ForecastsListDailyParams) {
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// (GET /forecasts/hourly)
|
||||||
|
func (_ Unimplemented) ForecastsListHourly(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.WriteHeader(http.StatusNotImplemented)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerInterfaceWrapper converts contexts to parameters.
|
||||||
|
type ServerInterfaceWrapper struct {
|
||||||
|
Handler ServerInterface
|
||||||
|
HandlerMiddlewares []MiddlewareFunc
|
||||||
|
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type MiddlewareFunc func(http.Handler) http.Handler
|
||||||
|
|
||||||
|
// ForecastsListDaily operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) ForecastsListDaily(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Parameter object where we will unmarshal all parameters from the context
|
||||||
|
var params ForecastsListDailyParams
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "timezone" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", false, false, "timezone", r.URL.Query(), ¶ms.Timezone)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "timezone", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- Optional query parameter "now" -------------
|
||||||
|
|
||||||
|
err = runtime.BindQueryParameter("form", false, false, "now", r.URL.Query(), ¶ms.Now)
|
||||||
|
if err != nil {
|
||||||
|
siw.ErrorHandlerFunc(w, r, &InvalidParamFormatError{ParamName: "now", Err: err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
siw.Handler.ForecastsListDaily(w, r, params)
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
handler = middleware(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForecastsListHourly operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) ForecastsListHourly(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
|
handler := http.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
siw.Handler.ForecastsListHourly(w, r)
|
||||||
|
}))
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
handler = middleware(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnescapedCookieParamError struct {
|
||||||
|
ParamName string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnescapedCookieParamError) Error() string {
|
||||||
|
return fmt.Sprintf("error unescaping cookie parameter '%s'", e.ParamName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnescapedCookieParamError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type UnmarshalingParamError struct {
|
||||||
|
ParamName string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnmarshalingParamError) Error() string {
|
||||||
|
return fmt.Sprintf("Error unmarshaling parameter %s as JSON: %s", e.ParamName, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *UnmarshalingParamError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequiredParamError struct {
|
||||||
|
ParamName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequiredParamError) Error() string {
|
||||||
|
return fmt.Sprintf("Query argument %s is required, but not found", e.ParamName)
|
||||||
|
}
|
||||||
|
|
||||||
|
type RequiredHeaderError struct {
|
||||||
|
ParamName string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequiredHeaderError) Error() string {
|
||||||
|
return fmt.Sprintf("Header parameter %s is required, but not found", e.ParamName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RequiredHeaderError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvalidParamFormatError struct {
|
||||||
|
ParamName string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidParamFormatError) Error() string {
|
||||||
|
return fmt.Sprintf("Invalid format for parameter %s: %s", e.ParamName, e.Err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *InvalidParamFormatError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type TooManyValuesForParamError struct {
|
||||||
|
ParamName string
|
||||||
|
Count int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *TooManyValuesForParamError) Error() string {
|
||||||
|
return fmt.Sprintf("Expected one value for %s, got %d", e.ParamName, e.Count)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handler creates http.Handler with routing matching OpenAPI spec.
|
||||||
|
func Handler(si ServerInterface) http.Handler {
|
||||||
|
return HandlerWithOptions(si, ChiServerOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChiServerOptions struct {
|
||||||
|
BaseURL string
|
||||||
|
BaseRouter chi.Router
|
||||||
|
Middlewares []MiddlewareFunc
|
||||||
|
ErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerFromMux creates http.Handler with routing matching OpenAPI spec based on the provided mux.
|
||||||
|
func HandlerFromMux(si ServerInterface, r chi.Router) http.Handler {
|
||||||
|
return HandlerWithOptions(si, ChiServerOptions{
|
||||||
|
BaseRouter: r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func HandlerFromMuxWithBaseURL(si ServerInterface, r chi.Router, baseURL string) http.Handler {
|
||||||
|
return HandlerWithOptions(si, ChiServerOptions{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
BaseRouter: r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlerWithOptions creates http.Handler with additional options
|
||||||
|
func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handler {
|
||||||
|
r := options.BaseRouter
|
||||||
|
|
||||||
|
if r == nil {
|
||||||
|
r = chi.NewRouter()
|
||||||
|
}
|
||||||
|
if options.ErrorHandlerFunc == nil {
|
||||||
|
options.ErrorHandlerFunc = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
wrapper := ServerInterfaceWrapper{
|
||||||
|
Handler: si,
|
||||||
|
HandlerMiddlewares: options.Middlewares,
|
||||||
|
ErrorHandlerFunc: options.ErrorHandlerFunc,
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Get(options.BaseURL+"/forecasts/daily", wrapper.ForecastsListDaily)
|
||||||
|
})
|
||||||
|
r.Group(func(r chi.Router) {
|
||||||
|
r.Get(options.BaseURL+"/forecasts/hourly", wrapper.ForecastsListHourly)
|
||||||
|
})
|
||||||
|
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListDailyRequestObject struct {
|
||||||
|
Params ForecastsListDailyParams
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListDailyResponseObject interface {
|
||||||
|
VisitForecastsListDailyResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListDaily200JSONResponse DailyForecastList
|
||||||
|
|
||||||
|
func (response ForecastsListDaily200JSONResponse) VisitForecastsListDailyResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListDaily500JSONResponse Error
|
||||||
|
|
||||||
|
func (response ForecastsListDaily500JSONResponse) VisitForecastsListDailyResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListHourlyRequestObject struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListHourlyResponseObject interface {
|
||||||
|
VisitForecastsListHourlyResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListHourly200JSONResponse HourlyForecastList
|
||||||
|
|
||||||
|
func (response ForecastsListHourly200JSONResponse) VisitForecastsListHourlyResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ForecastsListHourly500JSONResponse Error
|
||||||
|
|
||||||
|
func (response ForecastsListHourly500JSONResponse) VisitForecastsListHourlyResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(500)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StrictServerInterface represents all server handlers.
|
||||||
|
type StrictServerInterface interface {
|
||||||
|
|
||||||
|
// (GET /forecasts/daily)
|
||||||
|
ForecastsListDaily(ctx context.Context, request ForecastsListDailyRequestObject) (ForecastsListDailyResponseObject, error)
|
||||||
|
|
||||||
|
// (GET /forecasts/hourly)
|
||||||
|
ForecastsListHourly(ctx context.Context, request ForecastsListHourlyRequestObject) (ForecastsListHourlyResponseObject, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type StrictHandlerFunc = strictnethttp.StrictHTTPHandlerFunc
|
||||||
|
type StrictMiddlewareFunc = strictnethttp.StrictHTTPMiddlewareFunc
|
||||||
|
|
||||||
|
type StrictHTTPServerOptions struct {
|
||||||
|
RequestErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||||
|
ResponseErrorHandlerFunc func(w http.ResponseWriter, r *http.Request, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStrictHandler(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc) ServerInterface {
|
||||||
|
return &strictHandler{ssi: ssi, middlewares: middlewares, options: StrictHTTPServerOptions{
|
||||||
|
RequestErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
|
},
|
||||||
|
ResponseErrorHandlerFunc: func(w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewStrictHandlerWithOptions(ssi StrictServerInterface, middlewares []StrictMiddlewareFunc, options StrictHTTPServerOptions) ServerInterface {
|
||||||
|
return &strictHandler{ssi: ssi, middlewares: middlewares, options: options}
|
||||||
|
}
|
||||||
|
|
||||||
|
type strictHandler struct {
|
||||||
|
ssi StrictServerInterface
|
||||||
|
middlewares []StrictMiddlewareFunc
|
||||||
|
options StrictHTTPServerOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForecastsListDaily operation middleware
|
||||||
|
func (sh *strictHandler) ForecastsListDaily(w http.ResponseWriter, r *http.Request, params ForecastsListDailyParams) {
|
||||||
|
var request ForecastsListDailyRequestObject
|
||||||
|
|
||||||
|
request.Params = params
|
||||||
|
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.ForecastsListDaily(ctx, request.(ForecastsListDailyRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "ForecastsListDaily")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(r.Context(), w, r, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
} else if validResponse, ok := response.(ForecastsListDailyResponseObject); ok {
|
||||||
|
if err := validResponse.VisitForecastsListDailyResponse(w); err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForecastsListHourly operation middleware
|
||||||
|
func (sh *strictHandler) ForecastsListHourly(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var request ForecastsListHourlyRequestObject
|
||||||
|
|
||||||
|
handler := func(ctx context.Context, w http.ResponseWriter, r *http.Request, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.ForecastsListHourly(ctx, request.(ForecastsListHourlyRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "ForecastsListHourly")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(r.Context(), w, r, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
} else if validResponse, ok := response.(ForecastsListHourlyResponseObject); ok {
|
||||||
|
if err := validResponse.VisitForecastsListHourlyResponse(w); err != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
sh.options.ResponseErrorHandlerFunc(w, r, fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
11
api/main.tsp
11
api/main.tsp
@@ -62,7 +62,16 @@ model Error {
|
|||||||
interface Forecasts {
|
interface Forecasts {
|
||||||
@get
|
@get
|
||||||
@route("/daily")
|
@route("/daily")
|
||||||
listDaily(): {
|
@doc("일일 날씨 예보 목록을 조회합니다.")
|
||||||
|
listDaily(
|
||||||
|
@query
|
||||||
|
@doc("타임존 (기본값: Asia/Seoul)")
|
||||||
|
timezone?: string,
|
||||||
|
|
||||||
|
@query
|
||||||
|
@doc("현재 시간 (기본값: 현재 시간)")
|
||||||
|
now?: offsetDateTime,
|
||||||
|
): {
|
||||||
@statusCode statusCode: 200;
|
@statusCode statusCode: 200;
|
||||||
@body body: DailyForecastList;
|
@body body: DailyForecastList;
|
||||||
} | {
|
} | {
|
||||||
|
|||||||
67
cmd/seven-skies/handler.go
Normal file
67
cmd/seven-skies/handler.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
//nolint:ireturn
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/neatflowcv/seven-skies/api"
|
||||||
|
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ api.StrictServerInterface = (*Handler)(nil)
|
||||||
|
|
||||||
|
type Handler struct {
|
||||||
|
flow *flow.Flow
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewHandler(flow *flow.Flow) *Handler {
|
||||||
|
return &Handler{
|
||||||
|
flow: flow,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ForecastsListDaily(
|
||||||
|
ctx context.Context,
|
||||||
|
req api.ForecastsListDailyRequestObject,
|
||||||
|
) (api.ForecastsListDailyResponseObject, error) {
|
||||||
|
timezone := "Asia/Seoul"
|
||||||
|
if req.Params.Timezone != nil {
|
||||||
|
timezone = *req.Params.Timezone
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
if req.Params.Now != nil {
|
||||||
|
now = *req.Params.Now
|
||||||
|
}
|
||||||
|
|
||||||
|
weathers, err := h.flow.ListDailyWeathers(ctx, timezone, now)
|
||||||
|
if err != nil {
|
||||||
|
return api.ForecastsListDaily500JSONResponse{ //nolint:nilerr
|
||||||
|
ErrorCode: http.StatusInternalServerError,
|
||||||
|
ErrorMessage: err.Error(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []api.DailyForecast
|
||||||
|
for _, weather := range weathers {
|
||||||
|
items = append(items, api.DailyForecast{
|
||||||
|
Date: weather.Date,
|
||||||
|
High: api.Celsius(weather.High),
|
||||||
|
Low: api.Celsius(weather.Low),
|
||||||
|
Condition: api.WeatherCondition(weather.Condition),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return api.ForecastsListDaily200JSONResponse{
|
||||||
|
Items: items,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Handler) ForecastsListHourly(
|
||||||
|
ctx context.Context,
|
||||||
|
req api.ForecastsListHourlyRequestObject,
|
||||||
|
) (api.ForecastsListHourlyResponseObject, error) {
|
||||||
|
panic("unimplemented")
|
||||||
|
}
|
||||||
@@ -5,10 +5,14 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
|
"github.com/neatflowcv/seven-skies/api"
|
||||||
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
"github.com/neatflowcv/seven-skies/internal/app/flow"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/broker/nats"
|
"github.com/neatflowcv/seven-skies/internal/pkg/broker/nats"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||||
@@ -98,5 +102,25 @@ func main() {
|
|||||||
log.Panic("error in subscribe", err)
|
log.Panic("error in subscribe", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
select {}
|
err = serve(service)
|
||||||
|
if err != nil {
|
||||||
|
log.Panic("error in serve", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func serve(service *flow.Flow) error {
|
||||||
|
const timeout = 5 * time.Second
|
||||||
|
|
||||||
|
server := &http.Server{ //nolint:exhaustruct
|
||||||
|
ReadHeaderTimeout: timeout,
|
||||||
|
Handler: api.HandlerFromMux(api.NewStrictHandler(NewHandler(service), nil), chi.NewRouter()),
|
||||||
|
Addr: "0.0.0.0:8080",
|
||||||
|
}
|
||||||
|
|
||||||
|
err := server.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("error in listen and serve: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -3,9 +3,11 @@ module github.com/neatflowcv/seven-skies
|
|||||||
go 1.25.5
|
go 1.25.5
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2
|
github.com/go-co-op/gocron/v2 v2.18.2
|
||||||
github.com/joho/godotenv v1.5.1
|
github.com/joho/godotenv v1.5.1
|
||||||
github.com/nats-io/nats.go v1.47.0
|
github.com/nats-io/nats.go v1.47.0
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2
|
||||||
github.com/oklog/ulid/v2 v2.1.1
|
github.com/oklog/ulid/v2 v2.1.1
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
gorm.io/driver/postgres v1.6.0
|
gorm.io/driver/postgres v1.6.0
|
||||||
@@ -14,6 +16,7 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -1,7 +1,13 @@
|
|||||||
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||||
|
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2 h1:+5VU41FUXPWSPKLXZQ/77SGzUiPCcakU0v7ENc2H20Q=
|
github.com/go-co-op/gocron/v2 v2.18.2 h1:+5VU41FUXPWSPKLXZQ/77SGzUiPCcakU0v7ENc2H20Q=
|
||||||
github.com/go-co-op/gocron/v2 v2.18.2/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
|
github.com/go-co-op/gocron/v2 v2.18.2/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
@@ -22,6 +28,7 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
|||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||||
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I=
|
||||||
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60=
|
||||||
|
github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE=
|
||||||
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
|
||||||
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
@@ -34,6 +41,8 @@ github.com/nats-io/nkeys v0.4.12 h1:nssm7JKOG9/x4J8II47VWCL1Ds29avyiQDRn0ckMvDc=
|
|||||||
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
github.com/nats-io/nkeys v0.4.12/go.mod h1:MT59A1HYcjIcyQDJStTfaOY6vhy9XTUjOFo+SVsvpBg=
|
||||||
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
|
||||||
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI=
|
||||||
|
github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg=
|
||||||
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s=
|
||||||
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ=
|
||||||
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o=
|
||||||
@@ -44,6 +53,7 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
||||||
@@ -45,3 +47,86 @@ func (f *Flow) CreateWeather(ctx context.Context, weather *Weather) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *Flow) ListDailyWeathers(
|
||||||
|
ctx context.Context,
|
||||||
|
timezone string,
|
||||||
|
now time.Time,
|
||||||
|
) ([]*DailyWeather, error) {
|
||||||
|
location, err := time.LoadLocation(timezone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in load location: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const aWeek = 7
|
||||||
|
|
||||||
|
localNow := now.In(location)
|
||||||
|
|
||||||
|
from := time.Date(localNow.Year(), localNow.Month(), localNow.Day(), 0, 0, 0, 0, location)
|
||||||
|
to := from.AddDate(0, 0, aWeek).Add(-time.Nanosecond)
|
||||||
|
|
||||||
|
weathers, err := f.repo.ListWeathers(ctx, from, to)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error in list daily weathers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
weathersByDate := f.groupByDate(weathers, location)
|
||||||
|
|
||||||
|
var dailyWeathers []*DailyWeather
|
||||||
|
|
||||||
|
for dateKey, weathers := range weathersByDate {
|
||||||
|
newVar := f.mergeWeathers(dateKey, location, weathers)
|
||||||
|
dailyWeathers = append(dailyWeathers, newVar)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(dailyWeathers, func(i, j int) bool {
|
||||||
|
return dailyWeathers[i].Date.Before(dailyWeathers[j].Date)
|
||||||
|
})
|
||||||
|
|
||||||
|
return dailyWeathers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Flow) mergeWeathers(dateKey string, location *time.Location, weathers []*domain.Weather) *DailyWeather {
|
||||||
|
date, _ := time.Parse("2006-01-02", dateKey)
|
||||||
|
date = time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, location)
|
||||||
|
|
||||||
|
high := float64(weathers[0].Temperature().Value)
|
||||||
|
low := float64(weathers[0].Temperature().Value)
|
||||||
|
worstCondition := weathers[0].Condition()
|
||||||
|
|
||||||
|
for _, w := range weathers {
|
||||||
|
temp := float64(w.Temperature().Value)
|
||||||
|
if temp > high {
|
||||||
|
high = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
if temp < low {
|
||||||
|
low = temp
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Condition().IsWorseThan(worstCondition) {
|
||||||
|
worstCondition = w.Condition()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newVar := &DailyWeather{
|
||||||
|
Date: date,
|
||||||
|
High: high,
|
||||||
|
Low: low,
|
||||||
|
Condition: string(worstCondition),
|
||||||
|
}
|
||||||
|
|
||||||
|
return newVar
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*Flow) groupByDate(weathers []*domain.Weather, location *time.Location) map[string][]*domain.Weather {
|
||||||
|
weathersByDate := make(map[string][]*domain.Weather)
|
||||||
|
|
||||||
|
for _, weather := range weathers {
|
||||||
|
localDate := weather.TargetDate().In(location)
|
||||||
|
dateKey := time.Date(localDate.Year(), localDate.Month(), localDate.Day(), 0, 0, 0, 0, location).Format("2006-01-02")
|
||||||
|
weathersByDate[dateKey] = append(weathersByDate[dateKey], weather)
|
||||||
|
}
|
||||||
|
|
||||||
|
return weathersByDate
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,3 +9,10 @@ type Weather struct {
|
|||||||
Condition string
|
Condition string
|
||||||
Temperature float64 // Celsius
|
Temperature float64 // Celsius
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DailyWeather struct {
|
||||||
|
Date time.Time
|
||||||
|
High float64 // Celsius
|
||||||
|
Low float64 // Celsius
|
||||||
|
Condition string
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,3 +10,31 @@ const (
|
|||||||
WeatherConditionSnow WeatherCondition = "SNOW"
|
WeatherConditionSnow WeatherCondition = "SNOW"
|
||||||
WeatherConditionRainSnow WeatherCondition = "RAIN_SNOW"
|
WeatherConditionRainSnow WeatherCondition = "RAIN_SNOW"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsWorseThan은 현재 날씨 조건이 other보다 나쁜 조건인지 확인합니다.
|
||||||
|
// 우선순위: RAIN > RAIN_SNOW > SNOW > CLOUDY > CLEAR.
|
||||||
|
func (c WeatherCondition) IsWorseThan(other WeatherCondition) bool {
|
||||||
|
return c.priority() > other.priority()
|
||||||
|
}
|
||||||
|
|
||||||
|
// priority는 날씨 조건의 우선순위를 반환합니다.
|
||||||
|
// 우선순위가 높을수록 더 나쁜 날씨 조건입니다.
|
||||||
|
// 우선순위: RAIN(4) > RAIN_SNOW(3) > SNOW(2) > CLOUDY(1) > CLEAR(0) > UNKNOWN(-1).
|
||||||
|
func (c WeatherCondition) priority() int {
|
||||||
|
switch c {
|
||||||
|
case WeatherConditionClear:
|
||||||
|
return 0
|
||||||
|
case WeatherConditionCloudy:
|
||||||
|
return 1
|
||||||
|
case WeatherConditionSnow:
|
||||||
|
return 2 //nolint:mnd
|
||||||
|
case WeatherConditionRainSnow:
|
||||||
|
return 3 //nolint:mnd
|
||||||
|
case WeatherConditionRain:
|
||||||
|
return 4 //nolint:mnd
|
||||||
|
case WeatherConditionUnknown:
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package gorm
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
"github.com/neatflowcv/seven-skies/internal/pkg/repository"
|
||||||
@@ -42,3 +43,17 @@ func (r *Repository) CreateWeather(ctx context.Context, weather *domain.Weather)
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *Repository) ListWeathers(ctx context.Context, from, to time.Time) ([]*domain.Weather, error) {
|
||||||
|
weathers, err := gorm.G[*Weather](r.db).Where("target_date BETWEEN ? AND ?", from, to).Find(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to list weathers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var ret []*domain.Weather
|
||||||
|
for _, weather := range weathers {
|
||||||
|
ret = append(ret, weather.toDomain())
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,3 +25,14 @@ func newModelWeather(weather *domain.Weather) *Weather {
|
|||||||
Temperature: float64(weather.Temperature().Value),
|
Temperature: float64(weather.Temperature().Value),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Weather) toDomain() *domain.Weather {
|
||||||
|
return domain.NewWeather(
|
||||||
|
w.ID,
|
||||||
|
domain.WeatherSource(w.Source),
|
||||||
|
w.TargetDate,
|
||||||
|
w.ForecastDate,
|
||||||
|
domain.WeatherCondition(w.Condition),
|
||||||
|
domain.Temperature{Value: domain.Celsius(w.Temperature)},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,12 @@ package repository
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
"github.com/neatflowcv/seven-skies/internal/pkg/domain"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repository interface {
|
type Repository interface {
|
||||||
CreateWeather(ctx context.Context, weather *domain.Weather) error
|
CreateWeather(ctx context.Context, weather *domain.Weather) error
|
||||||
|
ListWeathers(ctx context.Context, from, to time.Time) ([]*domain.Weather, error)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user