Add openAPI documentation (#68)

This commit is contained in:
itsscb 2023-10-03 23:36:46 +02:00 committed by GitHub
parent c6178e9cd3
commit 7d2e0a8819
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 785 additions and 35 deletions

View File

@ -93,7 +93,7 @@ func (server *Server) getAccount(ctx *gin.Context) {
ctx.JSON(http.StatusUnauthorized, errorResponse(err)) ctx.JSON(http.StatusUnauthorized, errorResponse(err))
return return
} }
//v4.public.eyJlbWFpbCI6ImFAYi5jZSIsImV4cCI6IjIwMjMtMTAtMDRUMDA6NDY6NDYrMDI6MDAiLCJpYXQiOiIyMDIzLTEwLTA0VDAwOjMxOjQ2KzAyOjAwIiwiaWQiOiJhZWU0MGE0NC0yMGIwLTQ3YmYtYmI5Yy04M2Y3ZmI5ZTU0MTEiLCJuYmYiOiIyMDIzLTEwLTA0VDAwOjMxOjQ2KzAyOjAwIn2ZcKWD7cA7y_aHnfUtYOnaR6iCYFaFOY5BbUQkyTpu-ZK9xyaHO1_j9GqAZ6GbntmtWktojWRBkLpoWEntMuQM
ctx.JSON(http.StatusOK, account) ctx.JSON(http.StatusOK, account)
} }
@ -172,7 +172,7 @@ func (server *Server) updateAccountPrivacy(ctx *gin.Context) {
account, err = server.store.UpdateAccountPrivacyTx(ctx, db.UpdateAccountPrivacyTxParams{ account, err = server.store.UpdateAccountPrivacyTx(ctx, db.UpdateAccountPrivacyTxParams{
ID: req.ID, ID: req.ID,
Changer: authPayload.Email, Changer: authPayload.Email,
PrivacyAccepted: *req.PrivacyAccepted, PrivacyAccepted: req.PrivacyAccepted,
}) })
if err != nil { if err != nil {
ctx.JSON(http.StatusInternalServerError, errorResponse(err)) ctx.JSON(http.StatusInternalServerError, errorResponse(err))

View File

@ -163,7 +163,7 @@ func TestCreateAccountAPI(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store) tc.buildStubs(store)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -299,7 +299,7 @@ func TestGetAccountAPI(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store) tc.buildStubs(store)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -447,7 +447,7 @@ func TestUpdateAccountTxAPI(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store) tc.buildStubs(store)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -601,7 +601,7 @@ func TestListAccountsAPI(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store) tc.buildStubs(store)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
@ -643,9 +643,10 @@ func TestUpdateAccountPrivacyTxAPI(t *testing.T) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute) addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute)
}, },
buildStubs: func(store *mockdb.MockStore) { buildStubs: func(store *mockdb.MockStore) {
trueBool := true
arg := db.UpdateAccountPrivacyTxParams{ arg := db.UpdateAccountPrivacyTxParams{
ID: account.ID, ID: account.ID,
PrivacyAccepted: true, PrivacyAccepted: &trueBool,
Changer: account.Email, Changer: account.Email,
} }
@ -689,9 +690,11 @@ func TestUpdateAccountPrivacyTxAPI(t *testing.T) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute) addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute)
}, },
buildStubs: func(store *mockdb.MockStore) { buildStubs: func(store *mockdb.MockStore) {
trueBool := true
arg := db.UpdateAccountPrivacyTxParams{ arg := db.UpdateAccountPrivacyTxParams{
ID: account.ID, ID: account.ID,
PrivacyAccepted: true, PrivacyAccepted: &trueBool,
Changer: account.Email, Changer: account.Email,
} }
@ -735,9 +738,11 @@ func TestUpdateAccountPrivacyTxAPI(t *testing.T) {
addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute) addAuthorization(t, request, tokenMaker, authorizationTypeBearer, account.Email, time.Minute)
}, },
buildStubs: func(store *mockdb.MockStore) { buildStubs: func(store *mockdb.MockStore) {
falseBool := false
arg := db.UpdateAccountPrivacyTxParams{ arg := db.UpdateAccountPrivacyTxParams{
ID: account.ID, ID: account.ID,
PrivacyAccepted: false, PrivacyAccepted: &falseBool,
Changer: account.Email, Changer: account.Email,
} }
@ -806,7 +811,7 @@ func TestUpdateAccountPrivacyTxAPI(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
tc.buildStubs(store) tc.buildStubs(store)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()

View File

@ -91,7 +91,7 @@ func TestAuthMiddleware(t *testing.T) {
store := mockdb.NewMockStore(ctrl) store := mockdb.NewMockStore(ctrl)
server, err := NewServer(config, store) server, err := NewServer(config, store, nil)
require.NoError(t, err) require.NoError(t, err)
authPath := "/auth" authPath := "/auth"
server.router.GET( server.router.GET(

View File

@ -3,6 +3,7 @@ package api
import ( import (
"fmt" "fmt"
"log/slog" "log/slog"
"net/http"
"os" "os"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -17,10 +18,11 @@ type Server struct {
router *gin.Engine router *gin.Engine
config util.Config config util.Config
tokenMaker token.Maker tokenMaker token.Maker
swaggerFiles http.FileSystem
} }
// NewServer creates a new HTTP server and sets up routing // NewServer creates a new HTTP server and sets up routing
func NewServer(config util.Config, store db.Store) (*Server, error) { func NewServer(config util.Config, store db.Store, swaggerFS http.FileSystem) (*Server, error) {
tokenMaker, err := token.NewPasetoMaker(config.TokenPrivateKeyHex) tokenMaker, err := token.NewPasetoMaker(config.TokenPrivateKeyHex)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create token maker: %w", err) return nil, fmt.Errorf("cannot create token maker: %w", err)
@ -30,11 +32,20 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
store: store, store: store,
config: config, config: config,
tokenMaker: tokenMaker, tokenMaker: tokenMaker,
swaggerFiles: swaggerFS,
} }
router := gin.New()
router.Use(gin.Recovery())
logLevel := slog.LevelError logLevel := slog.LevelError
if config.Environment == "development" { if config.Environment == "development" {
logLevel = slog.LevelDebug logLevel = slog.LevelDebug
// router.Static("/swagger/", "./doc/swagger/")
}
if swaggerFS != nil {
router.StaticFS("/swagger/", swaggerFS)
} }
opts := slog.HandlerOptions{ opts := slog.HandlerOptions{
@ -48,10 +59,6 @@ func NewServer(config util.Config, store db.Store) (*Server, error) {
slog.SetDefault(logger) slog.SetDefault(logger)
router := gin.New()
router.Use(gin.Recovery())
router.Use(Logger()) router.Use(Logger())
router.POST("/login", server.loginAccount) router.POST("/login", server.loginAccount)

View File

@ -9,7 +9,7 @@ import (
type UpdateAccountPrivacyTxParams struct { type UpdateAccountPrivacyTxParams struct {
ID int64 `json:"ID"` ID int64 `json:"ID"`
Changer string `json:"changer"` Changer string `json:"changer"`
PrivacyAccepted bool `json:"privacy_accepted"` PrivacyAccepted *bool `json:"privacy_accepted"`
} }
type UpdateAccountPrivacyTxResult struct { type UpdateAccountPrivacyTxResult struct {
@ -17,19 +17,16 @@ type UpdateAccountPrivacyTxResult struct {
} }
func (store *SQLStore) UpdateAccountPrivacyTx(ctx context.Context, arg UpdateAccountPrivacyTxParams) (Account, error) { func (store *SQLStore) UpdateAccountPrivacyTx(ctx context.Context, arg UpdateAccountPrivacyTxParams) (Account, error) {
var result UpdateAccountPrivacyTxResult var date sql.NullTime
param := UpdateAccountPrivacyParams{ var account Account
ID: arg.ID,
Changer: arg.Changer,
}
if arg.PrivacyAccepted { if *arg.PrivacyAccepted {
param.PrivacyAcceptedDate = sql.NullTime{ date = sql.NullTime{
Valid: true, Valid: true,
Time: time.Now(), Time: time.Now(),
} }
} else { } else {
param.PrivacyAcceptedDate = sql.NullTime{ date = sql.NullTime{
Valid: true, Valid: true,
Time: time.Time{}, Time: time.Time{},
} }
@ -38,9 +35,17 @@ func (store *SQLStore) UpdateAccountPrivacyTx(ctx context.Context, arg UpdateAcc
err := store.execTx(ctx, func(q *Queries) error { err := store.execTx(ctx, func(q *Queries) error {
var err error var err error
result.Account, err = q.UpdateAccountPrivacy(ctx, param) account, err = q.UpdateAccountPrivacy(ctx, UpdateAccountPrivacyParams{
PrivacyAccepted: sql.NullBool{
Bool: *arg.PrivacyAccepted,
Valid: true,
},
PrivacyAcceptedDate: date,
ID: arg.ID,
Changer: arg.Changer,
})
return err return err
}) })
return result.Account, err return account, err
} }

View File

@ -0,0 +1,561 @@
{
"openapi": "3.1.0",
"info": {
"title": "df API",
"version": "1.0",
"contact": {
"name": "itsscb",
"url": "https://github.com/itsscb/df",
"email": "dev@itsscb.de"
}
},
"tags": [
{
"name": "df"
}
],
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"paths": {
"/accounts/{id}": {
"get": {
"security": [
{
"bearerAuth": []
}
],
"summary": "Get account",
"description": "Use this API to get a new account",
"operationId": "df_GetAccount",
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/GetAccountResponse"
}
}
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"$ref": "#/definitions/GetAccountRequest"
}
}
],
"tags": [
"df"
]
}
},
"/accounts": {
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/definitions/CreateAccountRequest"
}
}
}
},
"summary": "Create new account",
"description": "Use this API to create a new account",
"operationId": "df_CreateAccount",
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/CreateAccountResponse"
}
}
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/CreateAccountRequest"
}
}
],
"tags": [
"df"
]
},
"put": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/definitions/UpdateAccountRequest"
}
}
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Update account",
"description": "Use this API to update account",
"operationId": "df_UpdateAccount",
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/definitions/UpdateAccountResponse"
}
}
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/UpdateAccountRequest"
}
}
],
"tags": [
"df"
]
}
},
"/accounts/privacy": {
"put": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/definitions/UpdateAccountPrivacyRequest"
}
}
}
},
"security": [
{
"bearerAuth": []
}
],
"summary": "Update account privacy settings",
"description": "Use this API to update account privacy settings",
"operationId": "df_UpdateAccountPrivacy",
"responses": {
"200": {
"description": "A successful response.",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Account"
}
}
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/UpdateAccountPrivacyRequest"
}
}
],
"tags": [
"df"
]
}
},
"/login": {
"post": {
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"$ref": "#/definitions/LoginAccountRequest"
}
}
}
},
"summary": "Login account",
"description": "Use this API to login account and get access token \u0026 refresh token",
"operationId": "df_LoginAccount",
"responses": {
"200": {
"description": "A successful response.",
"schema": {
"$ref": "#/definitions/LoginAccountResponse"
}
},
"default": {
"description": "An unexpected error response.",
"schema": {
"$ref": "#/definitions/rpcStatus"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"schema": {
"$ref": "#/definitions/LoginAccountRequest"
}
}
],
"tags": [
"df"
]
}
}
},
"security": {
"bearerAuth": []
},
"components": {
"securitySchemes": {
"bearerAuth": {
"type": "http",
"scheme": "bearer",
"bearerFormat": "paseto",
"in": "header"
}
},
"schemas": {
"Account": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"email": {
"type": "string"
},
"passwordhash": {
"type": "string",
"format": "password"
},
"permission_level": {
"type": "integer"
},
"privacy_accepted": {
"type": "object",
"properties": {
"Bool": {
"type": "boolean"
},
"Valid": {
"type": "boolean"
}
}
},
"privacy_accepted_date": {
"type": "object",
"properties": {
"Time": {
"type":"string",
"format": "date-time"
},
"Valid": {
"type": "boolean"
}
}
},
"phone": {
"type": "object",
"properties": {
"String": {
"type": "string"
},
"Valid": {
"type": "boolean"
}
}
},
"firstname": {
"type": "string"
},
"lastname": {
"type": "string"
},
"birthday": {
"type":"string",
"format": "date-time"
},
"street": {
"type": "string"
},
"city": {
"type": "string"
},
"zip": {
"type": "string"
},
"country": {
"type": "string"
},
"creator": {
"type": "string"
},
"created": {
"type":"string",
"format": "date-time"
},
"changer": {
"type": "string"
},
"changed": {
"type":"string",
"format": "date-time"
}
}
}
}
},
"definitions": {
"CreateAccountRequest": {
"required": [
"firstname",
"lastname",
"city",
"zip",
"street",
"country",
"email",
"password",
"birthday"
],
"type": "object",
"properties": {
"firstname": {
"type": "string"
},
"lastname": {
"type": "string"
},
"city": {
"type": "string"
},
"zip": {
"type": "string"
},
"country": {
"type": "string"
},
"street": {
"type": "string"
},
"birthday": {
"type":"string",
"format": "date-time"
},
"phone": {
"type": "string"
},
"email": {
"type": "string"
},
"password": {
"type": "string",
"format": "password"
}
}
},
"CreateAccountResponse": {
"type": "object",
"properties": {
"account": {
"$ref": "#/components/schemas/Account"
}
}
},
"GetAccountRequest": {
"required": [
"id"
],
"type": "integer",
"format": "int64"
},
"GetAccountResponse": {
"type": "object",
"properties": {
"account": {
"$ref": "#/components/schemas/Account"
}
}
},
"LoginAccountRequest": {
"required": [
"email",
"password"
],
"type": "object",
"properties": {
"email": {
"type": "string",
"format": "email"
},
"password": {
"type": "string",
"format": "password"
}
}
},
"UpdateAccountPrivacyRequest": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"privacy_accepted": {
"type": "boolean"
}
}
},
"UpdateAccountPrivacyResponse": {
"type": "object",
"properties": {
"account": {
"$ref": "#/components/schemas/Account"
}
}
},
"UpdateAccountRequest": {
"required": [
"id"
],
"type": "object",
"properties": {
"id": {
"type": "integer",
"format": "int64"
},
"new_password": {
"type": "string",
"format": "password"
},
"email": {
"type": "string",
"format": "email"
},
"firstname": {
"type": "string"
},
"lastname": {
"type": "string"
},
"phone": {
"type": "string"
},
"city": {
"type": "string"
},
"zip": {
"type": "string"
},
"street": {
"type": "string"
},
"country": {
"type": "string"
},
"birthday": {
"type": "string",
"format": "date-time"
}
}
},
"UpdateAccountResponse": {
"type": "object",
"properties": {
"account": {
"$ref": "#/components/schemas/Account"
}
}
},
"protobufAny": {
"type": "object",
"properties": {
"@type": {
"type": "string"
}
},
"additionalProperties": {}
},
"rpcStatus": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"format": "int32"
},
"message": {
"type": "string"
},
"details": {
"type": "array",
"items": {
"$ref": "#/definitions/protobufAny"
}
}
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

16
bff/doc/swagger/index.css Normal file
View File

@ -0,0 +1,16 @@
html {
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
background: #fafafa;
}

View File

@ -0,0 +1,19 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
<link rel="stylesheet" type="text/css" href="index.css" />
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
</head>
<body>
<div id="swagger-ui"></div>
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
<script src="./swagger-initializer.js" charset="UTF-8"> </script>
</body>
</html>

View File

@ -0,0 +1,79 @@
<!doctype html>
<html lang="en-US">
<head>
<title>Swagger UI: OAuth2 Redirect</title>
</head>
<body>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1).replace('?', '&');
} else {
qp = location.search.substring(1);
}
arr = qp.split("&");
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value);
}
) : {};
isValid = qp.state === sentState;
if ((
oauth2.auth.schema.get("flow") === "accessCode" ||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
oauth2.auth.schema.get("flow") === "authorization_code"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server. The passed state wasn't returned from auth server."
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg;
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server."
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
if (document.readyState !== 'loading') {
run();
} else {
document.addEventListener('DOMContentLoaded', function () {
run();
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
window.onload = function() {
//<editor-fold desc="Changeable Configuration Block">
// the following lines will be replaced by docker/configurator, when it runs in a docker-container
window.ui = SwaggerUIBundle({
url: "df.swagger.json",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
//</editor-fold>
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,10 @@ package main
import ( import (
"database/sql" "database/sql"
"embed"
"io/fs"
"log" "log"
"net/http"
"github.com/itsscb/df/bff/api" "github.com/itsscb/df/bff/api"
db "github.com/itsscb/df/bff/db/sqlc" db "github.com/itsscb/df/bff/db/sqlc"
@ -10,7 +13,11 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
//go:embed doc/swagger
var swaggerFiles embed.FS
func main() { func main() {
var swaggerFS http.FileSystem
config, err := util.LoadConfig(".") config, err := util.LoadConfig(".")
if err != nil { if err != nil {
log.Fatal("cannot load config:", err) log.Fatal("cannot load config:", err)
@ -20,8 +27,16 @@ func main() {
log.Fatalf("could not connect to DB: %s", err) log.Fatalf("could not connect to DB: %s", err)
} }
if config.Environment == "development" {
subDir, err := fs.Sub(swaggerFiles, "doc/swagger")
if err != nil {
log.Fatalf("could not import swagger files")
}
swaggerFS = http.FS(subDir)
}
store := db.NewStore(conn) store := db.NewStore(conn)
server, err := api.NewServer(config, store) server, err := api.NewServer(config, store, swaggerFS)
if err != nil { if err != nil {
log.Fatalf("could not start server: %s", err) log.Fatalf("could not start server: %s", err)
} }