Browse Source
master
ktm-booking-bot/ktm-booking-bot/pipeline/head Something is wrong with the build of this commit
master
ktm-booking-bot/ktm-booking-bot/pipeline/head Something is wrong with the build of this commit
commit
7cf10b07d4
44 changed files with 4569 additions and 0 deletions
@ -0,0 +1,2 @@ |
|||
_docker_mnt |
|||
.git |
@ -0,0 +1,16 @@ |
|||
ENVIRONMENT= |
|||
DB_HOST= |
|||
DB_PORT= |
|||
DB_USER= |
|||
DB_PASS= |
|||
DB_NAME= |
|||
DB_SSL= |
|||
TEST_DB_HOST= |
|||
TEST_DB_PORT= |
|||
TEST_DB_USER= |
|||
TEST_DB_PASS= |
|||
TEST_DB_NAME= |
|||
TEST_DB_SSL= |
|||
COOKIE_STRING= |
|||
ALLOW_REGISTRATION= |
|||
LOGGER_WEBHOOK_URL= |
@ -0,0 +1,74 @@ |
|||
# ---> Go |
|||
# Binaries for programs and plugins |
|||
*.exe |
|||
*.exe~ |
|||
*.dll |
|||
*.so |
|||
*.dylib |
|||
|
|||
# Test binary, built with `go test -c` |
|||
*.test |
|||
|
|||
# Output of the go coverage tool, specifically when used with LiteIDE |
|||
*.out |
|||
|
|||
# Dependency directories (remove the comment below to include it) |
|||
# vendor/ |
|||
|
|||
# ---> Vim |
|||
# Swap |
|||
[._]*.s[a-v][a-z] |
|||
!*.svg # comment out if you don't need vector files |
|||
[._]*.sw[a-p] |
|||
[._]s[a-rt-v][a-z] |
|||
[._]ss[a-gi-z] |
|||
[._]sw[a-p] |
|||
|
|||
# Session |
|||
Session.vim |
|||
Sessionx.vim |
|||
|
|||
# Temporary |
|||
.netrwhist |
|||
*~ |
|||
# Auto-generated tag files |
|||
tags |
|||
# Persistent undo |
|||
[._]*.un~ |
|||
|
|||
# ---> VisualStudioCode |
|||
.vscode/* |
|||
!.vscode/settings.json |
|||
!.vscode/tasks.json |
|||
!.vscode/launch.json |
|||
!.vscode/extensions.json |
|||
*.code-workspace |
|||
.vscode |
|||
|
|||
# Local History for Visual Studio Code |
|||
.history/ |
|||
|
|||
# ---> Linux |
|||
*~ |
|||
|
|||
# temporary files which can be created if a process still has a handle open of a deleted file |
|||
.fuse_hidden* |
|||
|
|||
# KDE directory preferences |
|||
.directory |
|||
|
|||
# Linux trash folder which might appear on any partition or disk |
|||
.Trash-* |
|||
|
|||
# .nfs files are created when an open file is removed but is still being accessed |
|||
.nfs* |
|||
|
|||
# Custom Ignores |
|||
_postgres_data |
|||
backend/server |
|||
.env |
|||
*.burp |
|||
*.burp.* |
|||
chrome-profile |
|||
screenshots |
|||
debug-screenshots |
@ -0,0 +1,50 @@ |
|||
pipeline { |
|||
agent any |
|||
|
|||
environment { |
|||
ATHENA_DEPLOYMENT_SSH_KEY = credentials("athena_ssh_key") |
|||
ENVIRONMENT = credentials("ENVIRONMENT") |
|||
DB_HOST = credentials("DB_HOST") |
|||
DB_PORT = credentials("DB_PORT") |
|||
DB_USER = credentials("DB_USER") |
|||
DB_PASS = credentials("DB_PASS") |
|||
DB_NAME = credentials("DB_NAME") |
|||
DB_SSL = credentials("DB_SSL") |
|||
COOKIE_STRING = credentials("COOKIE_STRING") |
|||
ALLOW_REGISTRATION = credentials("ALLOW_REGISTRATION") |
|||
LOGGER_WEBHOOK_URL = credentials("LOGGER_WEBHOOK_URL") |
|||
} |
|||
|
|||
stages { |
|||
stage('Build') { |
|||
steps { |
|||
echo 'Creating environment variables (.env)' |
|||
sh 'echo ENVIRONMENT=$ENVIRONMENT >> .env' |
|||
sh 'echo DB_HOST=$DB_HOST >> .env' |
|||
sh 'echo DB_PORT=$DB_PORT >> .env' |
|||
sh 'echo DB_USER=$DB_USER >> .env' |
|||
sh 'echo DB_PASS=$DB_PASS >> .env' |
|||
sh 'echo DB_NAME=$DB_NAME >> .env' |
|||
sh 'echo DB_SSL=$DB_SSL >> .env' |
|||
sh 'echo COOKIE_STRING=$COOKIE_STRING >> .env' |
|||
sh 'echo ALLOW_REGISTRATION=$ALLOW_REGISTRATION >> .env' |
|||
sh 'echo LOGGER_WEBHOOK_URL=$LOGGER_WEBHOOK_URL >> .env' |
|||
echo 'Clearing Git directory' |
|||
sh 'rm -rf ./.git' |
|||
} |
|||
} |
|||
stage('Test') { |
|||
steps { |
|||
echo 'Testing..' |
|||
} |
|||
} |
|||
stage('Deploy') { |
|||
steps { |
|||
echo 'Creating SSH Key...' |
|||
sh 'apt update' |
|||
sh 'apt install -y rsync' |
|||
sh 'bash scripts/deploy.sh' |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,49 @@ |
|||
# KTM Booking Bot |
|||
|
|||
## About |
|||
The project's goal is build bot to book KTM train ticket (Restricted to 1 booking per day) |
|||
|
|||
## Deployment |
|||
The project uses the following environment variables: |
|||
- ENVIRONMENT |
|||
- DB_HOST |
|||
- DB_PORT |
|||
- DB_USER |
|||
- DB_PASS |
|||
- DB_NAME |
|||
- DB_SSL |
|||
- COOKIE_STRING |
|||
- ALLOW_REGISTRATION |
|||
- LOGGER_WEBHOOK_URL |
|||
|
|||
The following environment variables are used for testing |
|||
- TEST_DB_HOST |
|||
- TEST_DB_PORT |
|||
- TEST_DB_USER |
|||
- TEST_DB_PASS |
|||
- TEST_DB_NAME |
|||
- TEST_DB_SSL |
|||
|
|||
The application may be deployed using the deployment script at `scripts/deploy.sh`. |
|||
|
|||
## Build |
|||
The build script is available at `scripts/build.sh`. |
|||
`.env` is required to be populated. The example `.env` is available at `.env.example`. |
|||
|
|||
## Run |
|||
The application may be staged and run using docker-compose. The following command may be used: |
|||
``` |
|||
docker-compose up --build -d |
|||
``` |
|||
|
|||
## Development |
|||
The development postgres server can be launched using the script at `scripts/dev_postgres_docker.sh`. |
|||
|
|||
## Testing |
|||
Testing is enabled through go test. The following command may be used to conduct testing: |
|||
``` |
|||
go test -cover ./... |
|||
``` |
|||
|
|||
## Disclaimer |
|||
This application is built for educational purpose only. Users who use this application is assumed to be knowledgable and aware of what the application does technically. The author does not bear any liability due to the execution of this application. |
@ -0,0 +1 @@ |
|||
All docker mounts should be here so that they can be ignored |
@ -0,0 +1,31 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
type HealthStatus struct { |
|||
Status string `json:"status"` |
|||
} |
|||
|
|||
func (*HealthStatus) Render(w http.ResponseWriter, r *http.Request) error { |
|||
// Pre-processing before a response is marshalled and sent across the wire
|
|||
return nil |
|||
} |
|||
|
|||
// Health Check``
|
|||
// @Summary Responds to health check
|
|||
// @Description Description
|
|||
// @Tags Base
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Success 200 {object} string
|
|||
// @Failure 404 {object} string
|
|||
// @Router /health [get]
|
|||
func healthHandler(w http.ResponseWriter, r *http.Request) { |
|||
var okRender HealthStatus |
|||
okRender.Status = "ok" |
|||
render.Render(w, r, &okRender) |
|||
} |
@ -0,0 +1,39 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/http/httptest" |
|||
"testing" |
|||
) |
|||
|
|||
func TestHealthStatus(t *testing.T) { |
|||
handler := http.HandlerFunc(healthHandler) |
|||
req, err := http.NewRequest("GET", "/health", nil) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request: %v", err) |
|||
} |
|||
|
|||
rr := httptest.NewRecorder() |
|||
handler.ServeHTTP(rr, req) |
|||
|
|||
if rr.Result().StatusCode != 200 { |
|||
t.Errorf("Health did not respond with status-code 200") |
|||
} |
|||
|
|||
responseBytes, err := ioutil.ReadAll(rr.Result().Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading response body: %v", err) |
|||
} |
|||
|
|||
var results map[string]any |
|||
err = json.Unmarshal(responseBytes, &results) |
|||
if err != nil { |
|||
t.Errorf("Error decoding response body: %v", err) |
|||
} |
|||
|
|||
if results["status"] != "ok" { |
|||
t.Errorf("Health status is not ok") |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
package main |
|||
|
|||
import ( |
|||
"log" |
|||
"net/http" |
|||
"os" |
|||
|
|||
_ "git.samuelpua.com/telboon/ktm-train-bot/backend/docs" |
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/ktmtrainbot" |
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/go-chi/chi" |
|||
"github.com/joho/godotenv" |
|||
httpSwagger "github.com/swaggo/http-swagger" |
|||
) |
|||
|
|||
// Root Handler - Test
|
|||
// @Summary This is test
|
|||
// @Description Description
|
|||
// @Tags Base
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Success 200 {object} string
|
|||
// @Failure 404 {object} string
|
|||
// @Router / [get]
|
|||
func rootHandler(w http.ResponseWriter, r *http.Request) { |
|||
w.Write([]byte("hello world")) |
|||
} |
|||
|
|||
// @title KTM Train Booking Bot
|
|||
// @version 1.0
|
|||
// @description API for frontend - built on Go-chi
|
|||
// @BasePath /
|
|||
// @contact.name Samuel Pua
|
|||
// @contact.url https://git.samuelpua.com/telboon
|
|||
func main() { |
|||
err := godotenv.Load() |
|||
if err != nil { |
|||
log.Fatal("Error loading .env file") |
|||
} |
|||
environment := os.Getenv("ENVIRONMENT") |
|||
|
|||
db := common.InitDB() |
|||
db.AutoMigrate(&user.User{}) |
|||
db.AutoMigrate(&user.Profile{}) |
|||
db.AutoMigrate(&user.Session{}) |
|||
db.AutoMigrate(&ktmtrainbot.Booking{}) |
|||
|
|||
r := chi.NewRouter() |
|||
|
|||
if environment == "dev" { |
|||
r.Mount("/docs", httpSwagger.WrapHandler) |
|||
} |
|||
|
|||
r.Mount("/api/v1/user", user.UserRoutes(db)) |
|||
r.Mount("/api/v1/ktmtrainbot", ktmtrainbot.KTMTrainBotRoutes(db)) |
|||
|
|||
r.Get("/", rootHandler) |
|||
r.Get("/health", healthHandler) |
|||
|
|||
server_str := ":8000" |
|||
log.Printf("Starting server at %s\n", server_str) |
|||
http.ListenAndServe(server_str, r) |
|||
} |
@ -0,0 +1,609 @@ |
|||
// Package docs GENERATED BY SWAG; DO NOT EDIT
|
|||
// This file was generated by swaggo/swag
|
|||
package docs |
|||
|
|||
import "github.com/swaggo/swag" |
|||
|
|||
const docTemplate = `{ |
|||
"schemes": {{ marshal .Schemes }}, |
|||
"swagger": "2.0", |
|||
"info": { |
|||
"description": "{{escape .Description}}", |
|||
"title": "{{.Title}}", |
|||
"contact": { |
|||
"name": "Samuel Pua", |
|||
"url": "https://git.samuelpua.com/telboon" |
|||
}, |
|||
"version": "{{.Version}}" |
|||
}, |
|||
"host": "{{.Host}}", |
|||
"basePath": "{{.BasePath}}", |
|||
"paths": { |
|||
"/": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Base" |
|||
], |
|||
"summary": "This is test", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Not Found", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/booking": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Get All Booking", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Create New Booking", |
|||
"parameters": [ |
|||
{ |
|||
"description": "Booking Create Request", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingCreateRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/booking/{bookingID}": { |
|||
"delete": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Delete booking", |
|||
"parameters": [ |
|||
{ |
|||
"type": "string", |
|||
"description": "Booking ID", |
|||
"name": "bookingID", |
|||
"in": "path", |
|||
"required": true |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/current-time": { |
|||
"get": { |
|||
"description": "Description", |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Info" |
|||
], |
|||
"summary": "Get current server time", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.ServerTimeResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/login": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user login", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User Login info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserLoginRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/logout": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user logout", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.TextResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/me": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "Returns current logged in user", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/profile": { |
|||
"put": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For setting current user profile", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User registration info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.ProfileRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/register": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user registration", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User registration info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserRegisterRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/health": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Base" |
|||
], |
|||
"summary": "Responds to health check", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Not Found", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"definitions": { |
|||
"common.ErrResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"code": { |
|||
"description": "application-specific error code", |
|||
"type": "integer" |
|||
}, |
|||
"error": { |
|||
"description": "application-level error message, for debugging", |
|||
"type": "string" |
|||
}, |
|||
"status": { |
|||
"description": "user-level status message", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"common.TextResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"status": { |
|||
"description": "user-level status message", |
|||
"type": "string" |
|||
}, |
|||
"text": { |
|||
"description": "application-specific error code", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.BookingCreateRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"name", |
|||
"passport", |
|||
"passportExpiry", |
|||
"travelDate" |
|||
], |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string" |
|||
}, |
|||
"gender": { |
|||
"type": "string" |
|||
}, |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"passport": { |
|||
"type": "string" |
|||
}, |
|||
"passportExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"timeCode": { |
|||
"type": "string" |
|||
}, |
|||
"travelDate": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.BookingResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string" |
|||
}, |
|||
"gender": { |
|||
"type": "string" |
|||
}, |
|||
"id": { |
|||
"type": "string" |
|||
}, |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"passport": { |
|||
"type": "string" |
|||
}, |
|||
"passportExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"status": { |
|||
"type": "string" |
|||
}, |
|||
"timeCode": { |
|||
"type": "string" |
|||
}, |
|||
"travelDate": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.ServerTimeResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"serverLocalTime": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.ProfileRequest": { |
|||
"type": "object", |
|||
"properties": { |
|||
"ktmTrainCreditCard": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardCVV": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardType": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainPassword": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainUsername": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.ProfileResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"ktmTrainCreditCard": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardCVV": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardType": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainPassword": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainUsername": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.UserLoginRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"password", |
|||
"username" |
|||
], |
|||
"properties": { |
|||
"password": { |
|||
"type": "string" |
|||
}, |
|||
"username": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 2 |
|||
} |
|||
} |
|||
}, |
|||
"user.UserRegisterRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"password", |
|||
"username" |
|||
], |
|||
"properties": { |
|||
"password": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 6 |
|||
}, |
|||
"username": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 2 |
|||
} |
|||
} |
|||
}, |
|||
"user.UserResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string" |
|||
}, |
|||
"profile": { |
|||
"$ref": "#/definitions/user.ProfileResponse" |
|||
}, |
|||
"username": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}` |
|||
|
|||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
|||
var SwaggerInfo = &swag.Spec{ |
|||
Version: "1.0", |
|||
Host: "", |
|||
BasePath: "/", |
|||
Schemes: []string{}, |
|||
Title: "KTM Train Booking Bot", |
|||
Description: "API for frontend - built on Go-chi", |
|||
InfoInstanceName: "swagger", |
|||
SwaggerTemplate: docTemplate, |
|||
} |
|||
|
|||
func init() { |
|||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) |
|||
} |
@ -0,0 +1,585 @@ |
|||
{ |
|||
"swagger": "2.0", |
|||
"info": { |
|||
"description": "API for frontend - built on Go-chi", |
|||
"title": "KTM Train Booking Bot", |
|||
"contact": { |
|||
"name": "Samuel Pua", |
|||
"url": "https://git.samuelpua.com/telboon" |
|||
}, |
|||
"version": "1.0" |
|||
}, |
|||
"basePath": "/", |
|||
"paths": { |
|||
"/": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Base" |
|||
], |
|||
"summary": "This is test", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Not Found", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/booking": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Get All Booking", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Create New Booking", |
|||
"parameters": [ |
|||
{ |
|||
"description": "Booking Create Request", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingCreateRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/booking/{bookingID}": { |
|||
"delete": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"ktmtrainbot Booking" |
|||
], |
|||
"summary": "Delete booking", |
|||
"parameters": [ |
|||
{ |
|||
"type": "string", |
|||
"description": "Booking ID", |
|||
"name": "bookingID", |
|||
"in": "path", |
|||
"required": true |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.BookingResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/ktmtrainbot/current-time": { |
|||
"get": { |
|||
"description": "Description", |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Info" |
|||
], |
|||
"summary": "Get current server time", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/ktmtrainbot.ServerTimeResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/login": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user login", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User Login info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserLoginRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/logout": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user logout", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.TextResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/me": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "Returns current logged in user", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/profile": { |
|||
"put": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For setting current user profile", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User registration info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.ProfileRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/api/v1/user/register": { |
|||
"post": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"User" |
|||
], |
|||
"summary": "For user registration", |
|||
"parameters": [ |
|||
{ |
|||
"description": "User registration info", |
|||
"name": "user", |
|||
"in": "body", |
|||
"required": true, |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserRegisterRequest" |
|||
} |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"$ref": "#/definitions/user.UserResponse" |
|||
} |
|||
}, |
|||
"400": { |
|||
"description": "Bad Request", |
|||
"schema": { |
|||
"$ref": "#/definitions/common.ErrResponse" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/health": { |
|||
"get": { |
|||
"description": "Description", |
|||
"consumes": [ |
|||
"application/json" |
|||
], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"tags": [ |
|||
"Base" |
|||
], |
|||
"summary": "Responds to health check", |
|||
"responses": { |
|||
"200": { |
|||
"description": "OK", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Not Found", |
|||
"schema": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"definitions": { |
|||
"common.ErrResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"code": { |
|||
"description": "application-specific error code", |
|||
"type": "integer" |
|||
}, |
|||
"error": { |
|||
"description": "application-level error message, for debugging", |
|||
"type": "string" |
|||
}, |
|||
"status": { |
|||
"description": "user-level status message", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"common.TextResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"status": { |
|||
"description": "user-level status message", |
|||
"type": "string" |
|||
}, |
|||
"text": { |
|||
"description": "application-specific error code", |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.BookingCreateRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"name", |
|||
"passport", |
|||
"passportExpiry", |
|||
"travelDate" |
|||
], |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string" |
|||
}, |
|||
"gender": { |
|||
"type": "string" |
|||
}, |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"passport": { |
|||
"type": "string" |
|||
}, |
|||
"passportExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"timeCode": { |
|||
"type": "string" |
|||
}, |
|||
"travelDate": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.BookingResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"contact": { |
|||
"type": "string" |
|||
}, |
|||
"gender": { |
|||
"type": "string" |
|||
}, |
|||
"id": { |
|||
"type": "string" |
|||
}, |
|||
"name": { |
|||
"type": "string" |
|||
}, |
|||
"passport": { |
|||
"type": "string" |
|||
}, |
|||
"passportExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"status": { |
|||
"type": "string" |
|||
}, |
|||
"timeCode": { |
|||
"type": "string" |
|||
}, |
|||
"travelDate": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"ktmtrainbot.ServerTimeResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"serverLocalTime": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.ProfileRequest": { |
|||
"type": "object", |
|||
"properties": { |
|||
"ktmTrainCreditCard": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardCVV": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardType": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainPassword": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainUsername": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.ProfileResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"ktmTrainCreditCard": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardCVV": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardExpiry": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainCreditCardType": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainPassword": { |
|||
"type": "string" |
|||
}, |
|||
"ktmTrainUsername": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
}, |
|||
"user.UserLoginRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"password", |
|||
"username" |
|||
], |
|||
"properties": { |
|||
"password": { |
|||
"type": "string" |
|||
}, |
|||
"username": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 2 |
|||
} |
|||
} |
|||
}, |
|||
"user.UserRegisterRequest": { |
|||
"type": "object", |
|||
"required": [ |
|||
"password", |
|||
"username" |
|||
], |
|||
"properties": { |
|||
"password": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 6 |
|||
}, |
|||
"username": { |
|||
"type": "string", |
|||
"maxLength": 100, |
|||
"minLength": 2 |
|||
} |
|||
} |
|||
}, |
|||
"user.UserResponse": { |
|||
"type": "object", |
|||
"properties": { |
|||
"id": { |
|||
"type": "string" |
|||
}, |
|||
"profile": { |
|||
"$ref": "#/definitions/user.ProfileResponse" |
|||
}, |
|||
"username": { |
|||
"type": "string" |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,387 @@ |
|||
basePath: / |
|||
definitions: |
|||
common.ErrResponse: |
|||
properties: |
|||
code: |
|||
description: application-specific error code |
|||
type: integer |
|||
error: |
|||
description: application-level error message, for debugging |
|||
type: string |
|||
status: |
|||
description: user-level status message |
|||
type: string |
|||
type: object |
|||
common.TextResponse: |
|||
properties: |
|||
status: |
|||
description: user-level status message |
|||
type: string |
|||
text: |
|||
description: application-specific error code |
|||
type: string |
|||
type: object |
|||
ktmtrainbot.BookingCreateRequest: |
|||
properties: |
|||
contact: |
|||
type: string |
|||
gender: |
|||
type: string |
|||
name: |
|||
type: string |
|||
passport: |
|||
type: string |
|||
passportExpiry: |
|||
type: string |
|||
timeCode: |
|||
type: string |
|||
travelDate: |
|||
type: string |
|||
required: |
|||
- name |
|||
- passport |
|||
- passportExpiry |
|||
- travelDate |
|||
type: object |
|||
ktmtrainbot.BookingResponse: |
|||
properties: |
|||
contact: |
|||
type: string |
|||
gender: |
|||
type: string |
|||
id: |
|||
type: string |
|||
name: |
|||
type: string |
|||
passport: |
|||
type: string |
|||
passportExpiry: |
|||
type: string |
|||
status: |
|||
type: string |
|||
timeCode: |
|||
type: string |
|||
travelDate: |
|||
type: string |
|||
type: object |
|||
ktmtrainbot.ServerTimeResponse: |
|||
properties: |
|||
serverLocalTime: |
|||
type: string |
|||
type: object |
|||
user.ProfileRequest: |
|||
properties: |
|||
ktmTrainCreditCard: |
|||
type: string |
|||
ktmTrainCreditCardCVV: |
|||
type: string |
|||
ktmTrainCreditCardExpiry: |
|||
type: string |
|||
ktmTrainCreditCardType: |
|||
type: string |
|||
ktmTrainPassword: |
|||
type: string |
|||
ktmTrainUsername: |
|||
type: string |
|||
type: object |
|||
user.ProfileResponse: |
|||
properties: |
|||
ktmTrainCreditCard: |
|||
type: string |
|||
ktmTrainCreditCardCVV: |
|||
type: string |
|||
ktmTrainCreditCardExpiry: |
|||
type: string |
|||
ktmTrainCreditCardType: |
|||
type: string |
|||
ktmTrainPassword: |
|||
type: string |
|||
ktmTrainUsername: |
|||
type: string |
|||
type: object |
|||
user.UserLoginRequest: |
|||
properties: |
|||
password: |
|||
type: string |
|||
username: |
|||
maxLength: 100 |
|||
minLength: 2 |
|||
type: string |
|||
required: |
|||
- password |
|||
- username |
|||
type: object |
|||
user.UserRegisterRequest: |
|||
properties: |
|||
password: |
|||
maxLength: 100 |
|||
minLength: 6 |
|||
type: string |
|||
username: |
|||
maxLength: 100 |
|||
minLength: 2 |
|||
type: string |
|||
required: |
|||
- password |
|||
- username |
|||
type: object |
|||
user.UserResponse: |
|||
properties: |
|||
id: |
|||
type: string |
|||
profile: |
|||
$ref: '#/definitions/user.ProfileResponse' |
|||
username: |
|||
type: string |
|||
type: object |
|||
info: |
|||
contact: |
|||
name: Samuel Pua |
|||
url: https://git.samuelpua.com/telboon |
|||
description: API for frontend - built on Go-chi |
|||
title: KTM Train Booking Bot |
|||
version: "1.0" |
|||
paths: |
|||
/: |
|||
get: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
type: string |
|||
"404": |
|||
description: Not Found |
|||
schema: |
|||
type: string |
|||
summary: This is test |
|||
tags: |
|||
- Base |
|||
/api/v1/ktmtrainbot/booking: |
|||
get: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
items: |
|||
$ref: '#/definitions/ktmtrainbot.BookingResponse' |
|||
type: array |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: Get All Booking |
|||
tags: |
|||
- ktmtrainbot Booking |
|||
post: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
parameters: |
|||
- description: Booking Create Request |
|||
in: body |
|||
name: user |
|||
required: true |
|||
schema: |
|||
$ref: '#/definitions/ktmtrainbot.BookingCreateRequest' |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/ktmtrainbot.BookingResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: Create New Booking |
|||
tags: |
|||
- ktmtrainbot Booking |
|||
/api/v1/ktmtrainbot/booking/{bookingID}: |
|||
delete: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
parameters: |
|||
- description: Booking ID |
|||
in: path |
|||
name: bookingID |
|||
required: true |
|||
type: string |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/ktmtrainbot.BookingResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: Delete booking |
|||
tags: |
|||
- ktmtrainbot Booking |
|||
/api/v1/ktmtrainbot/current-time: |
|||
get: |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/ktmtrainbot.ServerTimeResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: Get current server time |
|||
tags: |
|||
- Info |
|||
/api/v1/user/login: |
|||
post: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
parameters: |
|||
- description: User Login info |
|||
in: body |
|||
name: user |
|||
required: true |
|||
schema: |
|||
$ref: '#/definitions/user.UserLoginRequest' |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/user.UserResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: For user login |
|||
tags: |
|||
- User |
|||
/api/v1/user/logout: |
|||
post: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/common.TextResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: For user logout |
|||
tags: |
|||
- User |
|||
/api/v1/user/me: |
|||
get: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/user.UserResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: Returns current logged in user |
|||
tags: |
|||
- User |
|||
/api/v1/user/profile: |
|||
put: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
parameters: |
|||
- description: User registration info |
|||
in: body |
|||
name: user |
|||
required: true |
|||
schema: |
|||
$ref: '#/definitions/user.ProfileRequest' |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/user.UserResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: For setting current user profile |
|||
tags: |
|||
- User |
|||
/api/v1/user/register: |
|||
post: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
parameters: |
|||
- description: User registration info |
|||
in: body |
|||
name: user |
|||
required: true |
|||
schema: |
|||
$ref: '#/definitions/user.UserRegisterRequest' |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
$ref: '#/definitions/user.UserResponse' |
|||
"400": |
|||
description: Bad Request |
|||
schema: |
|||
$ref: '#/definitions/common.ErrResponse' |
|||
summary: For user registration |
|||
tags: |
|||
- User |
|||
/health: |
|||
get: |
|||
consumes: |
|||
- application/json |
|||
description: Description |
|||
produces: |
|||
- application/json |
|||
responses: |
|||
"200": |
|||
description: OK |
|||
schema: |
|||
type: string |
|||
"404": |
|||
description: Not Found |
|||
schema: |
|||
type: string |
|||
summary: Responds to health check |
|||
tags: |
|||
- Base |
|||
swagger: "2.0" |
@ -0,0 +1,54 @@ |
|||
module git.samuelpua.com/telboon/ktm-train-bot/backend |
|||
|
|||
go 1.18 |
|||
|
|||
require ( |
|||
github.com/go-chi/chi v1.5.4 |
|||
github.com/go-chi/render v1.0.1 |
|||
github.com/go-playground/validator/v10 v10.11.0 |
|||
github.com/go-rod/rod v0.109.1 |
|||
github.com/google/uuid v1.3.0 |
|||
github.com/swaggo/http-swagger v1.2.0 |
|||
github.com/swaggo/swag v1.7.9 |
|||
gorm.io/driver/postgres v1.2.3 |
|||
gorm.io/gorm v1.22.5 |
|||
) |
|||
|
|||
require ( |
|||
github.com/KyleBanks/depth v1.2.1 // indirect |
|||
github.com/PuerkitoBio/purell v1.1.1 // indirect |
|||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect |
|||
github.com/go-openapi/jsonpointer v0.19.5 // indirect |
|||
github.com/go-openapi/jsonreference v0.19.6 // indirect |
|||
github.com/go-openapi/spec v0.20.4 // indirect |
|||
github.com/go-openapi/swag v0.19.15 // indirect |
|||
github.com/go-playground/locales v0.14.0 // indirect |
|||
github.com/go-playground/universal-translator v0.18.0 // indirect |
|||
github.com/josharian/intern v1.0.0 // indirect |
|||
github.com/leodido/go-urn v1.2.1 // indirect |
|||
github.com/mailru/easyjson v0.7.6 // indirect |
|||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 // indirect |
|||
github.com/ysmood/goob v0.4.0 // indirect |
|||
github.com/ysmood/gson v0.7.1 // indirect |
|||
github.com/ysmood/leakless v0.8.0 // indirect |
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect |
|||
golang.org/x/tools v0.1.7 // indirect |
|||
) |
|||
|
|||
require ( |
|||
github.com/jackc/chunkreader/v2 v2.0.1 // indirect |
|||
github.com/jackc/pgconn v1.10.1 // indirect |
|||
github.com/jackc/pgio v1.0.0 // indirect |
|||
github.com/jackc/pgpassfile v1.0.0 // indirect |
|||
github.com/jackc/pgproto3/v2 v2.2.0 // indirect |
|||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect |
|||
github.com/jackc/pgtype v1.9.0 // indirect |
|||
github.com/jackc/pgx/v4 v4.14.0 // indirect |
|||
github.com/jinzhu/inflection v1.0.0 // indirect |
|||
github.com/jinzhu/now v1.1.4 // indirect |
|||
github.com/joho/godotenv v1.4.0 |
|||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed |
|||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e // indirect |
|||
golang.org/x/text v0.3.7 // indirect |
|||
gopkg.in/yaml.v2 v2.4.0 // indirect |
|||
) |
@ -0,0 +1,304 @@ |
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |
|||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= |
|||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= |
|||
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= |
|||
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= |
|||
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/agiledragon/gomonkey/v2 v2.3.1 h1:k+UnUY0EMNYUFUAQVETGY9uUTxjMdnUkP0ARyJS1zzs= |
|||
github.com/agiledragon/gomonkey/v2 v2.3.1/go.mod h1:ap1AmDzcVOAz1YpeJ3TCzIgstoaWLA6jbbgxfB4w2iY= |
|||
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= |
|||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= |
|||
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/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= |
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= |
|||
github.com/go-chi/chi v1.5.4 h1:QHdzF2szwjqVV4wmByUnTcsbIg7UGaQ0tPF2t5GcAIs= |
|||
github.com/go-chi/chi v1.5.4/go.mod h1:uaf8YgoFazUOkPBG7fxPftUylNumIev9awIWOENIuEg= |
|||
github.com/go-chi/render v1.0.1 h1:4/5tis2cKaNdnv9zFLfXzcquC9HbeZgCnxGnKrltBS8= |
|||
github.com/go-chi/render v1.0.1/go.mod h1:pq4Rr7HbnsdaeHagklXub+p6Wd16Af5l9koip1OvJns= |
|||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= |
|||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= |
|||
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.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= |
|||
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= |
|||
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= |
|||
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= |
|||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= |
|||
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= |
|||
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= |
|||
github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= |
|||
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= |
|||
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= |
|||
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= |
|||
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= |
|||
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= |
|||
github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= |
|||
github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= |
|||
github.com/go-rod/rod v0.109.1 h1:658X/G9xyQKjFUNo5apMsIyHpEb/KJnJ5LkAl6a62AI= |
|||
github.com/go-rod/rod v0.109.1/go.mod h1:GZDtmEs6RpF6kBRYpGCZXxXlKNneKVPiKOjaMbmVVjE= |
|||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= |
|||
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= |
|||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= |
|||
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= |
|||
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= |
|||
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= |
|||
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= |
|||
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= |
|||
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= |
|||
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= |
|||
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= |
|||
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= |
|||
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= |
|||
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= |
|||
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= |
|||
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= |
|||
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= |
|||
github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= |
|||
github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= |
|||
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= |
|||
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= |
|||
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= |
|||
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= |
|||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= |
|||
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= |
|||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= |
|||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= |
|||
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= |
|||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= |
|||
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= |
|||
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= |
|||
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= |
|||
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= |
|||
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= |
|||
github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= |
|||
github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= |
|||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= |
|||
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= |
|||
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= |
|||
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= |
|||
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= |
|||
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= |
|||
github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= |
|||
github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= |
|||
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= |
|||
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= |
|||
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= |
|||
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= |
|||
github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= |
|||
github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= |
|||
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= |
|||
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= |
|||
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= |
|||
github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= |
|||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= |
|||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= |
|||
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= |
|||
github.com/jinzhu/now v1.1.4 h1:tHnRBy1i5F2Dh8BAFxqFzxKqqvezXrL2OW1TnX+Mlas= |
|||
github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= |
|||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= |
|||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= |
|||
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/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= |
|||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= |
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
|||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= |
|||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= |
|||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= |
|||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= |
|||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= |
|||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= |
|||
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= |
|||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= |
|||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= |
|||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= |
|||
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= |
|||
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= |
|||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= |
|||
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= |
|||
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= |
|||
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8= |
|||
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= |
|||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= |
|||
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= |
|||
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= |
|||
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= |
|||
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= |
|||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= |
|||
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= |
|||
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= |
|||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= |
|||
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= |
|||
github.com/otiai10/copy v1.7.0 h1:hVoPiN+t+7d2nzzwMiDHPSOogsWAStewq3TwU05+clE= |
|||
github.com/otiai10/copy v1.7.0/go.mod h1:rmRl6QPdJj6EiUqXQ/4Nn2lLXoNQjFCQbbNrxgc/t3U= |
|||
github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= |
|||
github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= |
|||
github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= |
|||
github.com/otiai10/mint v1.3.3/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= |
|||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= |
|||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= |
|||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
|||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= |
|||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= |
|||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= |
|||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= |
|||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= |
|||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= |
|||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= |
|||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= |
|||
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= |
|||
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= |
|||
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= |
|||
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= |
|||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= |
|||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= |
|||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= |
|||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= |
|||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= |
|||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= |
|||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= |
|||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
|||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= |
|||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= |
|||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= |
|||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2 h1:+iNTcqQJy0OZ5jk6a5NLib47eqXK8uYcPX+O4+cBpEM= |
|||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2/go.mod h1:lKJPbtWzJ9JhsTN1k1gZgleJWY/cqq0psdoMmaThG3w= |
|||
github.com/swaggo/http-swagger v1.2.0 h1:G5EBD5nvw379l2sFhact660YDT++eLviczLPrgNw/lU= |
|||
github.com/swaggo/http-swagger v1.2.0/go.mod h1:P7+V1SLG2zloe+VvAGL7WgFimhJACaBLAv2N7YQ0ikI= |
|||
github.com/swaggo/swag v1.7.8/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= |
|||
github.com/swaggo/swag v1.7.9 h1:6vCG5mm43ebDzGlZPMGYrYI4zKFfOr5kicQX8qjeDwc= |
|||
github.com/swaggo/swag v1.7.9/go.mod h1:gZ+TJ2w/Ve1RwQsA2IRoSOTidHz6DX+PIG8GWvbnoLU= |
|||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= |
|||
github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= |
|||
github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= |
|||
github.com/ysmood/got v0.31.3 h1:UvvF+TDVsZLO7MSzm/Bd/H4HVp+7S5YwsxgdwaKq8uA= |
|||
github.com/ysmood/got v0.31.3/go.mod h1:pE1l4LOwOBhQg6A/8IAatkGp7uZjnalzrZolnlhhMgY= |
|||
github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY= |
|||
github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= |
|||
github.com/ysmood/gson v0.7.1 h1:zKL2MTGtynxdBdlZjyGsvEOZ7dkxaY5TH6QhAbTgz0Q= |
|||
github.com/ysmood/gson v0.7.1/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= |
|||
github.com/ysmood/leakless v0.8.0 h1:BzLrVoiwxikpgEQR0Lk8NyBN5Cit2b1z+u0mgL4ZJak= |
|||
github.com/ysmood/leakless v0.8.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= |
|||
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= |
|||
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= |
|||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= |
|||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= |
|||
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= |
|||
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= |
|||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= |
|||
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= |
|||
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= |
|||
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= |
|||
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= |
|||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= |
|||
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= |
|||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |
|||
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= |
|||
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |
|||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= |
|||
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= |
|||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
|||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
|||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= |
|||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= |
|||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed h1:YoWVYYAfvQ4ddHv3OKmIvX7NCAhFGTj62VP2l2kfBbA= |
|||
golang.org/x/crypto v0.0.0-20220128200615-198e4374d7ed/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= |
|||
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.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= |
|||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= |
|||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |
|||
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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= |
|||
golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= |
|||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= |
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= |
|||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |
|||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |
|||
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= |
|||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= |
|||
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/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|||
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= |
|||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= |
|||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= |
|||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |
|||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
|||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |
|||
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= |
|||
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= |
|||
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/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/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |
|||
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ= |
|||
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= |
|||
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-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= |
|||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |
|||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= |
|||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= |
|||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= |
|||
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= |
|||
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.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/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|||
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= |
|||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|||
gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= |
|||
gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= |
|||
gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= |
|||
gorm.io/gorm v1.22.5 h1:lYREBgc02Be/5lSCTuysZZDb6ffL2qrat6fg9CFbvXU= |
|||
gorm.io/gorm v1.22.5/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= |
|||
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= |
@ -0,0 +1,131 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"fmt" |
|||
"log" |
|||
"os" |
|||
"strings" |
|||
"time" |
|||
|
|||
"github.com/google/uuid" |
|||
"gorm.io/driver/postgres" |
|||
"gorm.io/gorm" |
|||
"gorm.io/gorm/logger" |
|||
) |
|||
|
|||
type Database struct { |
|||
*gorm.DB |
|||
} |
|||
|
|||
var DB *gorm.DB |
|||
|
|||
// Opening a database and save the reference to `Database` struct.
|
|||
func InitDB() *gorm.DB { |
|||
host := os.Getenv("DB_HOST") |
|||
user := os.Getenv("DB_USER") |
|||
pass := os.Getenv("DB_PASS") |
|||
dbName := os.Getenv("DB_NAME") |
|||
port := os.Getenv("DB_PORT") |
|||
|
|||
var sslMode string |
|||
if os.Getenv("DB_SSL") == "TRUE" { |
|||
sslMode = "enable" |
|||
} else { |
|||
sslMode = "disable" |
|||
} |
|||
|
|||
// DB Logger config
|
|||
newLogger := logger.New( |
|||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
|||
logger.Config{ |
|||
SlowThreshold: time.Second, // Slow SQL threshold
|
|||
LogLevel: logger.Silent, // Log level
|
|||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
|||
Colorful: true, // Disable color
|
|||
}, |
|||
) |
|||
|
|||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=Asia/Singapore", host, user, pass, dbName, port, sslMode) |
|||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ |
|||
Logger: newLogger, |
|||
}) |
|||
if err != nil { |
|||
fmt.Println("db err: (Init) ", err) |
|||
} |
|||
|
|||
// Setup UUID
|
|||
// CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|||
db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") |
|||
|
|||
DB = db |
|||
return DB |
|||
} |
|||
|
|||
// This function will create a temporarily database for running testing cases
|
|||
func TestDBInit() *gorm.DB { |
|||
host := os.Getenv("TEST_DB_HOST") |
|||
user := os.Getenv("TEST_DB_USER") |
|||
pass := os.Getenv("TEST_DB_PASS") |
|||
dbName := fmt.Sprintf("%s_%s", os.Getenv("TEST_DB_NAME"), uuid.New().String()) |
|||
dbName = strings.ReplaceAll(dbName, "-", "_") |
|||
port := os.Getenv("TEST_DB_PORT") |
|||
|
|||
var sslMode string |
|||
if os.Getenv("TEST_DB_SSL") == "TRUE" { |
|||
sslMode = "enable" |
|||
} else { |
|||
sslMode = "disable" |
|||
} |
|||
|
|||
// DB Logger config
|
|||
newLogger := logger.New( |
|||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
|||
logger.Config{ |
|||
SlowThreshold: time.Second, // Slow SQL threshold
|
|||
LogLevel: logger.Silent, // Log level
|
|||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
|||
Colorful: true, // Disable color
|
|||
}, |
|||
) |
|||
|
|||
dsn := fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=Asia/Singapore", host, user, pass, "postgres", port, sslMode) |
|||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{ |
|||
Logger: newLogger, |
|||
}) |
|||
if err != nil { |
|||
fmt.Println("db err: (Init) ", err) |
|||
} |
|||
|
|||
// Create Database
|
|||
err = db.Exec(fmt.Sprintf("CREATE DATABASE %s;", dbName)).Error |
|||
if err != nil { |
|||
fmt.Println("db err: (Init) ", err) |
|||
} |
|||
|
|||
// Get into testing database
|
|||
dsn = fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s sslmode=%s TimeZone=Asia/Singapore", host, user, pass, dbName, port, sslMode) |
|||
db, err = gorm.Open(postgres.Open(dsn), &gorm.Config{ |
|||
Logger: newLogger, |
|||
}) |
|||
if err != nil { |
|||
fmt.Println("db err: (Init) ", err) |
|||
} |
|||
|
|||
// Setup UUID
|
|||
// CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|||
db.Exec("CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";") |
|||
|
|||
DB = db |
|||
return DB |
|||
} |
|||
|
|||
func DestroyTestingDB(db *gorm.DB) { |
|||
var dbName string |
|||
db.Raw("SELECT current_database();").Scan(&dbName) |
|||
db.Exec(fmt.Sprintf("DROP DATABASE %s", dbName)) |
|||
} |
|||
|
|||
// Using this function to get a connection, you can create your connection pool here.
|
|||
func GetDB() *gorm.DB { |
|||
return DB |
|||
} |
@ -0,0 +1,56 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
type ErrResponse struct { |
|||
Err error `json:"-"` // low-level runtime error
|
|||
HTTPStatusCode int `json:"-"` // http response status code
|
|||
|
|||
StatusText string `json:"status"` // user-level status message
|
|||
AppCode int64 `json:"code,omitempty"` // application-specific error code
|
|||
ErrorText string `json:"error,omitempty"` // application-level error message, for debugging
|
|||
} |
|||
|
|||
func (e *ErrResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
render.Status(r, e.HTTPStatusCode) |
|||
return nil |
|||
} |
|||
|
|||
func ErrInvalidRequest(err error) render.Renderer { |
|||
return &ErrResponse{ |
|||
Err: err, |
|||
HTTPStatusCode: 400, |
|||
StatusText: "Invalid request.", |
|||
ErrorText: err.Error(), |
|||
} |
|||
} |
|||
|
|||
func ErrValidationError(err error) render.Renderer { |
|||
return &ErrResponse{ |
|||
Err: err, |
|||
HTTPStatusCode: 422, |
|||
StatusText: "Validation error.", |
|||
ErrorText: err.Error(), |
|||
} |
|||
} |
|||
|
|||
func ErrInternalError(err error) render.Renderer { |
|||
return &ErrResponse{ |
|||
Err: err, |
|||
HTTPStatusCode: 500, |
|||
StatusText: "Invalid request.", |
|||
ErrorText: err.Error(), |
|||
} |
|||
} |
|||
|
|||
func ErrNotFound(err error) render.Renderer { |
|||
return &ErrResponse{ |
|||
HTTPStatusCode: 404, |
|||
StatusText: "Resource not found.", |
|||
ErrorText: err.Error(), |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
package common |
|||
|
|||
import ( |
|||
"net/http" |
|||
|
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
type TextResponse struct { |
|||
Status string `json:"status"` // user-level status message
|
|||
Text string `json:"text"` // application-specific error code
|
|||
} |
|||
|
|||
func (e *TextResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
render.Status(r, http.StatusOK) |
|||
return nil |
|||
} |
|||
|
|||
func NewGenericTextResponse(status string, text string) render.Renderer { |
|||
return &TextResponse{ |
|||
Status: status, |
|||
Text: text, |
|||
} |
|||
} |
@ -0,0 +1,477 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"fmt" |
|||
"log" |
|||
"os" |
|||
"strconv" |
|||
"strings" |
|||
"time" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/go-rod/rod" |
|||
"github.com/go-rod/rod/lib/devices" |
|||
"github.com/go-rod/rod/lib/launcher" |
|||
"gorm.io/gorm" |
|||
"gorm.io/gorm/logger" |
|||
) |
|||
|
|||
const IMAGE_DIR = "/tmp/screenshots" |
|||
const TIMEOUT_MINUTE = 60 |
|||
|
|||
func (env *Env) BackgroundJobRunner() { |
|||
log.Println("Initialising background job...") |
|||
initialiseRodBrowser() |
|||
log.Println("Browser initialised...") |
|||
|
|||
// Initialise silent logger
|
|||
newLogger := logger.New( |
|||
log.New(os.Stdout, "\r\n", log.LstdFlags), // io writer
|
|||
logger.Config{ |
|||
SlowThreshold: time.Second, // Slow SQL threshold
|
|||
LogLevel: logger.Silent, // Log level
|
|||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
|||
Colorful: true, // Disable color
|
|||
}, |
|||
) |
|||
|
|||
tx := env.DB.Session(&gorm.Session{Logger: newLogger}) |
|||
for { |
|||
var jobToDo Booking |
|||
err := tx.Model(&jobToDo). |
|||
Where("status = ?", "pending"). |
|||
Preload("User"). |
|||
First(&jobToDo).Error |
|||
|
|||
// if no jobs pending found
|
|||
if err != nil { |
|||
time.Sleep(time.Second) |
|||
continue |
|||
} else { // if there's job to do
|
|||
// Create next run where it's not the past (either from old NextRun or now())
|
|||
timeNow := time.Now() |
|||
|
|||
if timeNow.Hour() == 0 && timeNow.Minute() == 10 { |
|||
err := env.DB.Where(&user.Profile{UserID: jobToDo.UserID}).First(&jobToDo.User.Profile).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
} |
|||
|
|||
log.Printf("Start doing job: %v", jobToDo.ID) |
|||
username := jobToDo.User.Profile.KtmTrainUsername |
|||
password := jobToDo.User.Profile.KtmTrainPassword |
|||
creditCardType := jobToDo.User.Profile.KtmTrainCreditCardType |
|||
creditCard := jobToDo.User.Profile.KtmTrainCreditCard |
|||
creditCardCVV := jobToDo.User.Profile.KtmTrainCreditCardCVV |
|||
creditCardExpiry := jobToDo.User.Profile.KtmTrainCreditCardExpiry |
|||
|
|||
func() { |
|||
defer func() { |
|||
if r := recover(); r != nil { |
|||
log.Println("Recovering from job panic...") |
|||
jobToDo.Status = "pending" |
|||
env.DB.Save(&jobToDo) |
|||
} |
|||
}() |
|||
success := env.startBooking(&jobToDo, username, password, creditCardType, creditCard, creditCardCVV, creditCardExpiry) |
|||
if success { |
|||
fmt.Println("Successfully made a booking.") |
|||
jobToDo.Status = "success" |
|||
env.DB.Save(jobToDo) |
|||
} else { |
|||
jobToDo.Status = "pending" |
|||
env.DB.Save(&jobToDo) |
|||
fmt.Println("Failed to make a booking.") |
|||
} |
|||
}() |
|||
jobToDo.Status = "running" |
|||
env.DB.Save(&jobToDo) |
|||
log.Printf("Job Started: %v", jobToDo.ID) |
|||
} |
|||
} |
|||
|
|||
} |
|||
} |
|||
|
|||
func initialiseRodBrowser() { |
|||
u := launcher.New(). |
|||
Set("headless"). |
|||
MustLaunch() |
|||
|
|||
defaultDevice := devices.LaptopWithMDPIScreen |
|||
browser := rod.New().ControlURL(u).MustConnect().DefaultDevice(defaultDevice) |
|||
page := browser.MustPage("https://www.google.com").MustWindowFullscreen() |
|||
page.MustWaitLoad() |
|||
browser.MustClose() |
|||
|
|||
// Initialise screenshot directory
|
|||
if _, err := os.Stat(IMAGE_DIR); errors.Is(err, os.ErrNotExist) { |
|||
err := os.Mkdir(IMAGE_DIR, os.ModePerm) |
|||
if err != nil { |
|||
log.Println(err) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func (env *Env) startBooking(job *Booking, username string, password string, creditCardType string, creditCard string, creditCardCVV string, creditCardExpiry string) bool { |
|||
timerCtx, cancelTimer := context.WithTimeout(context.Background(), TIMEOUT_MINUTE*time.Minute) |
|||
defer cancelTimer() |
|||
|
|||
headless := os.Getenv("HEADLESS") |
|||
var u string |
|||
if strings.ToUpper(headless) == "FALSE" { |
|||
u = launcher.New(). |
|||
Set("headless"). |
|||
Delete("--headless"). |
|||
MustLaunch() |
|||
} else { |
|||
u = launcher.New(). |
|||
Set("headless"). |
|||
MustLaunch() |
|||
} |
|||
|
|||
defaultDevice := devices.LaptopWithMDPIScreen |
|||
// defaultDevice.Screen.Vertical.Height = defaultDevice.Screen.Horizontal.Height
|
|||
// defaultDevice.Screen.Vertical.Width = defaultDevice.Screen.Horizontal.Width
|
|||
defaultDevice.UserAgent = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36" |
|||
browser := rod.New().ControlURL(u).MustConnect().DefaultDevice(defaultDevice) |
|||
|
|||
// Defer closing browser
|
|||
defer browser.MustClose() |
|||
|
|||
postLoginPage := ktmTrainLogin(browser, username, password) |
|||
nowTimeStr := time.Now().Format("2006-01-02-15_04_05") |
|||
postLoginPage.MustWaitLoad().MustScreenshot(fmt.Sprintf("%s/%s-01-login.png", IMAGE_DIR, nowTimeStr)) |
|||
|
|||
// Exits if context cancelled
|
|||
select { |
|||
case <-timerCtx.Done(): |
|||
browser.MustClose() |
|||
return false |
|||
default: |
|||
} |
|||
|
|||
var page *rod.Page |
|||
|
|||
getBookingSlotCtx, cancelGetBookingSlot := context.WithTimeout(context.Background(), TIMEOUT_MINUTE*time.Minute) |
|||
defer cancelGetBookingSlot() |
|||
|
|||
pageChan := make(chan *rod.Page) |
|||
|
|||
onwardDate := job.TravelDate.Format("2 Jan 2006") |
|||
timeCode := job.TimeCode |
|||
name := job.Name |
|||
gender := job.Gender |
|||
passport := job.Passport |
|||
passportExpiry := job.PassportExpiry.Format("2 Jan 2006") |
|||
contact := job.Contact |
|||
|
|||
threadCount := 10 |
|||
for i := 0; i < threadCount; i++ { |
|||
time.Sleep(time.Millisecond * 100) |
|||
go func() { |
|||
currPage := getBookingSlots(browser, onwardDate) |
|||
log.Println("Booking page loaded.") |
|||
|
|||
currPage = selectBookingSlot(getBookingSlotCtx, currPage, timeCode) |
|||
log.Println("Booking slot selected.") |
|||
|
|||
select { |
|||
case <-getBookingSlotCtx.Done(): |
|||
return |
|||
default: |
|||
log.Println("First page loaded.") |
|||
} |
|||
|
|||
// Cancelling context
|
|||
cancelGetBookingSlot() |
|||
pageChan <- currPage |
|||
}() |
|||
} |
|||
|
|||
page = <-pageChan |
|||
page.MustActivate() |
|||
|
|||
// Exits if context cancelled
|
|||
select { |
|||
case <-timerCtx.Done(): |
|||
browser.MustClose() |
|||
return false |
|||
default: |
|||
} |
|||
|
|||
page = fillPassengerDetails(page, name, gender, passport, passportExpiry, contact) |
|||
log.Println("Passenger details filled.") |
|||
|
|||
// Exits if context cancelled
|
|||
select { |
|||
case <-timerCtx.Done(): |
|||
browser.MustClose() |
|||
return false |
|||
default: |
|||
} |
|||
|
|||
page = choosePayment(page) |
|||
log.Println("Payment method chosen.") |
|||
|
|||
// Wait 5 seconds for payment gateway to load
|
|||
time.Sleep(time.Second * 5) |
|||
|
|||
for _, currPage := range browser.MustPages() { |
|||
currPage.MustWaitLoad() |
|||
|
|||
var currTitle string |
|||
err := rod.Try(func() { |
|||
currTitle = currPage.Timeout(100 * time.Millisecond).MustElement("title").MustText() |
|||
}) |
|||
if err != nil { |
|||
currTitle = "" |
|||
} |
|||
|
|||
if strings.Contains(currTitle, "Payment Acceptance") { |
|||
page = currPage |
|||
} |
|||
} |
|||
|
|||
// Exits if context cancelled
|
|||
select { |
|||
case <-timerCtx.Done(): |
|||
browser.MustClose() |
|||
return false |
|||
default: |
|||
} |
|||
|
|||
expiryMonth := strings.Split(creditCardExpiry, "/")[0] |
|||
expiryYear := strings.Split(creditCardExpiry, "/")[1] |
|||
|
|||
page = makePayment(page, creditCardType, creditCard, expiryMonth, expiryYear, creditCardCVV) |
|||
log.Println("Payment made.") |
|||
|
|||
// // Start debug screenshots
|
|||
// debugScreenshotCtx, cancelDebugScreenshot := context.WithCancel(context.Background())
|
|||
// go takeDebugScreenshots(debugScreenshotCtx, courtPage)
|
|||
|
|||
// // Defer done with debug screenshot
|
|||
// defer cancelDebugScreenshot()
|
|||
|
|||
time.Sleep(600 * time.Second) |
|||
_ = page |
|||
browser.MustClose() |
|||
|
|||
return true |
|||
} |
|||
|
|||
func ktmTrainLogin(browser *rod.Browser, username string, password string) *rod.Page { |
|||
page := browser.MustPage("https://online.ktmb.com.my/Account/Login") |
|||
page.MustElement("#Email").MustInput(username) |
|||
page.MustElement("#Password").MustInput(password) |
|||
page.MustElement("#LoginButton").MustClick() |
|||
|
|||
return page |
|||
} |
|||
|
|||
func getBookingSlots(browser *rod.Browser, onwardDate string) *rod.Page { |
|||
page := browser.MustPage("https://shuttleonline.ktmb.com.my/Home/Shuttle") |
|||
page.MustWaitLoad() |
|||
|
|||
// Dismiss system maintenance warning
|
|||
bodyText := page.MustElement("body").MustText() |
|||
containsCheckStr := "System maintenance scheduled at 23:00 to 00:15" |
|||
if strings.Contains(bodyText, containsCheckStr) { |
|||
page.MustEval(`() => document.querySelector("#validationSummaryModal > div > div > div.modal-body > div > div.text-center > button").click()`) |
|||
} |
|||
|
|||
passengerCount := 1 |
|||
passengerCountStr := strconv.Itoa(passengerCount) |
|||
requestVerificationToken := page.MustElement("#theForm > input[name=__RequestVerificationToken]").MustAttribute("value") |
|||
|
|||
// Get JB Sentral Station Info
|
|||
jBSentralData := page.MustElement("#FromStationData").MustAttribute("value") |
|||
jBSentralID := page.MustElement("#FromStationId").MustAttribute("value") |
|||
|
|||
// Get Woodlands Station Info
|
|||
woodlandsData := page.MustElement("#ToStationData").MustAttribute("value") |
|||
woodlandsID := page.MustElement("#ToStationId").MustAttribute("value") |
|||
|
|||
sensitiveCustomForm(page, *woodlandsData, *jBSentralData, *woodlandsID, *jBSentralID, onwardDate, passengerCountStr, *requestVerificationToken) |
|||
page.MustWaitLoad() |
|||
|
|||
return page |
|||
} |
|||
|
|||
func selectBookingSlot(ctx context.Context, page *rod.Page, timeCode string) *rod.Page { |
|||
time.Sleep(1 * time.Second) |
|||
// Initial closing of maintenance modal
|
|||
bodyText := page.MustElement("body").MustText() |
|||
if strings.Contains(bodyText, "System maintenance scheduled at 23:00 to 00:15 (UTC+8)") { |
|||
closeModalButton := page.MustElement("#popupModalCloseButton") |
|||
closeModalButton.Eval(`this.click()`) |
|||
time.Sleep(1000 * time.Millisecond) |
|||
} |
|||
|
|||
// Start probing
|
|||
completed := false |
|||
for !completed { |
|||
page.MustWaitLoad() |
|||
departTripsTable := page.MustElement(".depart-trips") |
|||
departRows := departTripsTable.MustElements("tr") |
|||
var rowElement *rod.Element |
|||
for _, row := range departRows { |
|||
timeCodeElement := row.MustAttribute("data-hourminute") |
|||
if *timeCodeElement == timeCode { |
|||
rowElement = row |
|||
} |
|||
} |
|||
|
|||
// Checks for context before clicking
|
|||
select { |
|||
case <-ctx.Done(): |
|||
return page |
|||
default: |
|||
} |
|||
|
|||
selectButtonElement := rowElement.MustElement("a") |
|||
selectButtonElement.Eval(`this.click()`) |
|||
|
|||
time.Sleep(1000 * time.Millisecond) |
|||
page.MustWaitLoad() |
|||
|
|||
// Check before exiting
|
|||
bodyText := page.MustElement("body").MustText() |
|||
if strings.Contains(bodyText, "System maintenance scheduled at 23:00 to 00:15 (UTC+8).") { |
|||
completed = false |
|||
closeModalButton := page.MustElement("#popupModalCloseButton") |
|||
closeModalButton.Eval(`this.click()`) |
|||
time.Sleep(1000 * time.Millisecond) |
|||
} else { |
|||
log.Println("Completed probing") |
|||
completed = true |
|||
} |
|||
} |
|||
|
|||
// Checks for context before clicking
|
|||
select { |
|||
case <-ctx.Done(): |
|||
return page |
|||
default: |
|||
} |
|||
|
|||
proceedButton := page.MustElement(".proceed-btn") |
|||
proceedButton.Eval(`this.click()`) |
|||
|
|||
return page |
|||
} |
|||
|
|||
func fillPassengerDetails(page *rod.Page, name string, gender string, passport string, passportExpiry string, contact string) *rod.Page { |
|||
ticketType := "DEWASA/ADULT" |
|||
|
|||
nameElement := page.MustElement(".FullName") |
|||
nameElement.MustInput(name) |
|||
|
|||
passportElement := page.MustElement(".PassportNo") |
|||
passportElement.MustInput(passport) |
|||
|
|||
passportExpiryElement := page.MustElement("#Passengers_0__PassportExpiryDate") |
|||
passportExpiryElement.MustInput(passportExpiry) |
|||
|
|||
contactElement := page.MustElement(".ContactNo") |
|||
contactElement.MustInput(contact) |
|||
|
|||
if gender == "M" { |
|||
maleElement := page.MustElement("#Passengers_0__GenderMale") |
|||
maleElement.Eval(`this.click()`) |
|||
} else { |
|||
|
|||
femaleElement := page.MustElement("#Passengers_0__GenderFemale") |
|||
femaleElement.Eval(`this.click()`) |
|||
} |
|||
ticketTypeElement := page.MustElement("#Passengers_0__TicketTypeId") |
|||
ticketTypeElement.MustSelect(ticketType) |
|||
|
|||
paymentButton := page.MustElement("#btnConfirmPayment") |
|||
paymentButton.Eval(`this.click()`) |
|||
|
|||
page.MustWaitLoad() |
|||
return page |
|||
} |
|||
|
|||
func choosePayment(page *rod.Page) *rod.Page { |
|||
creditCardButton := page.MustElement(".btn-public-bank") |
|||
creditCardButton.Eval(`this.click()`) |
|||
page.MustWaitLoad() |
|||
|
|||
paymentGatewayButton := page.MustElement("#PaymentGateway") |
|||
paymentGatewayButton.Eval(`this.click()`) |
|||
page.MustWaitLoad() |
|||
|
|||
return page |
|||
} |
|||
|
|||
func makePayment(page *rod.Page, cardType string, creditCard string, expiryMonth string, expiryYear string, creditCardCVV string) *rod.Page { |
|||
if cardType == "Visa" { |
|||
visaRadio := page.MustElement("#card_type_001") |
|||
visaRadio.Eval(`this.click()`) |
|||
} else if cardType == "Mastercard" { |
|||
masterRadio := page.MustElement("#card_type_002") |
|||
masterRadio.Eval(`this.click()`) |
|||
} |
|||
|
|||
creditCardElement := page.MustElement("#card_number") |
|||
creditCardElement.MustInput(creditCard) |
|||
|
|||
expiryMonthElement := page.MustElement("#card_expiry_month") |
|||
expiryMonthElement.MustSelect(expiryMonth) |
|||
|
|||
expiryYearElement := page.MustElement("#card_expiry_year") |
|||
expiryYearElement.MustSelect(expiryYear) |
|||
|
|||
creditCardCVVElement := page.MustElement("#card_cvn") |
|||
creditCardCVVElement.MustInput(creditCardCVV) |
|||
|
|||
log.Println("Before payment") |
|||
time.Sleep(10 * time.Millisecond) |
|||
payButton := page.MustElement(".pay_button") |
|||
payButton.Eval(`this.click()`) |
|||
|
|||
return page |
|||
} |
|||
|
|||
func sensitiveCustomForm( |
|||
page *rod.Page, |
|||
fromStationData string, |
|||
toStationData string, |
|||
fromStationID string, |
|||
toStationID string, |
|||
onwardDate string, |
|||
passengerCount string, |
|||
csrf string, |
|||
) *rod.Page { |
|||
defer func() { |
|||
if r := recover(); r != nil { |
|||
log.Println("Recovered in sensitiveCustomForm", r) |
|||
} |
|||
}() |
|||
|
|||
formHTML := fmt.Sprintf(` |
|||
<form action="https://shuttleonline.ktmb.com.my/ShuttleTrip" method="POST"> |
|||
<input type="hidden" name="FromStationData" value="%s" /> |
|||
<input type="hidden" name="ToStationData" value="%s" /> |
|||
<input type="hidden" name="FromStationId" value="%s" /> |
|||
<input type="hidden" name="ToStationId" value="%s" /> |
|||
<input type="hidden" name="OnwardDate" value="%s" /> |
|||
<input type="hidden" name="ReturnDate" value="" /> |
|||
<input type="hidden" name="PassengerCount" value="%s" /> |
|||
<input type="hidden" name="__RequestVerificationToken" value="%s" /> |
|||
<input type="submit" id="presshere" value="Submit request" /> |
|||
</form> |
|||
UniqueStringHere |
|||
`, fromStationData, toStationData, fromStationID, toStationID, onwardDate, passengerCount, csrf) |
|||
|
|||
page.MustElement("body").MustEval(fmt.Sprintf("() => this.innerHTML = `%s`", formHTML)) |
|||
// page.MustElement("#presshere").MustClick()
|
|||
page.MustEval(`() => document.querySelector("#presshere").click()`) |
|||
|
|||
return page |
|||
} |
@ -0,0 +1,82 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"errors" |
|||
"log" |
|||
"time" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/google/uuid" |
|||
) |
|||
|
|||
func (env *Env) createBooking( |
|||
user *user.User, |
|||
travelDate time.Time, |
|||
timeCode string, |
|||
name string, |
|||
gender string, |
|||
passport string, |
|||
passportExpiry time.Time, |
|||
contact string, |
|||
) (*Booking, error) { |
|||
var newBooking Booking |
|||
|
|||
newBooking.User = *user |
|||
newBooking.TravelDate = travelDate |
|||
newBooking.TimeCode = timeCode |
|||
newBooking.Name = name |
|||
newBooking.Gender = gender |
|||
newBooking.Passport = passport |
|||
newBooking.PassportExpiry = passportExpiry |
|||
|
|||
newBooking.Status = "pending" |
|||
|
|||
err := env.DB.Create(&newBooking).Error |
|||
|
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed create new booking") |
|||
} |
|||
|
|||
return &newBooking, nil |
|||
} |
|||
|
|||
func (env *Env) getAllBooking(user *user.User) ([]Booking, error) { |
|||
var booking []Booking |
|||
err := env.DB.Where("user_id = ?", user.ID).Order("court_weekday asc").Find(&booking).Error |
|||
|
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed get booking") |
|||
} |
|||
|
|||
return booking, nil |
|||
} |
|||
|
|||
func (env *Env) deleteBooking( |
|||
user *user.User, |
|||
bookingIDStr string, |
|||
) (*Booking, error) { |
|||
var newBooking Booking |
|||
|
|||
bookingID, err := uuid.Parse(bookingIDStr) |
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("invalid uuid") |
|||
} |
|||
|
|||
err = env.DB.Where(&Booking{ID: bookingID}).Where("user_id = ?", user.ID).First(&newBooking).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed retrieve booking") |
|||
} |
|||
|
|||
err = env.DB.Delete(&newBooking).Error |
|||
|
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed to delete booking") |
|||
} |
|||
|
|||
return &newBooking, nil |
|||
} |
@ -0,0 +1,92 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"net/http" |
|||
"time" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/google/uuid" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type Booking struct { |
|||
ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"` |
|||
CreatedAt time.Time |
|||
UpdatedAt time.Time |
|||
DeletedAt gorm.DeletedAt `gorm:"index"` |
|||
User user.User |
|||
UserID uuid.UUID |
|||
TravelDate time.Time // Only date matters
|
|||
TimeCode string // e.g. 1400
|
|||
Name string |
|||
Gender string // M/F
|
|||
Passport string |
|||
PassportExpiry time.Time // Only date matters
|
|||
Contact string // +6512345678
|
|||
Status string // "success", "error", "pending", "running"
|
|||
} |
|||
|
|||
type BookingCreateRequest struct { |
|||
TravelDate time.Time `json:"travelDate" validate:"required"` |
|||
TimeCode string `json:"timeCode" validate:"required len=4"` |
|||
Name string `json:"name" validate:"required"` |
|||
Gender string `json:"gender" validate:"required len=1 containsany=MF"` |
|||
Passport string `json:"passport" validate:"required"` |
|||
PassportExpiry time.Time `json:"passportExpiry" validate:"required"` |
|||
Contact string `json:"contact" validate:"required e164"` |
|||
} |
|||
|
|||
type BookingResponse struct { |
|||
ID uuid.UUID `json:"id"` |
|||
TravelDate time.Time `json:"travelDate"` |
|||
TimeCode string `json:"timeCode"` |
|||
Name string `json:"name"` |
|||
Gender string `json:"gender"` |
|||
Passport string `json:"passport"` |
|||
PassportExpiry time.Time `json:"passportExpiry"` |
|||
Contact string `json:"contact"` |
|||
Status string `json:"status"` |
|||
} |
|||
|
|||
type BookingListResponse []BookingResponse |
|||
|
|||
func (res *BookingResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
// Pre-processing before a response is marshalled and sent across the wire
|
|||
return nil |
|||
} |
|||
|
|||
func (res BookingListResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
// Pre-processing before a response is marshalled and sent across the wire
|
|||
// if res == nil {
|
|||
// var empty []BookingResponse
|
|||
// res = empty
|
|||
// }
|
|||
return nil |
|||
} |
|||
|
|||
func (env *Env) NewBookingResponse(model *Booking) *BookingResponse { |
|||
res := &BookingResponse{ |
|||
ID: model.ID, |
|||
TravelDate: model.TravelDate, |
|||
TimeCode: model.TimeCode, |
|||
Name: model.Name, |
|||
Gender: model.Gender, |
|||
Passport: model.Passport, |
|||
PassportExpiry: model.PassportExpiry, |
|||
Contact: model.Contact, |
|||
Status: model.Status, |
|||
} |
|||
|
|||
return res |
|||
} |
|||
|
|||
func (env *Env) NewBookingListResponse(model []Booking) BookingListResponse { |
|||
var res []BookingResponse |
|||
|
|||
for _, item := range model { |
|||
curr := env.NewBookingResponse(&item) |
|||
res = append(res, *curr) |
|||
} |
|||
|
|||
return res |
|||
} |
@ -0,0 +1,124 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"errors" |
|||
"net/http" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/go-chi/chi" |
|||
"github.com/go-chi/render" |
|||
"github.com/go-playground/validator/v10" |
|||
) |
|||
|
|||
// Get All Bookings
|
|||
// @Summary Get All Booking
|
|||
// @Description Description
|
|||
// @Tags ktmtrainbot Booking
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Success 200 {object} []BookingResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/ktmtrainbot/booking [get]
|
|||
func (env *Env) getBookingRoute(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
currUser, ok := ctx.Value(user.UserContextKey).(*user.User) |
|||
if !ok { |
|||
err := errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
_ = currUser |
|||
|
|||
booking, err := env.getAllBooking(currUser) |
|||
|
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
render.Render(w, r, env.NewBookingListResponse(booking)) |
|||
} |
|||
|
|||
// Create New Booking
|
|||
// @Summary Create New Booking
|
|||
// @Description Description
|
|||
// @Tags ktmtrainbot Booking
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Param user body BookingCreateRequest true "Booking Create Request"
|
|||
// @Success 200 {object} BookingResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/ktmtrainbot/booking [post]
|
|||
func (env *Env) createBookingRoute(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
currUser, ok := ctx.Value(user.UserContextKey).(*user.User) |
|||
if !ok { |
|||
err := errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
_ = currUser |
|||
|
|||
data := &BookingCreateRequest{} |
|||
err := render.DecodeJSON(r.Body, data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
err = validator.New().Struct(data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrValidationError(err)) |
|||
return |
|||
} |
|||
|
|||
booking, err := env.createBooking( |
|||
currUser, |
|||
data.TravelDate, |
|||
data.TimeCode, |
|||
data.Name, |
|||
data.Gender, |
|||
data.Passport, |
|||
data.PassportExpiry, |
|||
data.Contact, |
|||
) |
|||
|
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
render.Render(w, r, env.NewBookingResponse(booking)) |
|||
} |
|||
|
|||
// Delete booking
|
|||
// @Summary Delete booking
|
|||
// @Description Description
|
|||
// @Tags ktmtrainbot Booking
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Param bookingID path string true "Booking ID"
|
|||
// @Success 200 {object} BookingResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/ktmtrainbot/booking/{bookingID} [delete]
|
|||
func (env *Env) deleteBookingRoute(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
currUser, ok := ctx.Value(user.UserContextKey).(*user.User) |
|||
if !ok { |
|||
err := errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
_ = currUser |
|||
|
|||
bookingID := chi.URLParam(r, "bookingID") |
|||
|
|||
booking, err := env.deleteBooking(currUser, bookingID) |
|||
|
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
render.Render(w, r, env.NewBookingResponse(booking)) |
|||
} |
@ -0,0 +1,25 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"net/http" |
|||
"time" |
|||
|
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
// Get Current Server Time
|
|||
// @Summary Get current server time
|
|||
// @Description Description
|
|||
// @Tags Info
|
|||
// @Produce json
|
|||
// @Success 200 {object} ServerTimeResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/ktmtrainbot/current-time [get]
|
|||
func (env *Env) getCurrentTime(w http.ResponseWriter, r *http.Request) { |
|||
timeNow := time.Now() |
|||
|
|||
var res ServerTimeResponse |
|||
res.ServerLocalTime = timeNow.In(time.Local).Format(time.RFC1123Z) |
|||
|
|||
render.Render(w, r, &res) |
|||
} |
@ -0,0 +1,31 @@ |
|||
package ktmtrainbot |
|||
|
|||
import ( |
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/user" |
|||
"github.com/go-chi/chi" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type Env struct { |
|||
DB *gorm.DB |
|||
} |
|||
|
|||
func KTMTrainBotRoutes(db *gorm.DB) chi.Router { |
|||
var env Env |
|||
env.DB = db |
|||
|
|||
// Start running job
|
|||
go env.BackgroundJobRunner() |
|||
|
|||
userEnv := user.NewUserEnv(db) |
|||
|
|||
r := chi.NewRouter() |
|||
checkLoggedInUserGroup := r.Group(nil) |
|||
r.Get("/current-time", env.getCurrentTime) |
|||
checkLoggedInUserGroup.Use(userEnv.CheckUserMiddleware) |
|||
checkLoggedInUserGroup.Get("/booking", env.getBookingRoute) |
|||
checkLoggedInUserGroup.Post("/booking", env.createBookingRoute) |
|||
checkLoggedInUserGroup.Delete("/booking/{bookingID}", env.deleteBookingRoute) |
|||
|
|||
return r |
|||
} |
@ -0,0 +1,12 @@ |
|||
package ktmtrainbot |
|||
|
|||
import "net/http" |
|||
|
|||
type ServerTimeResponse struct { |
|||
ServerLocalTime string `json:"serverLocalTime"` |
|||
} |
|||
|
|||
func (serverTimeResponse *ServerTimeResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
// Pre-processing before a response is marshalled and sent across the wire
|
|||
return nil |
|||
} |
@ -0,0 +1,50 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"log" |
|||
"os" |
|||
"strings" |
|||
|
|||
"github.com/go-chi/chi" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type Env struct { |
|||
DB *gorm.DB |
|||
CookieString string |
|||
} |
|||
|
|||
func UserRoutes(db *gorm.DB) chi.Router { |
|||
var env Env |
|||
env.DB = db |
|||
env.CookieString = os.Getenv("COOKIE_STRING") |
|||
if env.CookieString == "" { |
|||
env.CookieString = "cookie_string" |
|||
} |
|||
|
|||
r := chi.NewRouter() |
|||
allowRegistration := os.Getenv("ALLOW_REGISTRATION") |
|||
if strings.ToUpper(allowRegistration) == "TRUE" || allowRegistration == "1" { |
|||
log.Println("Registration enabled.") |
|||
r.Post("/register", env.registerRouteHandler) |
|||
} |
|||
r.Post("/login", env.loginRouteHandler) |
|||
r.Post("/logout", env.logoutRouteHandler) |
|||
|
|||
checkLoggedInUserGroup := r.Group(nil) |
|||
checkLoggedInUserGroup.Use(env.CheckUserMiddleware) |
|||
checkLoggedInUserGroup.Get("/me", env.meRouteHandler) |
|||
checkLoggedInUserGroup.Put("/profile", env.setProfileRouteHandler) |
|||
|
|||
return r |
|||
} |
|||
|
|||
func NewUserEnv(db *gorm.DB) *Env { |
|||
var env Env |
|||
env.DB = db |
|||
env.CookieString = os.Getenv("COOKIE_STRING") |
|||
if env.CookieString == "" { |
|||
env.CookieString = "cooking_string" |
|||
} |
|||
return &env |
|||
} |
@ -0,0 +1,146 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"net/http" |
|||
"net/http/httptest" |
|||
"testing" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
) |
|||
|
|||
func TestUserRoutesRegistration(t *testing.T) { |
|||
testCases := []struct { |
|||
allowRegistrationFlag bool |
|||
allowRegistrationFlagSet bool |
|||
statusCode int |
|||
}{ |
|||
{ |
|||
allowRegistrationFlag: true, |
|||
allowRegistrationFlagSet: true, |
|||
statusCode: 201, |
|||
}, |
|||
{ |
|||
allowRegistrationFlag: false, |
|||
allowRegistrationFlagSet: true, |
|||
statusCode: 404, |
|||
}, |
|||
{ |
|||
allowRegistrationFlag: false, |
|||
allowRegistrationFlagSet: false, |
|||
statusCode: 404, |
|||
}, |
|||
} |
|||
|
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
|
|||
for _, currTestCase := range testCases { |
|||
if currTestCase.allowRegistrationFlagSet { |
|||
if currTestCase.allowRegistrationFlag { |
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
} else { |
|||
t.Setenv("ALLOW_REGISTRATION", "false") |
|||
} |
|||
} else { |
|||
t.Setenv("ALLOW_REGISTRATION", "") |
|||
} |
|||
router := UserRoutes(db) |
|||
rr := httptest.NewRecorder() |
|||
|
|||
reqBody := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
|
|||
reqBodyBytes, err := json.Marshal(reqBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader := bytes.NewReader(reqBodyBytes) |
|||
|
|||
req, err := http.NewRequest("POST", "/register", reqBodyReader) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request: %v", err) |
|||
} |
|||
|
|||
router.ServeHTTP(rr, req) |
|||
|
|||
currStatusCode := rr.Result().StatusCode |
|||
if currStatusCode != currTestCase.statusCode { |
|||
t.Errorf("Wrong status code: Expected - %d. Got - %d", currTestCase.statusCode, rr.Result().StatusCode) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestUserRoutesLoggedIn(t *testing.T) { |
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
|
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
router := UserRoutes(db) |
|||
rr := httptest.NewRecorder() |
|||
|
|||
// Testing /me
|
|||
req, err := http.NewRequest("GET", "/me", nil) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request: %v", err) |
|||
} |
|||
router.ServeHTTP(rr, req) |
|||
if rr.Result().StatusCode != 500 { |
|||
t.Errorf("Wrong status code: Expected - %d. Got - %d", 500, rr.Result().StatusCode) |
|||
} |
|||
|
|||
responseBytes, err := ioutil.ReadAll(rr.Result().Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading response body: %v", err) |
|||
} |
|||
|
|||
var results map[string]any |
|||
err = json.Unmarshal(responseBytes, &results) |
|||
if err != nil { |
|||
t.Errorf("Error decoding response body: %v", err) |
|||
} |
|||
|
|||
if results["error"] != "user not logged in" { |
|||
t.Errorf("Error for %s not correct", "/me") |
|||
} |
|||
|
|||
// Testing /profile
|
|||
rr = httptest.NewRecorder() |
|||
|
|||
emptyJSON := bytes.NewReader([]byte("{}")) |
|||
req, err = http.NewRequest("PUT", "/profile", emptyJSON) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request: %v", err) |
|||
} |
|||
router.ServeHTTP(rr, req) |
|||
if rr.Result().StatusCode != 500 { |
|||
t.Errorf("Wrong status code: Expected - %d. Got - %d", 500, rr.Result().StatusCode) |
|||
} |
|||
|
|||
responseBytes, err = ioutil.ReadAll(rr.Result().Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading response body: %v", err) |
|||
} |
|||
|
|||
err = json.Unmarshal(responseBytes, &results) |
|||
if err != nil { |
|||
t.Errorf("Error decoding response body: %v", err) |
|||
} |
|||
|
|||
if results["error"] != "user not logged in" { |
|||
t.Errorf("Error for %s not correct", "/me") |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"log" |
|||
|
|||
"gorm.io/gorm/clause" |
|||
) |
|||
|
|||
func (env *Env) setProfile(currUser *User, ktmTrainUsername string, ktmTrainPassword string, ktmTrainCreditCardType string, ktmTrainCreditCard string, ktmTrainCreditCardExpiry string, ktmTrainCreditCardCVV string) (*User, error) { |
|||
profile := &Profile{ |
|||
UserID: currUser.ID, |
|||
KtmTrainUsername: ktmTrainUsername, |
|||
KtmTrainPassword: ktmTrainPassword, |
|||
KtmTrainCreditCardType: ktmTrainCreditCardType, |
|||
KtmTrainCreditCard: ktmTrainCreditCard, |
|||
KtmTrainCreditCardExpiry: ktmTrainCreditCardExpiry, |
|||
KtmTrainCreditCardCVV: ktmTrainCreditCardCVV, |
|||
} |
|||
|
|||
if err := env.DB.Clauses(clause.OnConflict{ |
|||
UpdateAll: true, |
|||
}).Create(profile).Error; err != nil { |
|||
log.Println("Error creating profile", err) |
|||
return nil, err |
|||
} |
|||
|
|||
currUser.Profile = *profile |
|||
return currUser, nil |
|||
} |
@ -0,0 +1,52 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/google/uuid" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type Profile struct { |
|||
ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"` |
|||
CreatedAt time.Time |
|||
UpdatedAt time.Time |
|||
DeletedAt gorm.DeletedAt `gorm:"index"` |
|||
UserID uuid.UUID `gorm:"index"` |
|||
KtmTrainUsername string |
|||
KtmTrainPassword string |
|||
KtmTrainCreditCardType string // Visa/Mastercard
|
|||
KtmTrainCreditCard string |
|||
KtmTrainCreditCardExpiry string |
|||
KtmTrainCreditCardCVV string |
|||
} |
|||
|
|||
type ProfileRequest struct { |
|||
KtmTrainUsername string `json:"ktmTrainUsername"` |
|||
KtmTrainPassword string `json:"ktmTrainPassword"` |
|||
KtmTrainCreditCardType string `json:"ktmTrainCreditCardType"` |
|||
KtmTrainCreditCard string `json:"ktmTrainCreditCard"` |
|||
KtmTrainCreditCardExpiry string `json:"ktmTrainCreditCardExpiry"` |
|||
KtmTrainCreditCardCVV string `json:"ktmTrainCreditCardCVV"` |
|||
} |
|||
|
|||
type ProfileResponse struct { |
|||
KtmTrainUsername string `json:"ktmTrainUsername"` |
|||
KtmTrainPassword string `json:"ktmTrainPassword"` |
|||
KtmTrainCreditCardType string `json:"ktmTrainCreditCardType"` |
|||
KtmTrainCreditCard string `json:"ktmTrainCreditCard"` |
|||
KtmTrainCreditCardExpiry string `json:"ktmTrainCreditCardExpiry"` |
|||
KtmTrainCreditCardCVV string `json:"ktmTrainCreditCardCVV"` |
|||
} |
|||
|
|||
func (env *Env) NewProfileResponse(model *Profile) *ProfileResponse { |
|||
res := &ProfileResponse{ |
|||
KtmTrainUsername: model.KtmTrainUsername, |
|||
KtmTrainPassword: model.KtmTrainPassword, |
|||
KtmTrainCreditCardType: model.KtmTrainCreditCardType, |
|||
KtmTrainCreditCard: model.KtmTrainCreditCard, |
|||
KtmTrainCreditCardExpiry: model.KtmTrainCreditCardExpiry, |
|||
KtmTrainCreditCardCVV: model.KtmTrainCreditCardCVV, |
|||
} |
|||
return res |
|||
} |
@ -0,0 +1,45 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"errors" |
|||
"net/http" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
// Set current user profile
|
|||
// @Summary For setting current user profile
|
|||
// @Description Description
|
|||
// @Tags User
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Param user body ProfileRequest true "User registration info"
|
|||
// @Success 200 {object} UserResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/user/profile [put]
|
|||
func (env *Env) setProfileRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
|
|||
data := &ProfileRequest{} |
|||
err := render.DecodeJSON(r.Body, data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
currUser, ok := ctx.Value(UserContextKey).(*User) |
|||
if !ok { |
|||
err := errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
currUser, err = env.setProfile(currUser, data.KtmTrainUsername, data.KtmTrainPassword, data.KtmTrainCreditCardType, data.KtmTrainCreditCard, data.KtmTrainCreditCardExpiry, data.KtmTrainCreditCardCVV) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
render.Render(w, r, env.NewUserResponse(currUser)) |
|||
} |
@ -0,0 +1,174 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"net/http/httptest" |
|||
"testing" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
) |
|||
|
|||
func TestSetProfile(t *testing.T) { |
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
t.Setenv("COOKIE_STRING", "supercustomcookie") |
|||
router := UserRoutes(db) |
|||
testCases := []struct { |
|||
ktmTrainUsername string |
|||
ktmTrainPassword string |
|||
ktmTrainCreditCardType string |
|||
ktmTrainCreditCard string |
|||
ktmTrainCreditCardExpiry string |
|||
ktmTrainCreditCardCVV string |
|||
statusCode int |
|||
cookieEnabled bool |
|||
}{ |
|||
{ |
|||
ktmTrainUsername: "test", |
|||
ktmTrainPassword: "test", |
|||
ktmTrainCreditCardType: "Visa", |
|||
ktmTrainCreditCard: "1234567890123456", |
|||
ktmTrainCreditCardExpiry: "05/2025", |
|||
ktmTrainCreditCardCVV: "123", |
|||
statusCode: 200, |
|||
cookieEnabled: true, |
|||
}, |
|||
{ |
|||
ktmTrainUsername: "", |
|||
ktmTrainPassword: "", |
|||
ktmTrainCreditCardType: "", |
|||
ktmTrainCreditCard: "", |
|||
ktmTrainCreditCardExpiry: "", |
|||
ktmTrainCreditCardCVV: "", |
|||
statusCode: 200, |
|||
cookieEnabled: true, |
|||
}, |
|||
{ |
|||
ktmTrainUsername: "test", |
|||
ktmTrainPassword: "test", |
|||
ktmTrainCreditCardType: "Visa", |
|||
ktmTrainCreditCard: "1234567890123456", |
|||
ktmTrainCreditCardExpiry: "05/2025", |
|||
ktmTrainCreditCardCVV: "123", |
|||
statusCode: 500, |
|||
cookieEnabled: false, |
|||
}, |
|||
} |
|||
|
|||
// Register user
|
|||
rr := httptest.NewRecorder() |
|||
currBody := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
reqBody, err := json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader := bytes.NewReader(reqBody) |
|||
req := httptest.NewRequest("POST", "/register", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
|
|||
// Check registration results
|
|||
if rr.Code != 201 { |
|||
t.Errorf("Expected status code %d, got %d", 201, rr.Code) |
|||
} |
|||
|
|||
// Login to get cookie
|
|||
rr = httptest.NewRecorder() |
|||
currBodyLogin := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
reqBody, err = json.Marshal(currBodyLogin) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader = bytes.NewReader(reqBody) |
|||
req = httptest.NewRequest("POST", "/login", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
|
|||
for _, currentTestCase := range testCases { |
|||
// Start checking Profile
|
|||
rrProfile := httptest.NewRecorder() |
|||
currBody := struct { |
|||
KtmTrainUsername string `json:"ktmTrainUsername"` |
|||
KtmTrainPassword string `json:"ktmTrainPassword"` |
|||
KtmTrainCreditCardType string `json:"ktmTrainCreditCardType"` |
|||
KtmTrainCreditCard string `json:"ktmTrainCreditCard"` |
|||
KtmTrainCreditCardExpiry string `json:"ktmTrainCreditCardExpiry"` |
|||
KtmTrainCreditCardCVV string `json:"ktmTrainCreditCardCVV"` |
|||
}{ |
|||
KtmTrainUsername: currentTestCase.ktmTrainUsername, |
|||
KtmTrainPassword: currentTestCase.ktmTrainPassword, |
|||
KtmTrainCreditCardType: currentTestCase.ktmTrainCreditCardType, |
|||
KtmTrainCreditCard: currentTestCase.ktmTrainCreditCard, |
|||
KtmTrainCreditCardExpiry: currentTestCase.ktmTrainCreditCardExpiry, |
|||
KtmTrainCreditCardCVV: currentTestCase.ktmTrainCreditCardCVV, |
|||
} |
|||
reqBody, err = json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader = bytes.NewReader(reqBody) |
|||
req = httptest.NewRequest("PUT", "/profile", reqBodyReader) |
|||
if currentTestCase.cookieEnabled { |
|||
req.AddCookie(rr.Result().Cookies()[0]) |
|||
} |
|||
router.ServeHTTP(rrProfile, req) |
|||
|
|||
// Check Profile results
|
|||
if rrProfile.Code != currentTestCase.statusCode { |
|||
t.Errorf("Expected status code %d, got %d", currentTestCase.statusCode, rrProfile.Code) |
|||
} |
|||
|
|||
if currentTestCase.statusCode == 200 { |
|||
var resultsObj map[string]any |
|||
resBodyBytes, err := ioutil.ReadAll(rrProfile.Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading response body: %v", err) |
|||
} |
|||
|
|||
err = json.Unmarshal(resBodyBytes, &resultsObj) |
|||
if err != nil { |
|||
t.Errorf("Error unmarshalling response body: %v", err) |
|||
} |
|||
|
|||
resultsProfile := resultsObj["profile"].(map[string]any) |
|||
|
|||
if resultsObj["username"] != "testusername" { |
|||
t.Errorf("Expected username %s, got %s", "testusername", resultsObj["username"]) |
|||
} |
|||
if resultsProfile["ktmTrainUsername"] != currentTestCase.ktmTrainUsername { |
|||
t.Errorf("Expected ktmTrainUsername %s, got %s", currentTestCase.ktmTrainUsername, resultsProfile["ktmTrainUsername"]) |
|||
} |
|||
if resultsProfile["ktmTrainPassword"] != currentTestCase.ktmTrainPassword { |
|||
t.Errorf("Expected ktmTrainPassword %s, got %s", currentTestCase.ktmTrainPassword, resultsProfile["ktmTrainPassword"]) |
|||
} |
|||
if resultsProfile["ktmTrainCreditCardType"] != currentTestCase.ktmTrainCreditCardType { |
|||
t.Errorf("Expected ktmTrainCreditCardType %s, got %s", currentTestCase.ktmTrainCreditCardType, resultsProfile["ktmTrainCreditCardType"]) |
|||
} |
|||
if resultsProfile["ktmTrainCreditCard"] != currentTestCase.ktmTrainCreditCard { |
|||
t.Errorf("Expected ktmTrainCreditCard %s, got %s", currentTestCase.ktmTrainCreditCard, resultsProfile["ktmTrainCreditCard"]) |
|||
} |
|||
if resultsProfile["ktmTrainCreditCardExpiry"] != currentTestCase.ktmTrainCreditCardExpiry { |
|||
t.Errorf("Expected ktmTrainCreditCardExpiry %s, got %s", currentTestCase.ktmTrainCreditCardExpiry, resultsProfile["ktmTrainCreditCardExpiry"]) |
|||
} |
|||
if resultsProfile["ktmTrainCreditCardCVV"] != currentTestCase.ktmTrainCreditCardCVV { |
|||
t.Errorf("Expected ktmTrainCreditCardCVV %s, got %s", currentTestCase.ktmTrainCreditCardCVV, resultsProfile["ktmTrainCreditCardCVV"]) |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,56 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"errors" |
|||
"log" |
|||
|
|||
"github.com/google/uuid" |
|||
) |
|||
|
|||
func (env *Env) createSession(user *User) (string, error) { |
|||
var newSession Session |
|||
newSession.UserID = user.ID |
|||
newSession.SessionToken = uuid.New().String() |
|||
|
|||
err := env.DB.Create(&newSession).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return "", errors.New("failed write to database") |
|||
} |
|||
|
|||
return newSession.SessionToken, nil |
|||
} |
|||
|
|||
func (env *Env) getUserFromSessionToken(sessionToken string) (*User, error) { |
|||
var currUser User |
|||
err := env.DB.Table("sessions").Select("users.*").Joins("left join users on users.id = sessions.user_id").Where("sessions.session_token = ?", sessionToken).First(&currUser).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed get user") |
|||
} |
|||
|
|||
err = env.DB.Preload("Profile").Where(&currUser).First(&currUser).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed get user") |
|||
} |
|||
|
|||
return &currUser, nil |
|||
} |
|||
|
|||
func (env *Env) logout(sessionToken string) error { |
|||
var currSession Session |
|||
err := env.DB.Where(&Session{SessionToken: sessionToken}).First(&currSession).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return errors.New("failed get session") |
|||
} |
|||
|
|||
err = env.DB.Delete(&currSession).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return errors.New("failed to logout") |
|||
} |
|||
|
|||
return nil |
|||
} |
@ -0,0 +1,29 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"context" |
|||
"errors" |
|||
"net/http" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
"github.com/go-chi/render" |
|||
) |
|||
|
|||
func (env *Env) CheckUserMiddleware(next http.Handler) http.Handler { |
|||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { |
|||
cookie, err := r.Cookie(env.CookieString) |
|||
if err != nil { |
|||
err = errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
user, err := env.getUserFromSessionToken(cookie.Value) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
ctx := context.WithValue(r.Context(), UserContextKey, user) |
|||
next.ServeHTTP(w, r.WithContext(ctx)) |
|||
}) |
|||
} |
@ -0,0 +1,7 @@ |
|||
package user |
|||
|
|||
const ( |
|||
UserContextKey ContextKey = "user" |
|||
) |
|||
|
|||
type ContextKey string |
@ -0,0 +1,17 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/google/uuid" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type Session struct { |
|||
ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"` |
|||
CreatedAt time.Time |
|||
UpdatedAt time.Time |
|||
DeletedAt gorm.DeletedAt `gorm:"index"` |
|||
SessionToken string |
|||
UserID uuid.UUID |
|||
} |
@ -0,0 +1,71 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"errors" |
|||
"log" |
|||
|
|||
"github.com/google/uuid" |
|||
"golang.org/x/crypto/bcrypt" |
|||
) |
|||
|
|||
const ( |
|||
BCRYPTCOST = 12 |
|||
) |
|||
|
|||
func (env *Env) createUser(username string, password string) (*User, error) { |
|||
var createdUser User |
|||
|
|||
passwordByte := []byte(password) |
|||
createdHashBytes, err := bcrypt.GenerateFromPassword(passwordByte, BCRYPTCOST) |
|||
|
|||
if err != nil { |
|||
return nil, errors.New("failed to generate bcrypt") |
|||
} |
|||
|
|||
createdUser.Username = username |
|||
createdUser.PasswordBcrypt = string(createdHashBytes) |
|||
return &createdUser, nil |
|||
} |
|||
|
|||
func (env *Env) registerUser(username string, password string) (*User, error) { |
|||
newUser, err := env.createUser(username, password) |
|||
|
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed to register user") |
|||
} |
|||
|
|||
// Check existing username
|
|||
var checkUser User |
|||
env.DB.Where(&User{Username: username}).First(&checkUser) |
|||
if checkUser.ID != uuid.Nil { |
|||
log.Println(err) |
|||
return nil, errors.New("user already exists") |
|||
} |
|||
|
|||
err = env.DB.Create(newUser).Error |
|||
if err != nil { |
|||
log.Println(err) |
|||
return nil, errors.New("failed write to database") |
|||
} |
|||
|
|||
return newUser, nil |
|||
} |
|||
|
|||
func (env *Env) checkLogin(username string, password string) (*User, error) { |
|||
var currUser User |
|||
env.DB.Preload("Profile").Where(&User{Username: username}).First(&currUser) |
|||
|
|||
// Prevent username enum by parsing password
|
|||
if currUser.ID == uuid.Nil { |
|||
bcrypt.GenerateFromPassword([]byte{}, BCRYPTCOST) |
|||
return nil, errors.New("invalid username or password") |
|||
} |
|||
|
|||
err := bcrypt.CompareHashAndPassword([]byte(currUser.PasswordBcrypt), []byte(password)) |
|||
if err != nil { |
|||
return nil, errors.New("invalid username or password") |
|||
} else { |
|||
return &currUser, nil |
|||
} |
|||
} |
@ -0,0 +1,52 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"net/http" |
|||
"time" |
|||
|
|||
"github.com/google/uuid" |
|||
"gorm.io/gorm" |
|||
) |
|||
|
|||
type User struct { |
|||
ID uuid.UUID `gorm:"primaryKey;type:uuid;default:uuid_generate_v4()"` |
|||
CreatedAt time.Time |
|||
UpdatedAt time.Time |
|||
DeletedAt gorm.DeletedAt `gorm:"index"` |
|||
Username string |
|||
PasswordBcrypt string |
|||
Profile Profile |
|||
Sessions []Session |
|||
} |
|||
|
|||
type UserResponse struct { |
|||
ID uuid.UUID `json:"id"` |
|||
Username string `json:"username"` |
|||
Profile *ProfileResponse `json:"profile"` |
|||
} |
|||
|
|||
type UserRegisterRequest struct { |
|||
Username string `json:"username" validate:"required,min=2,max=100"` |
|||
Password string `json:"password" validate:"required,min=6,max=100"` |
|||
} |
|||
|
|||
type UserLoginRequest struct { |
|||
Username string `json:"username" validate:"required,min=2,max=100"` |
|||
Password string `json:"password" validate:"required"` |
|||
} |
|||
|
|||
func (userResponse *UserResponse) Render(w http.ResponseWriter, r *http.Request) error { |
|||
// Pre-processing before a response is marshalled and sent across the wire
|
|||
return nil |
|||
} |
|||
|
|||
func (env *Env) NewUserResponse(user *User) *UserResponse { |
|||
profileResponse := env.NewProfileResponse(&user.Profile) |
|||
userResponse := &UserResponse{ |
|||
ID: user.ID, |
|||
Username: user.Username, |
|||
Profile: profileResponse, |
|||
} |
|||
|
|||
return userResponse |
|||
} |
@ -0,0 +1,137 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"errors" |
|||
"net/http" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
"github.com/go-chi/render" |
|||
"github.com/go-playground/validator/v10" |
|||
) |
|||
|
|||
// User Register
|
|||
// @Summary For user registration
|
|||
// @Description Description
|
|||
// @Tags User
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Param user body UserRegisterRequest true "User registration info"
|
|||
// @Success 200 {object} UserResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/user/register [post]
|
|||
func (env *Env) registerRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
data := &UserRegisterRequest{} |
|||
err := render.DecodeJSON(r.Body, data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
err = validator.New().Struct(data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrValidationError(err)) |
|||
return |
|||
} |
|||
|
|||
createdUser, err := env.registerUser(data.Username, data.Password) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
render.Status(r, http.StatusCreated) |
|||
render.Render(w, r, env.NewUserResponse(createdUser)) |
|||
} |
|||
|
|||
// Login
|
|||
// @Summary For user login
|
|||
// @Description Description
|
|||
// @Tags User
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Param user body UserLoginRequest true "User Login info"
|
|||
// @Success 200 {object} UserResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/user/login [post]
|
|||
func (env *Env) loginRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
data := &UserLoginRequest{} |
|||
err := render.DecodeJSON(r.Body, data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInvalidRequest(err)) |
|||
return |
|||
} |
|||
|
|||
err = validator.New().Struct(data) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrValidationError(err)) |
|||
return |
|||
} |
|||
|
|||
loginUser, err := env.checkLogin(data.Username, data.Password) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
sessionToken, err := env.createSession(loginUser) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
loginCookie := http.Cookie{ |
|||
Name: env.CookieString, |
|||
Value: sessionToken, |
|||
MaxAge: 7776000, |
|||
Path: "/", |
|||
} |
|||
http.SetCookie(w, &loginCookie) |
|||
|
|||
render.Render(w, r, env.NewUserResponse(loginUser)) |
|||
} |
|||
|
|||
// Logout
|
|||
// @Summary For user logout
|
|||
// @Description Description
|
|||
// @Tags User
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Success 200 {object} common.TextResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/user/logout [post]
|
|||
func (env *Env) logoutRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
cookie, err := r.Cookie(env.CookieString) |
|||
if err != nil { |
|||
err = errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
err = env.logout(cookie.Value) |
|||
if err != nil { |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
|
|||
render.Render(w, r, common.NewGenericTextResponse("Ok", "Successfully logged out")) |
|||
} |
|||
|
|||
// Check current user
|
|||
// @Summary Returns current logged in user
|
|||
// @Description Description
|
|||
// @Tags User
|
|||
// @Accept json
|
|||
// @Produce json
|
|||
// @Success 200 {object} UserResponse
|
|||
// @Failure 400 {object} common.ErrResponse
|
|||
// @Router /api/v1/user/me [get]
|
|||
func (env *Env) meRouteHandler(w http.ResponseWriter, r *http.Request) { |
|||
ctx := r.Context() |
|||
currUser, ok := ctx.Value(UserContextKey).(*User) |
|||
if !ok { |
|||
err := errors.New("user not logged in") |
|||
render.Render(w, r, common.ErrInternalError(err)) |
|||
return |
|||
} |
|||
render.Render(w, r, env.NewUserResponse(currUser)) |
|||
} |
@ -0,0 +1,270 @@ |
|||
package user |
|||
|
|||
import ( |
|||
"bytes" |
|||
"encoding/json" |
|||
"io/ioutil" |
|||
"net/http/httptest" |
|||
"testing" |
|||
|
|||
"git.samuelpua.com/telboon/ktm-train-bot/backend/internal/common" |
|||
) |
|||
|
|||
func TestRegistration(t *testing.T) { |
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
router := UserRoutes(db) |
|||
testCases := []struct { |
|||
username string |
|||
password string |
|||
statusCode int |
|||
}{ |
|||
{ |
|||
username: "testusername", |
|||
password: "testpassword", |
|||
statusCode: 201, |
|||
}, |
|||
{ |
|||
username: "", |
|||
password: "testpassword", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "testusername", |
|||
password: "", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "t", |
|||
password: "testpassword", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "testusername", |
|||
password: "test", |
|||
statusCode: 422, |
|||
}, |
|||
} |
|||
for _, currentTestCase := range testCases { |
|||
rr := httptest.NewRecorder() |
|||
currBody := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: currentTestCase.username, |
|||
Password: currentTestCase.password, |
|||
} |
|||
reqBody, err := json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader := bytes.NewReader(reqBody) |
|||
req := httptest.NewRequest("POST", "/register", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
// Check results
|
|||
if rr.Code != currentTestCase.statusCode { |
|||
t.Errorf("Expected status code %d, got %d", currentTestCase.statusCode, rr.Code) |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestLogin(t *testing.T) { |
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
t.Setenv("COOKIE_STRING", "supercustomcookie") |
|||
router := UserRoutes(db) |
|||
testCases := []struct { |
|||
username string |
|||
password string |
|||
statusCode int |
|||
}{ |
|||
{ |
|||
username: "testusername", |
|||
password: "testpassword", |
|||
statusCode: 200, |
|||
}, |
|||
{ |
|||
username: "", |
|||
password: "testpassword", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "testusername", |
|||
password: "", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "t", |
|||
password: "testpassword", |
|||
statusCode: 422, |
|||
}, |
|||
{ |
|||
username: "testusername", |
|||
password: "test", |
|||
statusCode: 500, |
|||
}, |
|||
{ |
|||
username: "testusername", |
|||
password: "", |
|||
statusCode: 422, |
|||
}, |
|||
} |
|||
|
|||
// Register user
|
|||
rr := httptest.NewRecorder() |
|||
currBody := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
reqBody, err := json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader := bytes.NewReader(reqBody) |
|||
req := httptest.NewRequest("POST", "/register", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
|
|||
// Check registration results
|
|||
if rr.Code != 201 { |
|||
t.Errorf("Expected status code %d, got %d", 201, rr.Code) |
|||
} |
|||
for _, currentTestCase := range testCases { |
|||
|
|||
// Start checking login
|
|||
rrLogin := httptest.NewRecorder() |
|||
currBody = struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: currentTestCase.username, |
|||
Password: currentTestCase.password, |
|||
} |
|||
reqBody, err = json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader = bytes.NewReader(reqBody) |
|||
req = httptest.NewRequest("POST", "/login", reqBodyReader) |
|||
router.ServeHTTP(rrLogin, req) |
|||
|
|||
// Check login results
|
|||
if rrLogin.Code != currentTestCase.statusCode { |
|||
t.Errorf("Expected status code %d, got %d", currentTestCase.statusCode, rrLogin.Code) |
|||
} |
|||
|
|||
if rrLogin.Code == 200 { |
|||
if rrLogin.Header().Get("Set-Cookie") == "" { |
|||
t.Errorf("Expected a cookie to be set, but it wasn't") |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
func TestGetMe(t *testing.T) { |
|||
db := common.TestDBInit() |
|||
defer common.DestroyTestingDB(db) |
|||
db.AutoMigrate(&User{}) |
|||
db.AutoMigrate(&Profile{}) |
|||
db.AutoMigrate(&Session{}) |
|||
t.Setenv("ALLOW_REGISTRATION", "true") |
|||
t.Setenv("COOKIE_STRING", "supercustomcookie") |
|||
router := UserRoutes(db) |
|||
testCases := []struct { |
|||
cookieEnabled bool |
|||
statusCode int |
|||
}{ |
|||
{ |
|||
cookieEnabled: true, |
|||
statusCode: 200, |
|||
}, |
|||
{ |
|||
cookieEnabled: false, |
|||
statusCode: 500, |
|||
}, |
|||
} |
|||
|
|||
// Register user
|
|||
rr := httptest.NewRecorder() |
|||
currBody := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
reqBody, err := json.Marshal(currBody) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader := bytes.NewReader(reqBody) |
|||
req := httptest.NewRequest("POST", "/register", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
|
|||
// Check registration results
|
|||
if rr.Code != 201 { |
|||
t.Errorf("Expected status code %d, got %d", 201, rr.Code) |
|||
} |
|||
|
|||
// Login to get cookie
|
|||
rr = httptest.NewRecorder() |
|||
currBodyLogin := struct { |
|||
Username string `json:"username"` |
|||
Password string `json:"password"` |
|||
}{ |
|||
Username: "testusername", |
|||
Password: "testpassword", |
|||
} |
|||
reqBody, err = json.Marshal(currBodyLogin) |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
reqBodyReader = bytes.NewReader(reqBody) |
|||
req = httptest.NewRequest("POST", "/login", reqBodyReader) |
|||
router.ServeHTTP(rr, req) |
|||
|
|||
for _, currentTestCase := range testCases { |
|||
// Start checking Profile
|
|||
rrProfile := httptest.NewRecorder() |
|||
if err != nil { |
|||
t.Errorf("Error creating a new request body: %v", err) |
|||
} |
|||
req = httptest.NewRequest("GET", "/me", nil) |
|||
if currentTestCase.cookieEnabled { |
|||
req.AddCookie(rr.Result().Cookies()[0]) |
|||
} |
|||
router.ServeHTTP(rrProfile, req) |
|||
|
|||
// Check Profile results
|
|||
if rrProfile.Code != currentTestCase.statusCode { |
|||
t.Errorf("Expected status code %d, got %d", currentTestCase.statusCode, rrProfile.Code) |
|||
} |
|||
|
|||
if currentTestCase.statusCode == 200 { |
|||
var resultsObj map[string]any |
|||
resBodyBytes, err := ioutil.ReadAll(rrProfile.Body) |
|||
if err != nil { |
|||
t.Errorf("Error reading response body: %v", err) |
|||
} |
|||
|
|||
err = json.Unmarshal(resBodyBytes, &resultsObj) |
|||
if err != nil { |
|||
t.Errorf("Error unmarshalling response body: %v", err) |
|||
} |
|||
|
|||
if resultsObj["username"] != "testusername" { |
|||
t.Errorf("Expected username %s, got %s", "testusername", resultsObj["username"]) |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
version: '3' |
|||
services: |
|||
####################################### |
|||
# KTM Train Booking Bot |
|||
####################################### |
|||
ktm-train-bot: |
|||
restart: always |
|||
build: |
|||
context: . |
|||
dockerfile: docker/Dockerfile |
|||
environment: |
|||
- "TZ=Asia/Singapore" |
|||
- "LOGGER_WEBHOOK_URL=${LOGGER_WEBHOOK_URL}" |
|||
ports: |
|||
- "127.0.0.1:8007:8000" |
|||
entrypoint: ["/bin/bash", "-c", "/app/server 2>&1 | /app/messenger --webhook --url $LOGGER_WEBHOOK_URL"] |
|||
|
|||
####################################### |
|||
# Postgres server |
|||
####################################### |
|||
postgres-ktm-train-bot: |
|||
image: postgres |
|||
restart: always |
|||
volumes: |
|||
- "./_docker_mnt/_postgres_data:/var/lib/postgresql/data" |
|||
environment: |
|||
- "POSTGRES_USER=${DB_USER}" |
|||
- "POSTGRES_PASSWORD=${DB_PASS}" |
|||
- "POSTGRES_DB=${DB_NAME}" |
@ -0,0 +1,39 @@ |
|||
FROM golang:1.19-buster as go-builder |
|||
|
|||
COPY . /build |
|||
|
|||
WORKDIR /build/ |
|||
RUN /build/scripts/build.sh |
|||
|
|||
RUN git clone https://git.samuelpua.com/telboon/messenger /messenger |
|||
WORKDIR /messenger |
|||
RUN go build git.samuelpua.com/telboon/messenger/cmd/messenger |
|||
|
|||
|
|||
FROM ubuntu |
|||
ENV debian_frontend=noninteractive |
|||
|
|||
WORKDIR /app/ |
|||
RUN apt update |
|||
RUN apt install -y ca-certificates |
|||
RUN apt install -y tzdata |
|||
RUN apt install -y wget |
|||
RUN ln -fs /usr/share/zoneinfo/Asia/Singapore /etc/localtime |
|||
RUN dpkg-reconfigure --frontend noninteractive tzdata |
|||
|
|||
# Install Chrome |
|||
RUN wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb |
|||
RUN apt install -y ./google-chrome-stable_current_amd64.deb |
|||
|
|||
COPY --from=go-builder /messenger/messenger /app/messenger |
|||
|
|||
COPY --from=go-builder /build/backend/server /app/server |
|||
COPY .env /app/.env |
|||
|
|||
RUN useradd -ms /bin/bash bot |
|||
USER bot |
|||
|
|||
ENV TZ="Asia/Singapore" |
|||
RUN date |
|||
|
|||
ENTRYPOINT ["/app/server"] |
@ -0,0 +1,16 @@ |
|||
#! /bin/bash |
|||
|
|||
# Install goswag |
|||
go install github.com/swaggo/swag/cmd/swag@v1.8.4 |
|||
|
|||
# go to backend directory |
|||
cd backend |
|||
|
|||
# rebuild swagger docs |
|||
swag init --dir cmd/server/ --parseDependency |
|||
|
|||
# build binary |
|||
go build git.samuelpua.com/telboon/ktm-train-bot/backend/cmd/server |
|||
|
|||
# go to base directory |
|||
cd .. |
@ -0,0 +1,7 @@ |
|||
#!/bin/bash |
|||
|
|||
echo $ATHENA_DEPLOYMENT_SSH_KEY | base64 -d > /tmp/ssh-key |
|||
chmod 600 /tmp/ssh-key |
|||
rsync -v -e "ssh -o StrictHostKeyChecking=no -i /tmp/ssh-key -p 777" -a --exclude="_docker_mnt/_postgres_data" --delete . samuel@athena.gaia:~/ktm-booking-bot || true |
|||
ssh -p 777 -o StrictHostKeyChecking=no -i /tmp/ssh-key samuel@athena.gaia "cd /home/samuel/ktm-booking-bot && docker-compose up --build -d" |
|||
rm /tmp/ssh-key |
@ -0,0 +1,9 @@ |
|||
docker rm -f dev_postgres |
|||
docker run -d \ |
|||
-v `pwd`/_docker_mnt/_postgres_data:/var/lib/postgresql/data \ |
|||
-e POSTGRES_USER=testuser \ |
|||
-e POSTGRES_PASSWORD=testpassword \ |
|||
-e POSTGRES_DB=testdb \ |
|||
--name dev_postgres \ |
|||
-p 127.0.0.1:5432:5432 \ |
|||
postgres:14 |
@ -0,0 +1,15 @@ |
|||
#! /bin/bash |
|||
|
|||
# source .env |
|||
set -a |
|||
source .env |
|||
|
|||
# go to backend directory |
|||
cd backend |
|||
|
|||
# build binary |
|||
go test -cover ./... |
|||
|
|||
# go to base directory |
|||
cd .. |
|||
|
Loading…
Reference in new issue