Backing up my work

This commit is contained in:
Steven Tracey 2025-07-08 23:55:20 -04:00
parent 521ce7d8af
commit ae4044e886
15 changed files with 720 additions and 0 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

12
.idea/dataSources.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="LuggageDB" uuid="a44d7006-355f-4587-b552-8545e1268664">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://mariadb.traceyclan.us:3306/luggage</jdbc-url>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/LuggageLocatorBackend.iml" filepath="$PROJECT_DIR$/.idea/LuggageLocatorBackend.iml" />
</modules>
</component>
</project>

7
.idea/sqldialects.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$/formatDB.sql" dialect="MariaDB" />
<file url="PROJECT" dialect="MariaDB" />
</component>
</project>

View File

@ -1,2 +1,13 @@
# LuggageTracker
## Envs
- PORT | 8080
- DB_USER | user
- DB_PASS | pass
- DB_HOST | 10.0.0.1
- DB_PORT | 3306
- DB_SCHEMA | luggage
- DB_CA_CERT_PATH | /etc/ssl/ca-cert.pem
- DB_CERT_PATH | /etc/ssl/client-cert.pem
- DB_KEY_PATH | /etc/ssl/private/client-key.pem
- DB_SKIP_VERIFY | true

145
db.go Normal file
View File

@ -0,0 +1,145 @@
package main
import (
"crypto/tls"
"crypto/x509"
"database/sql"
"errors"
"fmt"
"github.com/go-sql-driver/mysql"
"log"
"os"
"strconv"
"time"
)
type LDB struct {
db *sql.DB
}
var NoEntriesFoundError error = errors.New("no entries found")
func createTLSConf(skipVerify bool) tls.Config {
rootCertPool := x509.NewCertPool()
pem, err := os.ReadFile(os.Getenv("DB_CA_CERT_PATH"))
if err != nil {
log.Fatal(err)
}
if ok := rootCertPool.AppendCertsFromPEM(pem); !ok {
log.Fatal("Failed to append PEM.")
}
clientCert := make([]tls.Certificate, 0, 1)
keyPair, err := tls.LoadX509KeyPair(os.Getenv("DB_CERT_PATH"), os.Getenv("DB_KEY_PATH"))
if err != nil {
log.Fatal(err)
}
clientCert = append(clientCert, keyPair)
return tls.Config{
RootCAs: rootCertPool,
Certificates: clientCert,
InsecureSkipVerify: skipVerify,
}
}
func connect() *LDB {
var db *sql.DB
caCert := os.Getenv("DB_CA_CERT_PATH")
if len(caCert) > 0 {
skipVerify, err := strconv.ParseBool(os.Getenv("DB_SKIP_VERIFY"))
if err != nil {
log.Fatal(err)
}
tlsConf := createTLSConf(skipVerify)
err = mysql.RegisterTLSConfig("custom", &tlsConf)
if err != nil {
log.Fatalf("Error %s on RegisterTLSConfig\n", err)
return nil
}
connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?tls=custom&parseTime=true&loc=Local",
os.Getenv("DB_USER"),
os.Getenv("DB_PASS"),
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_SCHEMA"),
)
db, err = sql.Open("mysql", connStr)
if err != nil {
log.Fatalf("Error connecting to db: %s", err)
return nil
}
} else {
connStr := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true&loc=Local",
os.Getenv("DB_USER"),
os.Getenv("DB_PASS"),
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_SCHEMA"),
)
var err error
db, err = sql.Open("mysql", connStr)
if err != nil {
log.Fatal(err)
}
}
return &LDB{db}
}
func (ldb *LDB) queryUser(userName string) (u *User, newError error) {
db := ldb.db
queryStr := `
SELECT users.user_name, users.secret_codes, users.current_token, users.contact_number, users.email_address,
addresses.street1, addresses.street2, addresses.city, addresses.state, addresses.postal_code,
addresses.country, addresses.created_at, addresses.updated_at
FROM users
INNER JOIN addresses ON users.mailing_address=addresses.id
WHERE users.user_name=?;
`
stmt, err := db.Prepare(queryStr)
if err != nil {
return nil, err
}
res, err := stmt.Query(userName)
if err != nil {
return nil, err
}
defer res.Close()
if !res.Next() {
return nil, NoEntriesFoundError
}
user := new(User)
err = res.Scan(&user.UserName, &user.SecretCodes, &user.CurrentToken, &user.ContactNumber, &user.EmailAddress,
&user.MailingAddress.Street1, &user.MailingAddress.Street2, &user.MailingAddress.City,
&user.MailingAddress.State, &user.MailingAddress.PostalCode, &user.MailingAddress.Country,
&user.MailingAddress.CreatedAt, &user.MailingAddress.UpdatedAt,
)
if err != nil {
return nil, err
}
return user, nil
}
func (ldb *LDB) updateToken(userName string, token string) error {
db := ldb.db
updateStr := `
UPDATE users
SET current_token=?,token_creation=?
WHERE user_name=?;
`
stmt, err := db.Prepare(updateStr)
if err != nil {
return err
}
_, err = stmt.Exec(token, time.Now(), userName)
if err != nil {
return err
}
return nil
}

25
formatDB.sql Normal file
View File

@ -0,0 +1,25 @@
CREATE TABLE addresses (
id BIGINT NOT NULL AUTO_INCREMENT,
street1 TEXT NOT NULL ,
street2 TEXT,
city TEXT NOT NULL ,
state TEXT,
postal_code TEXT NOT NULL,
country TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
updated_at TIMESTAMP NOT NULL,
PRIMARY KEY (id)
);
CREATE TABLE users (
id BIGINT NOT NULL AUTO_INCREMENT,
user_name TEXT NOT NULL,
secret_codes TEXT NOT NULL,
current_token TEXT,
token_creation TIMESTAMP,
contact_number TEXT NOT NULL,
email_address TEXT NOT NULL,
mailing_address BIGINT NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (mailing_address) REFERENCES addresses(id)
);

39
go.mod Normal file
View File

@ -0,0 +1,39 @@
module LuggageLocatorBackend
go 1.23.2
require (
github.com/gin-contrib/cors v1.7.6
github.com/gin-gonic/gin v1.10.1
github.com/go-sql-driver/mysql v1.9.3
)
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/bytedance/sonic v1.13.3 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.26.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.0 // indirect
golang.org/x/arch v0.18.0 // indirect
golang.org/x/crypto v0.39.0 // indirect
golang.org/x/net v0.41.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.26.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

96
go.sum Normal file
View File

@ -0,0 +1,96 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/bytedance/sonic v1.13.3 h1:MS8gmaH16Gtirygw7jV91pDCN33NyMrPbN7qiYhEsF0=
github.com/bytedance/sonic v1.13.3/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
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/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY=
github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok=
github.com/gin-contrib/cors v1.7.6 h1:3gQ8GMzs1Ylpf70y8bMw4fVpycXIeX1ZemuSQIsnQQY=
github.com/gin-contrib/cors v1.7.6/go.mod h1:Ulcl+xN4jel9t1Ry8vqph23a60FwH9xVLd+3ykmTjOk=
github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k=
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
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.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
golang.org/x/arch v0.18.0 h1:WN9poc33zL4AzGxqf8VtpKUnGvMi8O9lhNyBMF/85qc=
golang.org/x/arch v0.18.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=

227
main.go Normal file
View File

@ -0,0 +1,227 @@
package main
import (
"context"
"crypto/rand"
"database/sql"
"encoding/base64"
"errors"
"fmt"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"log"
"net/http"
"strings"
"time"
)
type Address struct {
Id int64 `json:"address_id"`
Street1 string `json:"street1"`
Street2 *string `json:"street2,omitempty"`
City string `json:"city"`
State *string `json:"state,omitempty"`
PostalCode string `json:"postal_code"`
Country string `json:"country"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
type User struct {
Status int32 `json:"status"`
Id int64 `json:"user_id"`
UserName string `json:"user_name"`
SecretCodes string `json:"secret_codes"`
CurrentToken *string
ContactNumber string `json:"contact_number"`
EmailAddress string `json:"email_address"`
MailingAddress Address `json:"mailing_address"`
}
func GenerateToken(n int) (string, error) {
b := make([]byte, n)
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("reading random bytes: %w\n", err)
}
return base64.RawURLEncoding.EncodeToString(b), nil
}
func startCleanupLoop(ctx context.Context, db *sql.DB, interval, ttl time.Duration) {
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
if err := cleanupExpired(db, ttl); err != nil {
log.Printf("token cleanup error: %v\n", err)
}
}
}
}
func cleanupExpired(db *sql.DB, ttl time.Duration) error {
sqlStmt := `
DELETE FROM users
WHERE token_creation < DATE_SUB(NOW(), INTERVAL ? SECOND);
`
res, err := db.Exec(sqlStmt, int(ttl.Seconds()))
if err != nil {
return err
}
n, _ := res.RowsAffected()
log.Printf("token cleanup: deleted %d rows\n", n)
return nil
}
func main() {
db := connect()
defer db.db.Close()
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go startCleanupLoop(ctx, db.db, time.Hour, 24*time.Hour)
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"http://localhost:8080", "http://localhost:8000"},
AllowMethods: []string{"GET", "POST"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length", "Authorization"},
AllowCredentials: true,
}))
r.Static("/static", "./static")
r.StaticFile("/favicon.ico", "./static/favicon.ico")
r.GET("/", func(c *gin.Context) {
c.File("./static/index.html")
})
api := r.Group("/api")
api.GET("/luggage/:user", func(c *gin.Context) {
user, err := db.queryUser(c.Param("user"))
if err != nil {
if errors.Is(err, NoEntriesFoundError) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "User not found",
})
} else {
fmt.Printf("Error: %s\n", err)
c.AbortWithStatus(http.StatusBadRequest)
}
} else {
c.JSON(http.StatusOK, user)
}
})
api.GET("/verify/:user", func(c *gin.Context) {
user, err := db.queryUser(c.Param("user"))
if err != nil {
if errors.Is(err, NoEntriesFoundError) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"status": 404,
"user": "",
"error": "User not found",
})
} else {
fmt.Printf("Error: %s\n", err)
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"status": 500,
"user": "",
"error": err.Error(),
})
}
} else {
codes := strings.Split(user.SecretCodes, "'")
responded := false
for _, code := range codes {
codeHeader := c.GetHeader("Authorization")
reqCode := strings.Split(codeHeader, " ")
if strings.Compare(code, reqCode[len(reqCode)-1]) == 0 {
token, err := GenerateToken(16)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": 500,
"user": "",
"error": err.Error(),
})
responded = true
break
} else {
err = db.updateToken(user.UserName, token)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"status": 500,
"user": "",
"error": err.Error(),
})
responded = true
break
} else {
user.Status = 200
c.JSON(http.StatusOK, gin.H{
"status": 200,
"user": user.UserName,
"error": "",
"token": token,
})
responded = true
break
}
}
}
}
if !responded {
c.JSON(http.StatusNotFound, gin.H{
"status": "404",
"user": "",
"error": "User not found",
})
}
}
})
api.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.GET("/u/:user/info", func(c *gin.Context) {
user, err := db.queryUser(c.Param("user"))
if user.CurrentToken == nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"status": 403,
"error": "Unauthorized",
})
} else if strings.Compare(c.Query("token"), *user.CurrentToken) == 0 {
if err != nil {
if errors.Is(err, NoEntriesFoundError) {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{
"error": "User not found",
})
} else {
fmt.Printf("Error: %s\n", err)
c.AbortWithStatus(http.StatusBadRequest)
}
} else {
c.JSON(http.StatusOK, user)
}
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"status": 403,
"user": "",
"error": "Unauthorized",
})
}
})
err := r.Run()
if err != nil {
log.Fatal(err)
}
}

BIN
static/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

BIN
static/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

29
static/index.html Normal file
View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Luggage Tracker</title>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<header class="header">
<img class="header-icon" src="/static/icon.png" alt="icon"/>
<h1 class="title">Luggage Tracker</h1>
<div class="spacer"></div>
</header>
<div class="container">
<div class="content">
<h3>Registration</h3>
<p>Contact Steven Tracey
<a class="contact-link" href="mailto:steven@nevets.tech?subject=QR%20Generator%20-%20(Your%20Name)&body=Name%3A%0APhone%20Number%3A%0AEmail%20Address%3A%0AMailing%20Address%3A%0AStreet%3A%0ACity%3A%0AZip%3A%0AState%3A%0ACountry%3A">via email</a>
to get your QR code</p>
</div>
</div>
<footer class="footer">
<p class="footer-text">Made hastily by <a class="footer-text" href="https://www.linkedin.com/in/steven-tracey18/">Steven Tracey</a></p>
</footer>
</body>
</html>

104
static/style.css Normal file
View File

@ -0,0 +1,104 @@
:root {
--dark: #1f363dff;
--mid-dark: #40798cff;
--mid: #70a9a1ff;
--mid-light: #9ec1a3ff;
--light: #cfe0c3ff;
font-family: Verdana,sans-serif;
}
body {
margin: 0;
}
.header {
display: flex;
align-items: center; /* vertical centering */
justify-content: space-between; /* icon left, spacer right */
background-color: var(--mid-dark);
padding: 0 1rem;
height: 80px;
}
.spacer {
flex: 0 0 64px; /* or your icons width */
}
.header-icon {
display: block; /* remove inline whitespace */
flex: 0 0 64px; /* or your icons width */
width: 15vw; /* fill the 64px container */
height: auto; /* keep aspect ratio */
max-height: 80px; /* never exceed 80px bar height */
}
.title {
flex: 1; /* take up all the space between */
text-align: center; /* center text within that space */
color: var(--light);
}
.footer {
background-color: var(--dark);
display: flex;
height: 80px;
}
.footer-text {
display: inherit;
align-content: center;
justify-content: center;
margin: auto;
background-color: inherit;
border: none;
outline: none;
text-align: center;
color: white;
}
.footer-text a {
cursor: pointer;
text-decoration: none;
color: var(--mid-light);
padding-left: 5px;
}
.footer-text a:hover {
color: var(--mid);
}
.contact-link {
cursor: pointer;
color: var(--mid-dark);
text-decoration: none;
}
.contact-link a:hover {
color: var(--mid);
}
.container {
padding: 20vh 0 calc(80vh - 303px) 0;
background-image: linear-gradient(
to bottom right,
var(--mid-light),
var(--mid-dark)
);
}
.content {
margin: 0 auto;
width: 90vw; /* takes 90% of viewport (or container) width */
max-width: 800px; /* but never wider than 800px */
padding: 1.5rem; /* give it some breathing room inside */
background: white;
border-radius: 5px;
}
.content h3 {
text-align: center;
}
.content p {
text-align: center;
}