diff --git a/README.md b/README.md index c54a8e1..b44a4e8 100644 --- a/README.md +++ b/README.md @@ -186,14 +186,13 @@ Wakapi uses [GORM](https://gorm.io) as an ORM. As a consequence, a set of differ See the [advanced setup instructions](docs/advanced_setup.md). ## 🔧 API Endpoints -The following API endpoints are available. A more detailed Swagger documentation is about to come ([#40](https://github.com/muety/wakapi/issues/40)). +See our [Swagger API Documentation](https://wakapi.dev/swagger-ui). -* `POST /api/heartbeat` -* `GET /api/summary` - * `string` parameter `interval`: One of `today`, `day`, `week`, `month`, `year`, `any` -* `GET /api/compat/wakatime/v1/users/current/all_time_since_today` (see [Wakatime API docs](https://wakatime.com/developers#all_time_since_today)) -* `GET /api/compat/wakatime/v1/users/current/summaries` (see [Wakatime API docs](https://wakatime.com/developers#summaries)) -* `GET /api/health` +### Generating Swagger docs +```bash +$ go get -u github.com/swaggo/swag/cmd/swag +$ swag init -o static/docs +``` ## 🤝 Integrations ### Prometheus Export diff --git a/go.mod b/go.mod index 3c617be..47b22eb 100644 --- a/go.mod +++ b/go.mod @@ -3,27 +3,29 @@ module github.com/muety/wakapi go 1.13 require ( + github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 github.com/emvi/logbuch v1.1.1 github.com/go-co-op/gocron v0.3.3 + github.com/go-openapi/spec v0.20.2 // indirect github.com/gorilla/handlers v1.4.2 github.com/gorilla/mux v1.7.3 github.com/gorilla/schema v1.1.0 github.com/gorilla/securecookie v1.1.1 github.com/jinzhu/configor v1.2.0 - github.com/kr/text v0.2.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/markbates/pkger v0.17.1 github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect github.com/mitchellh/hashstructure/v2 v2.0.1 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/patrickmn/go-cache v2.1.0+incompatible github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.6.1 + github.com/swaggo/swag v1.7.0 go.uber.org/atomic v1.6.0 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 - golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect - gopkg.in/yaml.v2 v2.2.8 // indirect + golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/tools v0.1.0 // indirect gorm.io/driver/mysql v1.0.3 gorm.io/driver/postgres v1.0.5 gorm.io/driver/sqlite v1.1.3 diff --git a/go.sum b/go.sum index c6f38eb..0456aed 100644 --- a/go.sum +++ b/go.sum @@ -3,11 +3,18 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -42,7 +49,9 @@ github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= @@ -65,6 +74,7 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-co-op/gocron v0.3.3 h1:QnarcMZWWKrEP25uCbtDiLsnnGw+PhCjL3wNITdWJOs= github.com/go-co-op/gocron v0.3.3/go.mod h1:Y9PWlYqDChf2Nbgg7kfS+ZsXHDTZbMZYPEQ0MILqH+M= @@ -74,6 +84,23 @@ github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgO github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/jsonreference v0.19.5 h1:1WJP/wi4OjB4iV8KVbH73rQaoialJrqv8gitZLxGLtM= +github.com/go-openapi/jsonreference v0.19.5/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= +github.com/go-openapi/spec v0.19.14 h1:r4fbYFo6N4ZelmSX8G6p+cv/hZRXzcuqQIADGT1iNKM= +github.com/go-openapi/spec v0.19.14/go.mod h1:gwrgJS15eCUgjLpMjBJmbZezCsw88LmgeEip0M63doA= +github.com/go-openapi/spec v0.20.2 h1:pFPUZsiIbZ20kLUcuCGeuQWG735fPMxW7wHF9BWlnQU= +github.com/go-openapi/spec v0.20.2/go.mod h1:RW6Xcbs6LOyWLU/mXGdzn2Qc+3aj+ASfI7rvSZh1Vls= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.11 h1:RFTu/dlFySpyVvJDfp/7674JY4SDglYWKztbiIGFpmc= +github.com/go-openapi/swag v0.19.11/go.mod h1:Uc0gKkdR+ojzsEpjh39QChyu92vPgIr72POcgHMAgSY= +github.com/go-openapi/swag v0.19.13 h1:233UVgMy1DlmCYYfOiFpta6e2urloh+sEs5id6lyzog= +github.com/go-openapi/swag v0.19.13/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= @@ -219,6 +246,8 @@ github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= @@ -246,6 +275,12 @@ github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-b github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/pkger v0.17.1 h1:/MKEtWqtc0mZvu9OinB9UzVN9iYCwLWuyUv4Bw+PCno= github.com/markbates/pkger v0.17.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= @@ -355,7 +390,9 @@ github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OK github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc h1:+2DdDcxVYlarHjYcZTt8dZ4Ec8cXZirzL5ko0mkKPjU= github.com/rubenv/sql-migrate v0.0.0-20200402132117-435005d389bc/go.mod h1:DCgfY80j8GYL7MLEfvcpSFvjD0L5yZq/aZUJmhZklyg= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= @@ -365,6 +402,7 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= @@ -397,12 +435,18 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/swaggo/swag v1.7.0 h1:5bCA/MTLQoIqDXXyHfOpMeDvL9j68OY/udlK4pQoo4E= +github.com/swaggo/swag v1.7.0/go.mod h1:BdPIL73gvS9NBsdi7M1JOxLvlbfvNRaBP8m6WT6Aajo= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= @@ -447,6 +491,8 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -463,7 +509,13 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -473,6 +525,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -496,10 +550,21 @@ golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -517,14 +582,21 @@ golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191004055002-72853e10c5a3/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114 h1:DnSr2mCsxyCE6ZgIkmcWUQY2R5cH/6wL7eIxEmQOMSE= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e h1:t96dS3DO8DGjawSLJL/HIdz8CycAd2v07XxqB3UPTi0= +golang.org/x/tools v0.0.0-20201120155355-20be4ac4bd6e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -562,12 +634,17 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.0.3 h1:+JKBYPfn1tygR1/of/Fh2T8iwuVwzt+PEJmKaXzMQXg= gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= gorm.io/driver/postgres v1.0.5 h1:raX6ezL/ciUmaYTvOq48jq1GE95aMC0CmxQYbxQ4Ufw= diff --git a/main.go b/main.go index dde7b7d..af2f9be 100644 --- a/main.go +++ b/main.go @@ -56,6 +56,26 @@ var ( // TODO: Refactor entire project to be structured after business domains +// @title Wakapi API +// @version 1.0 +// @description REST API to interact with [Wakapi](https://wakapi.dev) +// @description +// @description ## Authentication +// @description Set header `Authorization` to your API Key encoded as Base64 and prefixed with `Basic` +// @description **Example:** `Basic ODY2NDhkNzQtMTljNS00NTJiLWJhMDEtZmIzZWM3MGQ0YzJmCg==` + +// @contact.name Ferdinand Mütsch +// @contact.url https://github.com/muety +// @contact.email ferdinand@muetsch.io + +// @license.name GPL-3.0 +// @license.url https://github.com/muety/wakapi/blob/master/LICENSE + +// @securitydefinitions.apikey ApiKeyAuth +// @in header +// @name Authorization + +// @BasePath /api func main() { config = conf.Load() @@ -173,7 +193,10 @@ func main() { shieldV1BadgeHandler.RegisterRoutes(apiRouter) // Static Routes - router.PathPrefix("/assets").Handler(http.FileServer(pkger.Dir("/static"))) + fileServer := http.FileServer(pkger.Dir("/static")) + router.PathPrefix("/assets").Handler(fileServer) + router.PathPrefix("/swagger-ui").Handler(fileServer) + router.PathPrefix("/docs").Handler(fileServer) // Listen HTTP listen(router) diff --git a/models/heartbeat.go b/models/heartbeat.go index 5d71d84..003fce3 100644 --- a/models/heartbeat.go +++ b/models/heartbeat.go @@ -22,7 +22,7 @@ type Heartbeat struct { Editor string `json:"editor" hash:"ignore"` // ignored because editor might be parsed differently by wakatime OperatingSystem string `json:"operating_system" hash:"ignore"` // ignored because os might be parsed differently by wakatime Machine string `json:"machine" hash:"ignore"` // ignored because wakatime api doesn't return machines currently - Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user"` + Time CustomTime `json:"time" gorm:"type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time,idx_time_user" swaggertype:"primitive,number"` Hash string `json:"-" gorm:"type:varchar(17); uniqueIndex"` Origin string `json:"-" hash:"ignore"` OriginId string `json:"-" hash:"ignore"` diff --git a/models/summary.go b/models/summary.go index 7cae400..f1408fe 100644 --- a/models/summary.go +++ b/models/summary.go @@ -14,48 +14,14 @@ const ( SummaryMachine uint8 = 4 ) -// Support Wakapi and WakaTime range / interval identifiers -// See https://wakatime.com/developers/#summaries -var ( - IntervalToday = &IntervalKey{"today", "Today"} - IntervalYesterday = &IntervalKey{"day", "yesterday", "Yesterday"} - IntervalThisWeek = &IntervalKey{"week", "This Week"} - IntervalLastWeek = &IntervalKey{"Last Week"} - IntervalThisMonth = &IntervalKey{"month", "This Month"} - IntervalLastMonth = &IntervalKey{"Last Month"} - IntervalThisYear = &IntervalKey{"year"} - IntervalPast7Days = &IntervalKey{"7_days", "last_7_days", "Last 7 Days"} - IntervalPast7DaysYesterday = &IntervalKey{"Last 7 Days from Yesterday"} - IntervalPast14Days = &IntervalKey{"Last 14 Days"} - IntervalPast30Days = &IntervalKey{"30_days", "last_30_days", "Last 30 Days"} - IntervalPast12Months = &IntervalKey{"12_months", "last_12_months"} - IntervalAny = &IntervalKey{"any"} -) - -var AllIntervals = []*IntervalKey{ - IntervalToday, - IntervalYesterday, - IntervalThisWeek, - IntervalLastWeek, - IntervalThisMonth, - IntervalLastMonth, - IntervalThisYear, - IntervalPast7Days, - IntervalPast7DaysYesterday, - IntervalPast14Days, - IntervalPast30Days, - IntervalPast12Months, - IntervalAny, -} - const UnknownSummaryKey = "unknown" type Summary struct { ID uint `json:"-" gorm:"primary_key"` User *User `json:"-" gorm:"not null; constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` UserID string `json:"user_id" gorm:"not null; index:idx_time_summary_user"` - FromTime CustomTime `json:"from" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user"` - ToTime CustomTime `json:"to" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user"` + FromTime CustomTime `json:"from" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"` + ToTime CustomTime `json:"to" gorm:"not null; type:timestamp; default:CURRENT_TIMESTAMP; index:idx_time_summary_user" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"` Projects SummaryItems `json:"projects" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Languages SummaryItems `json:"languages" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` Editors SummaryItems `json:"editors" gorm:"constraint:OnUpdate:CASCADE,OnDelete:CASCADE"` @@ -71,7 +37,7 @@ type SummaryItem struct { SummaryID uint `json:"-"` Type uint8 `json:"-"` Key string `json:"key"` - Total time.Duration `json:"total"` + Total time.Duration `json:"total" swaggertype:"primitive,integer"` } type SummaryItemContainer struct { diff --git a/models/user.go b/models/user.go index bc85eeb..17e3dd3 100644 --- a/models/user.go +++ b/models/user.go @@ -4,8 +4,8 @@ type User struct { ID string `json:"id" gorm:"primary_key"` ApiKey string `json:"api_key" gorm:"unique"` Password string `json:"-"` - CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"` - LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP"` + CreatedAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"` + LastLoggedInAt CustomTime `gorm:"type:timestamp; default:CURRENT_TIMESTAMP" swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"` ShareDataMaxDays int `json:"-" gorm:"default:0"` ShareEditors bool `json:"-" gorm:"default:false; type:bool"` ShareLanguages bool `json:"-" gorm:"default:false; type:bool"` @@ -34,7 +34,7 @@ type CredentialsReset struct { type TimeByUser struct { User string - Time CustomTime + Time CustomTime `swaggertype:"string" format:"date" example:"2006-01-02 15:04:05.000"` } func (c *CredentialsReset) IsValid() bool { diff --git a/routes/api/health.go b/routes/api/health.go index a8165ef..72741e2 100644 --- a/routes/api/health.go +++ b/routes/api/health.go @@ -20,6 +20,12 @@ func (h *HealthApiHandler) RegisterRoutes(router *mux.Router) { r.Methods(http.MethodGet).HandlerFunc(h.Get) } +// @Summary Check the application's health status +// @ID get-health +// @Tags misc +// @Produce plain +// @Success 200 {string} string +// @Router /health [get] func (h *HealthApiHandler) Get(w http.ResponseWriter, r *http.Request) { var dbStatus int if sqlDb, err := h.db.DB(); err == nil { diff --git a/routes/api/heartbeat.go b/routes/api/heartbeat.go index 56eaa44..f5da92e 100644 --- a/routes/api/heartbeat.go +++ b/routes/api/heartbeat.go @@ -43,6 +43,14 @@ func (h *HeartbeatApiHandler) RegisterRoutes(router *mux.Router) { r.Methods(http.MethodPost).HandlerFunc(h.Post) } +// @Summary Push a new heartbeat +// @ID post-heartbeat +// @Tags heartbeat +// @Accept json +// @Param heartbeat body models.Heartbeat true "A heartbeat" +// @Security ApiKeyAuth +// @Success 201 +// @Router /heartbeat [post] func (h *HeartbeatApiHandler) Post(w http.ResponseWriter, r *http.Request) { var heartbeats []*models.Heartbeat user := r.Context().Value(models.UserKey).(*models.User) diff --git a/routes/api/summary.go b/routes/api/summary.go index 0e3c927..70ee5b7 100644 --- a/routes/api/summary.go +++ b/routes/api/summary.go @@ -32,6 +32,17 @@ func (h *SummaryApiHandler) RegisterRoutes(router *mux.Router) { r.Methods(http.MethodGet).HandlerFunc(h.Get) } +// @Summary Retrieve a summary +// @ID get-summary +// @Tags summary +// @Produce json +// @Param interval query string false "Interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any) +// @Param from query string false "Start date (e.g. '2021-02-07')" +// @Param to query string false "End date (e.g. '2021-02-08')" +// @Param recompute query bool false "Whether to recompute the summary from raw heartbeat or use cache" +// @Security ApiKeyAuth +// @Success 200 {object} models.Summary +// @Router /summary [get] func (h *SummaryApiHandler) Get(w http.ResponseWriter, r *http.Request) { summary, err, status := su.LoadUserSummary(h.summarySrvc, r) if err != nil { diff --git a/routes/compat/shields/v1/badge.go b/routes/compat/shields/v1/badge.go index 7c9ba99..cf57082 100644 --- a/routes/compat/shields/v1/badge.go +++ b/routes/compat/shields/v1/badge.go @@ -38,6 +38,16 @@ func (h *BadgeHandler) RegisterRoutes(router *mux.Router) { r.Methods(http.MethodGet).HandlerFunc(h.Get) } +// @Summary Get badge data +// @Description Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed. +// @ID get-badge +// @Tags badges +// @Produce json +// @Param user path string true "User ID to fetch data for" +// @Param interval path string true "Interval to aggregate data for" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any) +// @Param filter path string true "Filter to apply (e.g. 'project:wakapi' or 'language:Go')" +// @Success 200 {object} v1.BadgeData +// @Router /compat/shields/v1/{user}/{interval}/{filter} [get] func (h *BadgeHandler) Get(w http.ResponseWriter, r *http.Request) { intervalReg := regexp.MustCompile(intervalPattern) entityFilterReg := regexp.MustCompile(entityFilterPattern) diff --git a/routes/compat/wakatime/v1/all_time.go b/routes/compat/wakatime/v1/all_time.go index 9a41670..031afd7 100644 --- a/routes/compat/wakatime/v1/all_time.go +++ b/routes/compat/wakatime/v1/all_time.go @@ -35,6 +35,15 @@ func (h *AllTimeHandler) RegisterRoutes(router *mux.Router) { r.Methods(http.MethodGet).HandlerFunc(h.Get) } +// @Summary Retrieve summary for all time +// @Description Mimics https://wakatime.com/developers#all_time_since_today +// @ID get-all-time +// @Tags wakatime +// @Produce json +// @Param user path string true "User ID to fetch data for (or 'current')" +// @Security ApiKeyAuth +// @Success 200 {object} v1.AllTimeViewModel +// @Router /compat/wakatime/v1/users/{user}/all_time_since_today [get] func (h *AllTimeHandler) Get(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) values, _ := url.ParseQuery(r.URL.RawQuery) diff --git a/routes/compat/wakatime/v1/summaries.go b/routes/compat/wakatime/v1/summaries.go index 73d5803..66bdd7f 100644 --- a/routes/compat/wakatime/v1/summaries.go +++ b/routes/compat/wakatime/v1/summaries.go @@ -41,6 +41,18 @@ func (h *SummariesHandler) RegisterRoutes(router *mux.Router) { // Timezone can be specified via an offset suffix (e.g. +02:00) in date strings. // Requires https://github.com/muety/wakapi/issues/108. +// @Summary Retrieve WakaTime-compatible summaries +// @Description Mimics https://wakatime.com/developers#summaries. +// @ID get-wakatime-summaries +// @Tags wakatime +// @Produce json +// @Param user path string true "User ID to fetch data for (or 'current')" +// @Param range query string false "Range interval identifier" Enums(today, yesterday, week, month, year, 7_days, last_7_days, 30_days, last_30_days, 12_months, last_12_months, any) +// @Param start query string false "Start date (e.g. '2021-02-07')" +// @Param end query string false "End date (e.g. '2021-02-08')" +// @Security ApiKeyAuth +// @Success 200 {object} v1.SummariesViewModel +// @Router /compat/wakatime/v1/users/{user}/summaries [get] func (h *SummariesHandler) Get(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) requestedUser := vars["user"] diff --git a/static/docs/docs.go b/static/docs/docs.go new file mode 100644 index 0000000..71956b1 --- /dev/null +++ b/static/docs/docs.go @@ -0,0 +1,769 @@ +// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT +// This file was generated by swaggo/swag + +package docs + +import ( + "bytes" + "encoding/json" + "strings" + + "github.com/alecthomas/template" + "github.com/swaggo/swag" +) + +var doc = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{.Description}}", + "title": "{{.Title}}", + "contact": { + "name": "Ferdinand Mütsch", + "url": "https://github.com/muety", + "email": "ferdinand@muetsch.io" + }, + "license": { + "name": "GPL-3.0", + "url": "https://github.com/muety/wakapi/blob/master/LICENSE" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/compat/shields/v1/{user}/{interval}/{filter}": { + "get": { + "description": "Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed.", + "produces": [ + "application/json" + ], + "tags": [ + "badges" + ], + "summary": "Get badge data", + "operationId": "get-badge", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Interval to aggregate data for", + "name": "interval", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Filter to apply (e.g. 'project:wakapi' or 'language:Go')", + "name": "filter", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.BadgeData" + } + } + } + } + }, + "/compat/wakatime/v1/users/{user}/all_time_since_today": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Mimics https://wakatime.com/developers#all_time_since_today", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve summary for all time", + "operationId": "get-all-time", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.AllTimeViewModel" + } + } + } + } + }, + "/compat/wakatime/v1/users/{user}/summaries": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Mimics https://wakatime.com/developers#summaries.", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve WakaTime-compatible summaries", + "operationId": "get-wakatime-summaries", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Range interval identifier", + "name": "range", + "in": "query" + }, + { + "type": "string", + "description": "Start date (e.g. '2021-02-07')", + "name": "start", + "in": "query" + }, + { + "type": "string", + "description": "End date (e.g. '2021-02-08')", + "name": "end", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.SummariesViewModel" + } + } + } + } + }, + "/health": { + "get": { + "produces": [ + "text/plain" + ], + "tags": [ + "misc" + ], + "summary": "Check the application's health status", + "operationId": "get-health", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/heartbeat": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "tags": [ + "heartbeat" + ], + "summary": "Push a new heartbeat", + "operationId": "post-heartbeat", + "parameters": [ + { + "description": "A heartbeat", + "name": "heartbeat", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Heartbeat" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/summary": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "summary" + ], + "summary": "Retrieve a summary", + "operationId": "get-summary", + "parameters": [ + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Interval identifier", + "name": "interval", + "in": "query" + }, + { + "type": "string", + "description": "Start date (e.g. '2021-02-07')", + "name": "from", + "in": "query" + }, + { + "type": "string", + "description": "End date (e.g. '2021-02-08')", + "name": "to", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to recompute the summary from raw heartbeat or use cache", + "name": "recompute", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Summary" + } + } + } + } + }, + "/v1/users/{user}/stats/{range}": { + "get": { + "description": "Mimics https://wakatime.com/developers#stats. Requires public data access to be allowed.", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve stats", + "operationId": "get-stats", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Range interval identifier", + "name": "range", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.StatsViewModel" + } + } + } + } + } + }, + "definitions": { + "models.Heartbeat": { + "type": "object", + "properties": { + "branch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "editor": { + "description": "ignored because editor might be parsed differently by wakatime", + "type": "string" + }, + "entity": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_write": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "machine": { + "description": "ignored because wakatime api doesn't return machines currently", + "type": "string" + }, + "operating_system": { + "description": "ignored because os might be parsed differently by wakatime", + "type": "string" + }, + "project": { + "type": "string" + }, + "time": { + "type": "number" + }, + "type": { + "type": "string" + } + } + }, + "models.Summary": { + "type": "object", + "properties": { + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "from": { + "type": "string", + "format": "date", + "example": "2006-01-02 15:04:05.000" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "to": { + "type": "string", + "format": "date", + "example": "2006-01-02 15:04:05.000" + }, + "user_id": { + "type": "string" + } + } + }, + "models.SummaryItem": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "total": { + "type": "integer" + } + } + }, + "v1.AllTimeData": { + "type": "object", + "properties": { + "is_up_to_date": { + "description": "true if the stats are up to date; when false, a 202 response code is returned and stats will be refreshed soon\u003e", + "type": "boolean" + }, + "text": { + "description": "total time logged since account created as human readable string\u003e", + "type": "string" + }, + "total_seconds": { + "description": "total number of seconds logged since account created", + "type": "number" + } + } + }, + "v1.AllTimeViewModel": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1.AllTimeData" + } + } + }, + "v1.BadgeData": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "label": { + "type": "string" + }, + "message": { + "type": "string" + }, + "schemaVersion": { + "type": "integer" + } + } + }, + "v1.StatsData": { + "type": "object", + "properties": { + "daily_average": { + "type": "number" + }, + "days_including_holidays": { + "type": "integer" + }, + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "end": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "start": { + "type": "string" + }, + "total_seconds": { + "type": "number" + }, + "user_id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "v1.StatsViewModel": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1.StatsData" + } + } + }, + "v1.SummariesData": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "grand_total": { + "$ref": "#/definitions/v1.SummariesGrandTotal" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "range": { + "$ref": "#/definitions/v1.SummariesRange" + } + } + }, + "v1.SummariesEntry": { + "type": "object", + "properties": { + "digital": { + "type": "string" + }, + "hours": { + "type": "integer" + }, + "minutes": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "percent": { + "type": "number" + }, + "seconds": { + "type": "integer" + }, + "text": { + "type": "string" + }, + "total_seconds": { + "type": "number" + } + } + }, + "v1.SummariesGrandTotal": { + "type": "object", + "properties": { + "digital": { + "type": "string" + }, + "hours": { + "type": "integer" + }, + "minutes": { + "type": "integer" + }, + "text": { + "type": "string" + }, + "total_seconds": { + "type": "number" + } + } + }, + "v1.SummariesRange": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "end": { + "type": "string" + }, + "start": { + "type": "string" + }, + "text": { + "type": "string" + }, + "timezone": { + "type": "string" + } + } + }, + "v1.SummariesViewModel": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesData" + } + }, + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +type swaggerInfo struct { + Version string + Host string + BasePath string + Schemes []string + Title string + Description string +} + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = swaggerInfo{ + Version: "1.0", + Host: "", + BasePath: "/api", + Schemes: []string{}, + Title: "Wakapi API", + Description: "REST API to interact with [Wakapi](https://wakapi.dev)\n\n## Authentication\nSet header `Authorization` to your API Key encoded as Base64 and prefixed with `Basic`\n**Example:** `Basic ODY2NDhkNzQtMTljNS00NTJiLWJhMDEtZmIzZWM3MGQ0YzJmCg==`", +} + +type s struct{} + +func (s *s) ReadDoc() string { + sInfo := SwaggerInfo + sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) + + t, err := template.New("swagger_info").Funcs(template.FuncMap{ + "marshal": func(v interface{}) string { + a, _ := json.Marshal(v) + return string(a) + }, + }).Parse(doc) + if err != nil { + return doc + } + + var tpl bytes.Buffer + if err := t.Execute(&tpl, sInfo); err != nil { + return doc + } + + return tpl.String() +} + +func init() { + swag.Register(swag.Name, &s{}) +} diff --git a/static/docs/swagger.json b/static/docs/swagger.json new file mode 100644 index 0000000..7733f20 --- /dev/null +++ b/static/docs/swagger.json @@ -0,0 +1,706 @@ +{ + "swagger": "2.0", + "info": { + "description": "REST API to interact with [Wakapi](https://wakapi.dev)\n\n## Authentication\nSet header `Authorization` to your API Key encoded as Base64 and prefixed with `Basic`\n**Example:** `Basic ODY2NDhkNzQtMTljNS00NTJiLWJhMDEtZmIzZWM3MGQ0YzJmCg==`", + "title": "Wakapi API", + "contact": { + "name": "Ferdinand Mütsch", + "url": "https://github.com/muety", + "email": "ferdinand@muetsch.io" + }, + "license": { + "name": "GPL-3.0", + "url": "https://github.com/muety/wakapi/blob/master/LICENSE" + }, + "version": "1.0" + }, + "basePath": "/api", + "paths": { + "/compat/shields/v1/{user}/{interval}/{filter}": { + "get": { + "description": "Retrieve total time for a given entity (e.g. a project) within a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). Requires public data access to be allowed.", + "produces": [ + "application/json" + ], + "tags": [ + "badges" + ], + "summary": "Get badge data", + "operationId": "get-badge", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Interval to aggregate data for", + "name": "interval", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "Filter to apply (e.g. 'project:wakapi' or 'language:Go')", + "name": "filter", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.BadgeData" + } + } + } + } + }, + "/compat/wakatime/v1/users/{user}/all_time_since_today": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Mimics https://wakatime.com/developers#all_time_since_today", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve summary for all time", + "operationId": "get-all-time", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.AllTimeViewModel" + } + } + } + } + }, + "/compat/wakatime/v1/users/{user}/summaries": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "Mimics https://wakatime.com/developers#summaries.", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve WakaTime-compatible summaries", + "operationId": "get-wakatime-summaries", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Range interval identifier", + "name": "range", + "in": "query" + }, + { + "type": "string", + "description": "Start date (e.g. '2021-02-07')", + "name": "start", + "in": "query" + }, + { + "type": "string", + "description": "End date (e.g. '2021-02-08')", + "name": "end", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.SummariesViewModel" + } + } + } + } + }, + "/health": { + "get": { + "produces": [ + "text/plain" + ], + "tags": [ + "misc" + ], + "summary": "Check the application's health status", + "operationId": "get-health", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + } + } + }, + "/heartbeat": { + "post": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "consumes": [ + "application/json" + ], + "tags": [ + "heartbeat" + ], + "summary": "Push a new heartbeat", + "operationId": "post-heartbeat", + "parameters": [ + { + "description": "A heartbeat", + "name": "heartbeat", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Heartbeat" + } + } + ], + "responses": { + "201": { + "description": "" + } + } + } + }, + "/summary": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "summary" + ], + "summary": "Retrieve a summary", + "operationId": "get-summary", + "parameters": [ + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Interval identifier", + "name": "interval", + "in": "query" + }, + { + "type": "string", + "description": "Start date (e.g. '2021-02-07')", + "name": "from", + "in": "query" + }, + { + "type": "string", + "description": "End date (e.g. '2021-02-08')", + "name": "to", + "in": "query" + }, + { + "type": "boolean", + "description": "Whether to recompute the summary from raw heartbeat or use cache", + "name": "recompute", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Summary" + } + } + } + } + }, + "/v1/users/{user}/stats/{range}": { + "get": { + "description": "Mimics https://wakatime.com/developers#stats. Requires public data access to be allowed.", + "produces": [ + "application/json" + ], + "tags": [ + "wakatime" + ], + "summary": "Retrieve stats", + "operationId": "get-stats", + "parameters": [ + { + "type": "string", + "description": "User ID to fetch data for (or 'current')", + "name": "user", + "in": "path", + "required": true + }, + { + "enum": [ + "today", + "yesterday", + "week", + "month", + "year", + "7_days", + "last_7_days", + "30_days", + "last_30_days", + "12_months", + "last_12_months", + "any" + ], + "type": "string", + "description": "Range interval identifier", + "name": "range", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/v1.StatsViewModel" + } + } + } + } + } + }, + "definitions": { + "models.Heartbeat": { + "type": "object", + "properties": { + "branch": { + "type": "string" + }, + "category": { + "type": "string" + }, + "editor": { + "description": "ignored because editor might be parsed differently by wakatime", + "type": "string" + }, + "entity": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_write": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "machine": { + "description": "ignored because wakatime api doesn't return machines currently", + "type": "string" + }, + "operating_system": { + "description": "ignored because os might be parsed differently by wakatime", + "type": "string" + }, + "project": { + "type": "string" + }, + "time": { + "type": "number" + }, + "type": { + "type": "string" + } + } + }, + "models.Summary": { + "type": "object", + "properties": { + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "from": { + "type": "string", + "format": "date", + "example": "2006-01-02 15:04:05.000" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SummaryItem" + } + }, + "to": { + "type": "string", + "format": "date", + "example": "2006-01-02 15:04:05.000" + }, + "user_id": { + "type": "string" + } + } + }, + "models.SummaryItem": { + "type": "object", + "properties": { + "key": { + "type": "string" + }, + "total": { + "type": "integer" + } + } + }, + "v1.AllTimeData": { + "type": "object", + "properties": { + "is_up_to_date": { + "description": "true if the stats are up to date; when false, a 202 response code is returned and stats will be refreshed soon\u003e", + "type": "boolean" + }, + "text": { + "description": "total time logged since account created as human readable string\u003e", + "type": "string" + }, + "total_seconds": { + "description": "total number of seconds logged since account created", + "type": "number" + } + } + }, + "v1.AllTimeViewModel": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1.AllTimeData" + } + } + }, + "v1.BadgeData": { + "type": "object", + "properties": { + "color": { + "type": "string" + }, + "label": { + "type": "string" + }, + "message": { + "type": "string" + }, + "schemaVersion": { + "type": "integer" + } + } + }, + "v1.StatsData": { + "type": "object", + "properties": { + "daily_average": { + "type": "number" + }, + "days_including_holidays": { + "type": "integer" + }, + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "end": { + "type": "string" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "start": { + "type": "string" + }, + "total_seconds": { + "type": "number" + }, + "user_id": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, + "v1.StatsViewModel": { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/v1.StatsData" + } + } + }, + "v1.SummariesData": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "dependencies": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "editors": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "grand_total": { + "$ref": "#/definitions/v1.SummariesGrandTotal" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "machines": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "operating_systems": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "projects": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesEntry" + } + }, + "range": { + "$ref": "#/definitions/v1.SummariesRange" + } + } + }, + "v1.SummariesEntry": { + "type": "object", + "properties": { + "digital": { + "type": "string" + }, + "hours": { + "type": "integer" + }, + "minutes": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "percent": { + "type": "number" + }, + "seconds": { + "type": "integer" + }, + "text": { + "type": "string" + }, + "total_seconds": { + "type": "number" + } + } + }, + "v1.SummariesGrandTotal": { + "type": "object", + "properties": { + "digital": { + "type": "string" + }, + "hours": { + "type": "integer" + }, + "minutes": { + "type": "integer" + }, + "text": { + "type": "string" + }, + "total_seconds": { + "type": "number" + } + } + }, + "v1.SummariesRange": { + "type": "object", + "properties": { + "date": { + "type": "string" + }, + "end": { + "type": "string" + }, + "start": { + "type": "string" + }, + "text": { + "type": "string" + }, + "timezone": { + "type": "string" + } + } + }, + "v1.SummariesViewModel": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/v1.SummariesData" + } + }, + "end": { + "type": "string" + }, + "start": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "ApiKeyAuth": { + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/static/docs/swagger.yaml b/static/docs/swagger.yaml new file mode 100644 index 0000000..608a922 --- /dev/null +++ b/static/docs/swagger.yaml @@ -0,0 +1,488 @@ +basePath: /api +definitions: + models.Heartbeat: + properties: + branch: + type: string + category: + type: string + editor: + description: ignored because editor might be parsed differently by wakatime + type: string + entity: + type: string + id: + type: integer + is_write: + type: boolean + language: + type: string + machine: + description: ignored because wakatime api doesn't return machines currently + type: string + operating_system: + description: ignored because os might be parsed differently by wakatime + type: string + project: + type: string + time: + type: number + type: + type: string + type: object + models.Summary: + properties: + editors: + items: + $ref: '#/definitions/models.SummaryItem' + type: array + from: + example: "2006-01-02 15:04:05.000" + format: date + type: string + languages: + items: + $ref: '#/definitions/models.SummaryItem' + type: array + machines: + items: + $ref: '#/definitions/models.SummaryItem' + type: array + operating_systems: + items: + $ref: '#/definitions/models.SummaryItem' + type: array + projects: + items: + $ref: '#/definitions/models.SummaryItem' + type: array + to: + example: "2006-01-02 15:04:05.000" + format: date + type: string + user_id: + type: string + type: object + models.SummaryItem: + properties: + key: + type: string + total: + type: integer + type: object + v1.AllTimeData: + properties: + is_up_to_date: + description: true if the stats are up to date; when false, a 202 response + code is returned and stats will be refreshed soon> + type: boolean + text: + description: total time logged since account created as human readable string> + type: string + total_seconds: + description: total number of seconds logged since account created + type: number + type: object + v1.AllTimeViewModel: + properties: + data: + $ref: '#/definitions/v1.AllTimeData' + type: object + v1.BadgeData: + properties: + color: + type: string + label: + type: string + message: + type: string + schemaVersion: + type: integer + type: object + v1.StatsData: + properties: + daily_average: + type: number + days_including_holidays: + type: integer + editors: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + end: + type: string + languages: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + machines: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + operating_systems: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + projects: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + start: + type: string + total_seconds: + type: number + user_id: + type: string + username: + type: string + type: object + v1.StatsViewModel: + properties: + data: + $ref: '#/definitions/v1.StatsData' + type: object + v1.SummariesData: + properties: + categories: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + dependencies: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + editors: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + grand_total: + $ref: '#/definitions/v1.SummariesGrandTotal' + languages: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + machines: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + operating_systems: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + projects: + items: + $ref: '#/definitions/v1.SummariesEntry' + type: array + range: + $ref: '#/definitions/v1.SummariesRange' + type: object + v1.SummariesEntry: + properties: + digital: + type: string + hours: + type: integer + minutes: + type: integer + name: + type: string + percent: + type: number + seconds: + type: integer + text: + type: string + total_seconds: + type: number + type: object + v1.SummariesGrandTotal: + properties: + digital: + type: string + hours: + type: integer + minutes: + type: integer + text: + type: string + total_seconds: + type: number + type: object + v1.SummariesRange: + properties: + date: + type: string + end: + type: string + start: + type: string + text: + type: string + timezone: + type: string + type: object + v1.SummariesViewModel: + properties: + data: + items: + $ref: '#/definitions/v1.SummariesData' + type: array + end: + type: string + start: + type: string + type: object +info: + contact: + email: ferdinand@muetsch.io + name: Ferdinand Mütsch + url: https://github.com/muety + description: |- + REST API to interact with [Wakapi](https://wakapi.dev) + + ## Authentication + Set header `Authorization` to your API Key encoded as Base64 and prefixed with `Basic` + **Example:** `Basic ODY2NDhkNzQtMTljNS00NTJiLWJhMDEtZmIzZWM3MGQ0YzJmCg==` + license: + name: GPL-3.0 + url: https://github.com/muety/wakapi/blob/master/LICENSE + title: Wakapi API + version: "1.0" +paths: + /compat/shields/v1/{user}/{interval}/{filter}: + get: + description: Retrieve total time for a given entity (e.g. a project) within + a given range (e.g. one week) in a format compatible with [Shields.io](https://shields.io/endpoint). + Requires public data access to be allowed. + operationId: get-badge + parameters: + - description: User ID to fetch data for + in: path + name: user + required: true + type: string + - description: Interval to aggregate data for + enum: + - today + - yesterday + - week + - month + - year + - 7_days + - last_7_days + - 30_days + - last_30_days + - 12_months + - last_12_months + - any + in: path + name: interval + required: true + type: string + - description: Filter to apply (e.g. 'project:wakapi' or 'language:Go') + in: path + name: filter + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.BadgeData' + summary: Get badge data + tags: + - badges + /compat/wakatime/v1/users/{user}/all_time_since_today: + get: + description: Mimics https://wakatime.com/developers#all_time_since_today + operationId: get-all-time + parameters: + - description: User ID to fetch data for (or 'current') + in: path + name: user + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.AllTimeViewModel' + security: + - ApiKeyAuth: [] + summary: Retrieve summary for all time + tags: + - wakatime + /compat/wakatime/v1/users/{user}/summaries: + get: + description: Mimics https://wakatime.com/developers#summaries. + operationId: get-wakatime-summaries + parameters: + - description: User ID to fetch data for (or 'current') + in: path + name: user + required: true + type: string + - description: Range interval identifier + enum: + - today + - yesterday + - week + - month + - year + - 7_days + - last_7_days + - 30_days + - last_30_days + - 12_months + - last_12_months + - any + in: query + name: range + type: string + - description: Start date (e.g. '2021-02-07') + in: query + name: start + type: string + - description: End date (e.g. '2021-02-08') + in: query + name: end + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.SummariesViewModel' + security: + - ApiKeyAuth: [] + summary: Retrieve WakaTime-compatible summaries + tags: + - wakatime + /health: + get: + operationId: get-health + produces: + - text/plain + responses: + "200": + description: OK + schema: + type: string + summary: Check the application's health status + tags: + - misc + /heartbeat: + post: + consumes: + - application/json + operationId: post-heartbeat + parameters: + - description: A heartbeat + in: body + name: heartbeat + required: true + schema: + $ref: '#/definitions/models.Heartbeat' + responses: + "201": + description: "" + security: + - ApiKeyAuth: [] + summary: Push a new heartbeat + tags: + - heartbeat + /summary: + get: + operationId: get-summary + parameters: + - description: Interval identifier + enum: + - today + - yesterday + - week + - month + - year + - 7_days + - last_7_days + - 30_days + - last_30_days + - 12_months + - last_12_months + - any + in: query + name: interval + type: string + - description: Start date (e.g. '2021-02-07') + in: query + name: from + type: string + - description: End date (e.g. '2021-02-08') + in: query + name: to + type: string + - description: Whether to recompute the summary from raw heartbeat or use cache + in: query + name: recompute + type: boolean + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Summary' + security: + - ApiKeyAuth: [] + summary: Retrieve a summary + tags: + - summary + /v1/users/{user}/stats/{range}: + get: + description: Mimics https://wakatime.com/developers#stats. Requires public data + access to be allowed. + operationId: get-stats + parameters: + - description: User ID to fetch data for (or 'current') + in: path + name: user + required: true + type: string + - description: Range interval identifier + enum: + - today + - yesterday + - week + - month + - year + - 7_days + - last_7_days + - 30_days + - last_30_days + - 12_months + - last_12_months + - any + in: path + name: range + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/v1.StatsViewModel' + summary: Retrieve stats + tags: + - wakatime +securityDefinitions: + ApiKeyAuth: + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/static/swagger-ui/favicon-16x16.png b/static/swagger-ui/favicon-16x16.png new file mode 100644 index 0000000..8b194e6 Binary files /dev/null and b/static/swagger-ui/favicon-16x16.png differ diff --git a/static/swagger-ui/favicon-32x32.png b/static/swagger-ui/favicon-32x32.png new file mode 100644 index 0000000..249737f Binary files /dev/null and b/static/swagger-ui/favicon-32x32.png differ diff --git a/static/swagger-ui/index.html b/static/swagger-ui/index.html new file mode 100644 index 0000000..5a24755 --- /dev/null +++ b/static/swagger-ui/index.html @@ -0,0 +1,60 @@ + + + +
+ +>>u&y;if(_!==f>>>u&y)break;_&&(l+=(1<o&&(c=c.removeBefore(r,u,i-l)),c&&f
a&&(a=c.size),i(u)||(c=c.map((function(e){return he(e)}))),r.push(c)}return a>e.size&&(e=e.setSize(a)),mt(e,t,r)}function qt(e){return es)return q();var e=o.next();return r||t===M?e:U(t,u-1,t===N?void 0:e.value[1],e)}))},c}function on(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterate(r,o);var i=0;return e.__iterate((function(e,o,s){return t.call(n,e,o,s)&&++i&&r(e,o,a)})),i},r.__iteratorUncached=function(r,o){var a=this;if(o)return this.cacheResult().__iterator(r,o);var i=e.__iterator(R,o),s=!0;return new F((function(){if(!s)return q();var e=i.next();if(e.done)return e;var o=e.value,u=o[0],c=o[1];return t.call(n,c,u,a)?r===R?e:U(r,u,c,e):(s=!1,q())}))},r}function an(e,t,n,r){var o=bn(e);return o.__iterateUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterate(o,a);var s=!0,u=0;return e.__iterate((function(e,a,c){if(!s||!(s=t.call(n,e,a,c)))return u++,o(e,r?a:u-1,i)})),u},o.__iteratorUncached=function(o,a){var i=this;if(a)return this.cacheResult().__iterator(o,a);var s=e.__iterator(R,a),u=!0,c=0;return new F((function(){var e,a,l;do{if((e=s.next()).done)return r||o===M?e:U(o,c++,o===N?void 0:e.value[1],e);var p=e.value;a=p[0],l=p[1],u&&(u=t.call(n,l,a,i))}while(u);return o===R?e:U(o,a,l,e)}))},o}function sn(e,t){var n=s(e),o=[e].concat(t).map((function(e){return i(e)?n&&(e=r(e)):e=n?se(e):ue(Array.isArray(e)?e:[e]),e})).filter((function(e){return 0!==e.size}));if(0===o.length)return e;if(1===o.length){var a=o[0];if(a===e||n&&s(a)||u(e)&&u(a))return a}var c=new te(o);return n?c=c.toKeyedSeq():u(e)||(c=c.toSetSeq()),(c=c.flatten(!0)).size=o.reduce((function(e,t){if(void 0!==e){var n=t.size;if(void 0!==n)return e+n}}),0),c}function un(e,t,n){var r=bn(e);return r.__iterateUncached=function(r,o){var a=0,s=!1;function u(e,c){var l=this;e.__iterate((function(e,o){return(!t||cs&&(n=s-u),a=n;a>=0;a--){for(var p=!0,f=0;fo&&(r=o):r=o;var a=t.length;if(a%2!=0)throw new TypeError("Invalid hex string");r>a/2&&(r=a/2);for(var i=0;io)&&(n=o),e.length>0&&(n<0||t<0)||t>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var a=!1;;)switch(r){case"hex":return b(this,e,t,n);case"utf8":case"utf-8":return _(this,e,t,n);case"ascii":return w(this,e,t,n);case"latin1":case"binary":return x(this,e,t,n);case"base64":return E(this,e,t,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return S(this,e,t,n);default:if(a)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),a=!0}},u.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var k=4096;function O(e,t,n){var r="";n=Math.min(e.length,n);for(var o=t;o