git.0x0001f346.de/andreas/api integration

This commit is contained in:
Andreas Schulte 2023-10-10 21:47:08 +02:00
parent 7f0cb3d1ce
commit 16eaba69c4
Signed by: andreas
GPG Key ID: DCD1B6A247B69DB6
4 changed files with 292 additions and 69 deletions

View File

@ -5,7 +5,7 @@ As a fan of [TimeAPI.io](https://www.timeapi.io/swagger/index.html), but not thi
## Usage ## Usage
```shell ```shell
neo@matrix:~$ curl -s -H "Accept: application/json" "http://localhost:9100/Europe/Berlin" | jq neo@matrix:~$ curl -s -H "Accept: application/json" "http://localhost:9100/Time/current/zone?timeZone=Europe/Berlin" | jq
{ {
"year": 2023, "year": 2023,
"month": 6, "month": 6,

140
apiTime/api.go Normal file
View File

@ -0,0 +1,140 @@
package apiTime
import "git.0x0001f346.de/andreas/api"
var tags []string = []string{"Time"}
func GetComponents() *api.Components {
components := &api.Components{
Schemas: map[string]*api.ComponentsSchema{
"CurrentTime": {
Properties: map[string]*api.ComponentsProperty{
"year": {
Type: "integer",
Description: "Year",
Format: "int64",
Example: 2020,
},
"month": {
Type: "integer",
Description: "Month",
Format: "int64",
Example: 12,
},
"day": {
Type: "integer",
Description: "Day",
Format: "int64",
Example: 13,
},
"hour": {
Type: "integer",
Description: "Hour of the day in range 0-24",
Format: "int64",
Example: 9,
},
"minute": {
Type: "integer",
Description: "Minute",
Format: "int64",
Example: 30,
},
"seconds": {
Type: "integer",
Description: "Second",
Format: "int64",
Example: 30,
},
"milliSeconds": {
Type: "integer",
Description: "Milliseconds",
Format: "int64",
Example: 123,
},
"dateTime": {
Type: "string",
Description: "Full date time",
Format: "date-time",
Example: "2020-12-13T09:30:30+01:00",
},
"date": {
Type: "string",
Description: "Date string",
Example: "13/12/2020",
},
"time": {
Type: "string",
Description: "Time string",
Example: "09:30",
},
"timeZone": {
Type: "string",
Description: "TimeZone of the result",
Example: "Europe/Berlin",
},
"dayOfWeek": {
Type: "string",
Description: "The day of the week",
Example: "Monday",
},
"dstActive": {
Type: "boolean",
Description: "Boolean describing whether DST is applied and active in that timezone",
Example: false,
},
},
AdditionalProperties: false,
},
},
}
return components
}
func GetPaths() map[string]*api.Path {
paths := map[string]*api.Path{
"/Time/current/zone": {
GET: &api.Method{
Function: TimeCurrentZone,
Tags: tags,
Summary: "Gets the current time of a time zone.",
Parameters: []*api.Parameters{
{
Name: "timeZone",
In: "query",
Description: "Full IANA time zone names.",
Schema: &api.ParametersSchema{
Type: "string",
},
Example: "Europe/Berlin",
AllowReserved: true,
},
},
Responses: map[int]*api.Response{
200: {
Description: "Current time",
Content: map[string]*api.ResponseContent{
"application/json": {
Schema: &api.ResponseSchema{
Ref: "#/components/schemas/CurrentTime",
},
},
},
},
400: {
Description: "Error message",
Content: map[string]*api.ResponseContent{
"application/json": {
Schema: &api.ResponseSchema{
Type: "string",
},
},
},
},
},
},
},
}
return paths
}

81
apiTime/time.go Normal file
View File

@ -0,0 +1,81 @@
package apiTime
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"time"
)
type dateTimeResponse struct {
Year int `json:"year,omitempty"`
Month int `json:"month,omitempty"`
Day int `json:"day,omitempty"`
Hour int `json:"hour,omitempty"`
Minute int `json:"minute,omitempty"`
Seconds int `json:"seconds,omitempty"`
MilliSeconds int `json:"milliSeconds,omitempty"`
DateTime string `json:"dateTime,omitempty"`
Date string `json:"date,omitempty"`
Time string `json:"time,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
DayOfWeek string `json:"dayOfWeek,omitempty"`
DstActive bool `json:"dstActive,omitempty"`
}
func TimeCurrentZone(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Language", "en")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
timeZone := ""
vals := r.URL.Query()
parameters, ok := vals["timeZone"]
if ok && len(parameters) == 1 {
timeZone = parameters[0]
}
if timeZone == "" {
w.WriteHeader(400)
fmt.Fprint(w, "{\"error\": \"invalid value for timeZone\"}")
return
}
loc, err := time.LoadLocation(timeZone)
if err != nil {
w.WriteHeader(400)
fmt.Fprint(w, "{\"error\": \"invalid value for timeZone\"}")
return
}
log.Printf("%q %q\n", loc.String(), r.RemoteAddr)
w.WriteHeader(200)
fmt.Fprint(w, getDateTimeResponseAsJSONString(loc))
}
func getDateTimeResponse(loc *time.Location) dateTimeResponse {
t := time.Now().In(loc)
return dateTimeResponse{
Year: t.Year(),
Month: int(t.Month()),
Day: t.Day(),
Hour: t.Hour(),
Minute: t.Minute(),
Seconds: t.Second(),
MilliSeconds: func(i int64, err error) int { return int(i) }(strconv.ParseInt(strconv.Itoa(t.Nanosecond())[:3], 10, 0)),
DateTime: t.Format(time.RFC3339),
Date: fmt.Sprintf("%02d/%02d/%04d", t.Month(), t.Day(), t.Year()),
Time: fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute()),
TimeZone: loc.String(),
DayOfWeek: t.Weekday().String(),
DstActive: t.IsDST(),
}
}
func getDateTimeResponseAsJSONString(loc *time.Location) string {
r, _ := json.Marshal(getDateTimeResponse(loc))
return string(r)
}

138
main.go
View File

@ -1,34 +1,83 @@
package main package main
import ( import (
"encoding/json"
"fmt" "fmt"
"log" "log"
"net/http" "net/http"
"strconv"
"time" "git.0x0001f346.de/andreas/Datetime-API/apiTime"
"git.0x0001f346.de/andreas/api"
"github.com/gorilla/mux"
) )
var portToListenOn int = 9100 const apiTitle string = "Datetime-API"
var defaultTZ string = "Europe/Berlin" const apiDescription string = "A simple API to get the current time for a given time zone."
const apiVersion string = "0.1"
type DateTimeResponse struct { const portToListenOn int = 9100
Year int `json:"year,omitempty"`
Month int `json:"month,omitempty"`
Day int `json:"day,omitempty"`
Hour int `json:"hour,omitempty"`
Minute int `json:"minute,omitempty"`
Seconds int `json:"seconds,omitempty"`
MilliSeconds int `json:"milliSeconds,omitempty"`
DateTime string `json:"dateTime,omitempty"`
Date string `json:"date,omitempty"`
Time string `json:"time,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
DayOfWeek string `json:"dayOfWeek,omitempty"`
DstActive bool `json:"dstActive,omitempty"`
}
func main() { func main() {
API := buildAPI()
router := mux.NewRouter()
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Language", "en")
w.Header().Set("Content-Type", "application/json; charset=utf-8")
fmt.Fprint(w, API.ToJSONString())
}).Methods("GET")
for route, path := range API.Paths {
if path.DELETE != nil {
if path.DELETE.Function == nil {
continue
}
router.HandleFunc(route, path.DELETE.Function).Methods("DELETE")
}
if path.GET != nil {
if path.GET.Function == nil {
continue
}
router.HandleFunc(route, path.GET.Function).Methods("GET")
}
if path.PATCH != nil {
if path.PATCH.Function == nil {
continue
}
router.HandleFunc(route, path.PATCH.Function).Methods("PATCH")
}
if path.POST != nil {
if path.POST.Function == nil {
continue
}
router.HandleFunc(route, path.POST.Function).Methods("POST")
}
if path.PUT != nil {
if path.PUT.Function == nil {
continue
}
router.HandleFunc(route, path.PUT.Function).Methods("PUT")
}
}
printStartupBanner()
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", portToListenOn), router))
}
func buildAPI() api.API {
fullAPI := api.GetAPIPrototype(apiTitle, apiDescription, apiVersion)
for route, path := range apiTime.GetPaths() {
fullAPI.Paths[route] = path
}
for name, schema := range apiTime.GetComponents().Schemas {
fullAPI.Components.Schemas[name] = schema
}
return fullAPI
}
func printStartupBanner() {
fmt.Println("********************************************") fmt.Println("********************************************")
fmt.Println("* *") fmt.Println("* *")
fmt.Println("* git.0x0001f346.de/andreas/Datetime-API *") fmt.Println("* git.0x0001f346.de/andreas/Datetime-API *")
@ -37,51 +86,4 @@ func main() {
fmt.Println("") fmt.Println("")
fmt.Printf("Listening: http://localhost:%d\n", portToListenOn) fmt.Printf("Listening: http://localhost:%d\n", portToListenOn)
fmt.Println("") fmt.Println("")
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
loc, _ := time.LoadLocation(defaultTZ)
if r.URL.String() != "/" {
newloc, err := time.LoadLocation(r.URL.String()[1:])
if err == nil {
loc = newloc
}
}
log.Printf(
"%q %q\n",
loc.String(),
r.RemoteAddr,
)
w.Header().Add("Content-Language", "en")
w.Header().Add("Content-Type", "application/json; charset=utf-8")
w.Header().Add("Server", "git.0x0001f346.de/andreas/Datetime-API")
fmt.Fprint(w, GetDateTimeResponseAsJSONString(loc))
})
log.Fatal(http.ListenAndServe(fmt.Sprintf(":%d", portToListenOn), nil))
}
func GetDateTimeResponse(loc *time.Location) DateTimeResponse {
t := time.Now().In(loc)
return DateTimeResponse{
Year: t.Year(),
Month: int(t.Month()),
Day: t.Day(),
Hour: t.Hour(),
Minute: t.Minute(),
Seconds: t.Second(),
MilliSeconds: func(i int64, err error) int { return int(i) }(strconv.ParseInt(strconv.Itoa(t.Nanosecond())[:3], 10, 0)),
DateTime: t.Format(time.RFC3339),
Date: fmt.Sprintf("%02d/%02d/%04d", t.Month(), t.Day(), t.Year()),
Time: fmt.Sprintf("%02d:%02d", t.Hour(), t.Minute()),
TimeZone: loc.String(),
DayOfWeek: t.Weekday().String(),
DstActive: t.IsDST(),
}
}
func GetDateTimeResponseAsJSONString(loc *time.Location) string {
r, _ := json.Marshal(GetDateTimeResponse(loc))
return string(r)
} }