Feat(ktm-booking): Initial commit
Some checks failed
ktm-booking-bot/ktm-booking-bot/pipeline/head Something is wrong with the build of this commit
Some checks failed
ktm-booking-bot/ktm-booking-bot/pipeline/head Something is wrong with the build of this commit
This commit is contained in:
31
backend/cmd/server/health.go
Normal file
31
backend/cmd/server/health.go
Normal file
@@ -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)
|
||||
}
|
||||
39
backend/cmd/server/health_test.go
Normal file
39
backend/cmd/server/health_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
64
backend/cmd/server/main.go
Normal file
64
backend/cmd/server/main.go
Normal file
@@ -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)
|
||||
}
|
||||
609
backend/docs/docs.go
Normal file
609
backend/docs/docs.go
Normal file
@@ -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)
|
||||
}
|
||||
585
backend/docs/swagger.json
Normal file
585
backend/docs/swagger.json
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
387
backend/docs/swagger.yaml
Normal file
387
backend/docs/swagger.yaml
Normal file
@@ -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"
|
||||
54
backend/go.mod
Normal file
54
backend/go.mod
Normal file
@@ -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
|
||||
)
|
||||
304
backend/go.sum
Normal file
304
backend/go.sum
Normal file
@@ -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=
|
||||
131
backend/internal/common/database.go
Normal file
131
backend/internal/common/database.go
Normal file
@@ -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
|
||||
}
|
||||
56
backend/internal/common/errresponse.go
Normal file
56
backend/internal/common/errresponse.go
Normal file
@@ -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(),
|
||||
}
|
||||
}
|
||||
24
backend/internal/common/textresponse.go
Normal file
24
backend/internal/common/textresponse.go
Normal file
@@ -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,
|
||||
}
|
||||
}
|
||||
477
backend/internal/ktmtrainbot/backgroundbookingjob.go
Normal file
477
backend/internal/ktmtrainbot/backgroundbookingjob.go
Normal file
@@ -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
|
||||
}
|
||||
82
backend/internal/ktmtrainbot/bookingcontroller.go
Normal file
82
backend/internal/ktmtrainbot/bookingcontroller.go
Normal file
@@ -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
|
||||
}
|
||||
92
backend/internal/ktmtrainbot/bookingmodel.go
Normal file
92
backend/internal/ktmtrainbot/bookingmodel.go
Normal file
@@ -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
|
||||
}
|
||||
124
backend/internal/ktmtrainbot/bookingroute.go
Normal file
124
backend/internal/ktmtrainbot/bookingroute.go
Normal file
@@ -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))
|
||||
}
|
||||
25
backend/internal/ktmtrainbot/getcurrenttime.go
Normal file
25
backend/internal/ktmtrainbot/getcurrenttime.go
Normal file
@@ -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)
|
||||
}
|
||||
31
backend/internal/ktmtrainbot/main.go
Normal file
31
backend/internal/ktmtrainbot/main.go
Normal file
@@ -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
|
||||
}
|
||||
12
backend/internal/ktmtrainbot/servertimeresponsemodel.go
Normal file
12
backend/internal/ktmtrainbot/servertimeresponsemodel.go
Normal file
@@ -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
|
||||
}
|
||||
50
backend/internal/user/main.go
Normal file
50
backend/internal/user/main.go
Normal file
@@ -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
|
||||
}
|
||||
146
backend/internal/user/main_test.go
Normal file
146
backend/internal/user/main_test.go
Normal file
@@ -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")
|
||||
}
|
||||
}
|
||||
29
backend/internal/user/profilecontroller.go
Normal file
29
backend/internal/user/profilecontroller.go
Normal file
@@ -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
|
||||
}
|
||||
52
backend/internal/user/profilemodel.go
Normal file
52
backend/internal/user/profilemodel.go
Normal file
@@ -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
|
||||
}
|
||||
45
backend/internal/user/profileroute.go
Normal file
45
backend/internal/user/profileroute.go
Normal file
@@ -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))
|
||||
}
|
||||
174
backend/internal/user/profileroute_test.go
Normal file
174
backend/internal/user/profileroute_test.go
Normal file
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
backend/internal/user/sessioncontroller.go
Normal file
56
backend/internal/user/sessioncontroller.go
Normal file
@@ -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
|
||||
}
|
||||
29
backend/internal/user/sessionmiddleware.go
Normal file
29
backend/internal/user/sessionmiddleware.go
Normal file
@@ -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))
|
||||
})
|
||||
}
|
||||
7
backend/internal/user/sessionmiddlewarecontext.go
Normal file
7
backend/internal/user/sessionmiddlewarecontext.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package user
|
||||
|
||||
const (
|
||||
UserContextKey ContextKey = "user"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
17
backend/internal/user/sessionmodel.go
Normal file
17
backend/internal/user/sessionmodel.go
Normal file
@@ -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
|
||||
}
|
||||
71
backend/internal/user/usercontroller.go
Normal file
71
backend/internal/user/usercontroller.go
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
52
backend/internal/user/usermodel.go
Normal file
52
backend/internal/user/usermodel.go
Normal file
@@ -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
|
||||
}
|
||||
137
backend/internal/user/userroute.go
Normal file
137
backend/internal/user/userroute.go
Normal file
@@ -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))
|
||||
}
|
||||
270
backend/internal/user/userroute_test.go
Normal file
270
backend/internal/user/userroute_test.go
Normal file
@@ -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"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user