Compare commits
10 Commits
df53fb7990
...
65e87129a6
| Author | SHA1 | Date | |
|---|---|---|---|
| 65e87129a6 | |||
| eb222d8677 | |||
| b69b4e25bf | |||
| 45e568a824 | |||
| 391becf14e | |||
| afb402223f | |||
| cb3199966c | |||
| db8c61da56 | |||
| c15256a9d4 | |||
| 5b32cd34a2 |
19
.vscode/launch.json
vendored
@ -13,10 +13,27 @@
|
||||
{
|
||||
"name": "Attach to Remote",
|
||||
"type": "go",
|
||||
"debugAdapter": "dlv-dap",
|
||||
"request": "attach",
|
||||
"mode": "remote",
|
||||
"port": 4321,
|
||||
"host": "45.76.84.7",
|
||||
"host": "losandesgames.com",
|
||||
// "substitutePath": [
|
||||
// { "from": "${workspaceFolder}", "to": "/home/alfheim/linux_amd64" },
|
||||
// ]
|
||||
},
|
||||
{
|
||||
"name": "Launch Remote",
|
||||
"type": "go",
|
||||
"debugAdapter": "dlv-dap",
|
||||
"request": "launch",
|
||||
"mode": "exec",
|
||||
"program": "/home/alfheim/linux_amd64/alfheim-website",
|
||||
"port": 4321,
|
||||
"host": "losandesgames.com",
|
||||
// "substitutePath": [
|
||||
// { "from": "${workspaceFolder}", "to": "/home/alfheim/linux_amd64" },
|
||||
// ]
|
||||
}
|
||||
]
|
||||
}
|
||||
31
Makefile
@ -1,32 +1,29 @@
|
||||
svn_revision = $(shell svn info --show-item revision)
|
||||
|
||||
#LDFLAGS = "-X main.version=$(svn_revision)"
|
||||
|
||||
build:
|
||||
@echo "Building the website..."
|
||||
rm -rf bin
|
||||
mkdir bin
|
||||
mkdir bin/static
|
||||
mkdir bin/ui
|
||||
go build -o bin/alfheim-website
|
||||
go build -ldflags=$(LDFLAGS) -o bin/alfheim-website ./cmd/web
|
||||
cp -r ui bin
|
||||
cp -r static bin
|
||||
cp favicon.ico bin
|
||||
cp Caddyfile bin
|
||||
mkdir bin/linux_amd64
|
||||
mkdir bin/linux_amd64/static
|
||||
mkdir bin/linux_amd64/ui
|
||||
GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/alfheim-website
|
||||
GOOS=linux GOARCH=amd64 go build -ldflags=$(LDFLAGS) -o bin/linux_amd64/alfheim-website ./cmd/web
|
||||
cp -r ui bin/linux_amd64
|
||||
cp -r static bin/linux_amd64
|
||||
cp favicon.ico bin/linux_amd64
|
||||
cp Caddyfile bin/linux_amd64
|
||||
|
||||
run:
|
||||
@echo "Running the website..."
|
||||
go run ./*.go
|
||||
go run ./cmd/web/*.go
|
||||
|
||||
.PHONY: deploy
|
||||
deploy:
|
||||
.PHONY: production/deploy
|
||||
production/deploy:
|
||||
rsync -rP --delete bin/linux_amd64 alfheim@alfheimgame.com:/home/alfheim
|
||||
|
||||
.PHONY: service
|
||||
service:
|
||||
rsync -P alfheim-website.service alfheim@alfheimgame.com:/home/alfheim
|
||||
rsync -P remote/production/alfheim-website.service alfheim@alfheimgame.com:/home/alfheim
|
||||
ssh -t root@alfheimgame.com 'mv /home/alfheim/alfheim-website.service /etc/systemd/system && systemctl enable alfheim-website && systemctl restart alfheim-website'
|
||||
|
||||
.PHONY: production/connect
|
||||
production/connect:
|
||||
ssh alfheim@losandesgames.com
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
email vicenteferrarismith@gmail.com
|
||||
}
|
||||
|
||||
alfheimgame.com {
|
||||
reverse_proxy localhost:4000
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
{
|
||||
email vicenteferrarismith@gmail.com
|
||||
}
|
||||
|
||||
alfheimgame.com {
|
||||
reverse_proxy localhost:4000
|
||||
}
|
||||
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M480-120v-80h280v-560H480v-80h280q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 173 B |
@ -1,202 +0,0 @@
|
||||
html {
|
||||
/*background: no-repeat url(/static/image.png);
|
||||
background-size: cover;
|
||||
background-position: center;*/
|
||||
height: 100vh;
|
||||
background-color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: #3475CB;
|
||||
font-family: "Vollkorn";
|
||||
color: white;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 900px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbuttons a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.maintitle {
|
||||
margin: 0px;
|
||||
font-size: 6rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.maintitle {
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loginbutton {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
border: 12px solid;
|
||||
border-image-source: url("/static/panel-000.png");
|
||||
border-image-slice: 12 fill;
|
||||
padding-top: 15px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 15px;
|
||||
padding-left: 20px;
|
||||
color: black;
|
||||
/*box-shadow: 10px 10px 5px lightblue;*/
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 8px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
padding: 30px 40px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: inline-block;
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
padding: 30px 40px;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper h1 {
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper .input-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-box input {
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: 2px solid rgba(255, 255, 255, .2);
|
||||
border-radius: 25px;
|
||||
padding: 10px 45px 10px 20px
|
||||
}
|
||||
|
||||
.input-box input::placeholder {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.wrapper .input-error {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgba(240, 0, 0, .8);
|
||||
}
|
||||
|
||||
.input-error input {
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: 2px solid rgba(255, 0, 0, .2);
|
||||
border-radius: 25px;
|
||||
padding: 10px 45px 10px 20px
|
||||
}
|
||||
|
||||
.input-error input::placeholder {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.login-btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wrapper .btn {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.wrapper .register-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
footer {
|
||||
/*margin-top: auto;*/
|
||||
width: 100%;
|
||||
background-color: #132123;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="account-wrapper">
|
||||
<div>Username: {{.Account.Username}}</div>
|
||||
<div>First name: {{.Account.Firstname}}</div>
|
||||
<div>Last name: {{.Account.Lastname}}</div>
|
||||
<div>Color: {{.Account.Color}}</div>
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<form action="/deleteaccount" method="post">
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Delete Account" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="/managebilling">
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Manage Billing" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Alfheim</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="A handcrafted economy and politics MMO." />
|
||||
<meta name="author" content="Vicente Ferrari Smith" />
|
||||
<meta name="keywords" content="Alfheim, indie, video game, mmo, colony, colony simulator, vicente ferrari smith, game, economy, politics, alfheim" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Vollkorn";
|
||||
src: url(/static/Vollkorn-VariableFont_wght.ttf);
|
||||
}
|
||||
</style>
|
||||
<link href="/static/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/"><h1 class="maintitle">Alfheim</h1></a>
|
||||
<div class="navbuttons">
|
||||
{{if not .ActiveSubscription}}
|
||||
<a href="/subscribe"><div class="loginbutton"><strong>Subscribe</strong></div></a>
|
||||
{{end}}
|
||||
|
||||
{{if .AuthenticatedUser}}
|
||||
<a href="/account"><div class="loginbutton"><strong>Account</strong></div></a>
|
||||
<a href="/logout"><div class="loginbutton"><strong>Log out</strong></div></a>
|
||||
{{else}}
|
||||
<a href="/login"><div class="loginbutton"><strong>Log in</strong><img src="/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg" alt=""/></div></a>
|
||||
<a href="/register"><div class="loginbutton"><strong>Register</strong></div></a>
|
||||
{{end}}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{template "body" .}}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Alfheim © 2025, Vicente Ferrari Smith, Los Andes Games, Vienna, Austria. All rights reserved.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,9 +0,0 @@
|
||||
{{define "body"}}
|
||||
<p>Alfheim is a game about humanity: our own will, and our place in the world at large.</p>
|
||||
<p>Alfheim is an MMO where you—the player—administer a medieval settlement of elves.
|
||||
In an infinite world, and with thousands of other players, you will have to design an efficient command economy, if you wish to be able to compete with your enemies... Or your friends?</p>
|
||||
<br \>
|
||||
<video playsinline loop autoplay muted><source src="/static/video.mp4" type="video/mp4">Your browser does not support the video tag.</video>
|
||||
<br \>
|
||||
<img src="/static/image.png" alt="Alfheim" style="height: 100%; width: 100%; object-fit: contain">
|
||||
{{end}}
|
||||
@ -1,39 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Log in</h1>
|
||||
|
||||
{{with .FormErrors.generic}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
{{with .FormErrors.username}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div {{if .FormErrors.username}}class="input-error"{{else}}class="input-box"{{end}}>
|
||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{{with .FormErrors.password}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div {{if .FormErrors.password}}class="input-error"{{else}}class="input-box"{{end}}>
|
||||
<input type="password" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Log in" class="btn">
|
||||
</div>
|
||||
<br />
|
||||
<a href="/forgotten">Have you forgotten your password?</a>
|
||||
|
||||
<div class="register-link">
|
||||
<p>Don't have an account? <a href="/register">Register</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,11 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Log out</h1>
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Log out" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,45 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Register</h1>
|
||||
|
||||
{{with .FormErrors.username}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div class="input-box" {{with .FormErrors.username}}class="input-error"{{end}}>
|
||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
|
||||
<div class="input-box">
|
||||
<input type="email" id="email" name="email" placeholder="Email" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="input-box">
|
||||
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="input-box">
|
||||
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{{with .FormErrors.password}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div class="input-box" {{with .FormErrors.password}}class="input-error"{{end}}>
|
||||
<input type="password" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Register" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,8 +0,0 @@
|
||||
{{define "body"}}
|
||||
{{range .Prices}}
|
||||
<div class="wrapper">
|
||||
{{$price := divide .UnitAmountDecimal 100}}
|
||||
{{.Currency}} {{printf "%.2f" $price}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@ -1,5 +0,0 @@
|
||||
{{define "body"}}
|
||||
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
|
||||
<stripe-pricing-table pricing-table-id="prctbl_1PIaJpKUHKCjyTmcy1ONKQiT" publishable-key="pk_test_51PGebgKUHKCjyTmcyuhj7C5lfHfKFLCxRJ2opoqxL3mGEHSRaMvOHYKKgX4MdFfESW78dssjyunboUcFhg3LTwmn005PmzVIXw" customer-session-client-secret={{.ClientSecret}}>
|
||||
</stripe-pricing-table>
|
||||
{{end}}
|
||||
@ -16,13 +16,15 @@ import (
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/stripe/stripe-go/v78"
|
||||
"github.com/stripe/stripe-go/v78/billingportal/session"
|
||||
"github.com/stripe/stripe-go/v78/customer"
|
||||
"github.com/stripe/stripe-go/v78/customersession"
|
||||
"github.com/stripe/stripe-go/v78/price"
|
||||
"github.com/stripe/stripe-go/v78/subscription"
|
||||
"github.com/stripe/stripe-go/v78/webhook"
|
||||
"github.com/stripe/stripe-go/v83"
|
||||
"github.com/stripe/stripe-go/v83/billingportal/session"
|
||||
"github.com/stripe/stripe-go/v83/customer"
|
||||
"github.com/stripe/stripe-go/v83/customersession"
|
||||
"github.com/stripe/stripe-go/v83/price"
|
||||
"github.com/stripe/stripe-go/v83/subscription"
|
||||
"github.com/stripe/stripe-go/v83/webhook"
|
||||
|
||||
"alfheimgame.com/alfheim/pkg/models"
|
||||
)
|
||||
|
||||
//import "strconv"
|
||||
@ -30,7 +32,7 @@ import (
|
||||
type TemplateData struct {
|
||||
AuthenticatedUser int32
|
||||
FormErrors map[string]string
|
||||
Account Account
|
||||
Account models.Account
|
||||
Prices []stripe.Price
|
||||
ClientSecret string
|
||||
ActiveSubscription bool
|
||||
@ -40,7 +42,7 @@ func favicon(w http.ResponseWriter, r *http.Request) {
|
||||
http.ServeFile(w, r, "favicon.ico")
|
||||
}
|
||||
|
||||
func authenticated_user(w http.ResponseWriter, r *http.Request) int32 {
|
||||
func authenticatedUser(w http.ResponseWriter, r *http.Request) int32 {
|
||||
session, err := store.Get(r, "id")
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -77,12 +79,12 @@ func home(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
account, _ := users.GetAccount(id)
|
||||
|
||||
active_subscription := subscriptions.HasActiveSubscription(id)
|
||||
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/index.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/index.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
@ -106,7 +108,7 @@ func home(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func login(w http.ResponseWriter, r *http.Request) {
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/login.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/login.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Fatal(err)
|
||||
@ -141,7 +143,7 @@ func login(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(errors) > 0 {
|
||||
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
@ -151,9 +153,9 @@ func login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
id, err := users.Authenticate(username, password)
|
||||
if err == ErrInvalidCredentials {
|
||||
if err == models.ErrInvalidCredentials {
|
||||
errors["generic"] = "Email or Password is incorrect"
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
@ -172,13 +174,13 @@ func login(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func logout(w http.ResponseWriter, r *http.Request) {
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/logout.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/logout.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
@ -207,13 +209,13 @@ func logout(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func register(w http.ResponseWriter, r *http.Request) {
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/register.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/register.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
account, _ := users.GetAccount(id)
|
||||
|
||||
// if err != nil {
|
||||
@ -231,7 +233,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
case http.MethodPost:
|
||||
account := Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")}
|
||||
account := models.Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")}
|
||||
|
||||
errors := make(map[string]string)
|
||||
|
||||
@ -249,7 +251,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if len(errors) > 0 {
|
||||
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
@ -258,7 +260,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
_, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email)
|
||||
|
||||
if err == ErrDuplicateEmail || err == ErrDuplicateUsername {
|
||||
if err == models.ErrDuplicateEmail || err == models.ErrDuplicateUsername {
|
||||
|
||||
} else if err != nil {
|
||||
|
||||
@ -279,7 +281,7 @@ func account(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Fatal(err)
|
||||
//}
|
||||
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
@ -289,7 +291,7 @@ func account(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
//log.Println(id, account)
|
||||
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/account.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/account.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
@ -312,9 +314,9 @@ func account(w http.ResponseWriter, r *http.Request) {
|
||||
// }
|
||||
}
|
||||
|
||||
func deleteaccount(w http.ResponseWriter, r *http.Request) {
|
||||
func deleteAccount(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
@ -335,7 +337,7 @@ func deleteaccount(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func subscribe(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticated_user(w, r)
|
||||
id := authenticatedUser(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
@ -361,7 +363,7 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
|
||||
},
|
||||
}
|
||||
|
||||
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html")
|
||||
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/html/base.html", "ui/html/subscribe.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
@ -375,8 +377,8 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticated_user(w, r)
|
||||
func subscribeStripe(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticatedUser(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
@ -397,7 +399,7 @@ func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html")
|
||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/subscribe_stripe.html")
|
||||
if err != nil {
|
||||
http.Error(w, "Internal Server Error", 500)
|
||||
log.Println(err)
|
||||
@ -410,8 +412,8 @@ func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func managebilling(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticated_user(w, r)
|
||||
func manageBilling(w http.ResponseWriter, r *http.Request) {
|
||||
id := authenticatedUser(w, r)
|
||||
account, err := users.GetAccount(id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -419,7 +421,7 @@ func managebilling(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
params := &stripe.BillingPortalSessionParams{
|
||||
Customer: stripe.String(account.StripeID),
|
||||
ReturnURL: stripe.String("http://localhost:8080/account"),
|
||||
ReturnURL: stripe.String("https://alfheimgame.com/account"),
|
||||
}
|
||||
|
||||
result, err := session.New(params)
|
||||
@ -508,11 +510,11 @@ func webhooks(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) error {
|
||||
func handle_checkout_session_completed(checkoutSession stripe.CheckoutSession) error {
|
||||
//toprint, _ := json.MarshalIndent(checkoutSession, "", " ")
|
||||
//log.Println(string(toprint))
|
||||
|
||||
subscription, err := subscription.Get(checkoutsession.Subscription.ID, nil)
|
||||
subscription, err := subscription.Get(checkoutSession.Subscription.ID, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
@ -540,19 +542,19 @@ func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) e
|
||||
// status = paused
|
||||
//}
|
||||
|
||||
subscriptions.Insert(checkoutsession.Customer.ID, subscription.ID, checkoutsession.ID, subscription.Status)
|
||||
subscriptions.Insert(checkoutSession.Customer.ID, subscription.ID, checkoutSession.ID, subscription.Status)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func handle_payment_method_attached(paymentmethod stripe.PaymentMethod) error {
|
||||
func handlePaymentMethodAttached(paymentMethod stripe.PaymentMethod) error {
|
||||
//toprint, _ := json.MarshalIndent(setupintent, "", " ")
|
||||
//log.Println(string(toprint))
|
||||
|
||||
// make this the new customer's default payment method
|
||||
params := &stripe.CustomerParams{}
|
||||
params.DefaultSource = &paymentmethod.ID
|
||||
/*result*/ _, err := customer.Update(paymentmethod.Customer.ID, params)
|
||||
params.DefaultSource = &paymentMethod.ID
|
||||
/*result*/ _, err := customer.Update(paymentMethod.Customer.ID, params)
|
||||
//log.Println(result)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
@ -5,48 +5,64 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"database/sql"
|
||||
"regexp"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/stripe/stripe-go/v78"
|
||||
|
||||
"github.com/stripe/stripe-go/v83"
|
||||
|
||||
"alfheimgame.com/alfheim/pkg/models/postgresql"
|
||||
)
|
||||
|
||||
var users *Usermodel
|
||||
var subscriptions *SubscriptionModel
|
||||
var users *postgresql.AccountModel
|
||||
var subscriptions *postgresql.SubscriptionModel
|
||||
|
||||
var key = []byte("super-secret-key")
|
||||
var store = sessions.NewCookieStore(key)
|
||||
|
||||
var emailrx = regexp.MustCompile("/^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/")
|
||||
|
||||
var version string
|
||||
|
||||
func main() {
|
||||
addr := flag.String("addr", "127.0.0.1:8080", "HTTP network addr")
|
||||
prodaddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr")
|
||||
prodAddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr")
|
||||
//prodaddr := flag.String("prodaddr", "45.76.84.7:443", "HTTP network addr")
|
||||
|
||||
production := flag.Bool("production", false, "Whether to use production port and TLS")
|
||||
|
||||
displayVersion := flag.Bool("version", false, "Display version and exit")
|
||||
|
||||
_ = addr
|
||||
flag.Parse()
|
||||
|
||||
if *displayVersion {
|
||||
fmt.Printf("Version: %s\n", version)
|
||||
return
|
||||
}
|
||||
|
||||
log.Println("Hello, Sailor!")
|
||||
|
||||
stripe.Key = "sk_test_51PGebgKUHKCjyTmc97rfDPcvew6EhqDz2qp3U7XoAMIilAU9IVo2NO4P7ylkTvbBafFVr94trha1VYY32jRWMw2K00Yq7YJXFf"
|
||||
|
||||
store.MaxAge(0)
|
||||
|
||||
db, err := sql.Open("postgres", "postgres://elves_database:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@80.240.25.87/elves_database")
|
||||
db, err := pgx.Connect(context.Background(), "postgres://alfheim:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com/alfheim")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
users = &Usermodel{db}
|
||||
subscriptions = &SubscriptionModel{db}
|
||||
defer db.Close(context.Background())
|
||||
|
||||
users = &postgresql.AccountModel{DB: db}
|
||||
subscriptions = &postgresql.SubscriptionModel{DB: db}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
@ -59,7 +75,7 @@ func main() {
|
||||
//accounts := make([]*Account, 0)
|
||||
//for rows.Next() {
|
||||
// acc := new(Account)
|
||||
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Color)
|
||||
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Colour)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
@ -74,7 +90,7 @@ func main() {
|
||||
// log.Println(acc)
|
||||
//}
|
||||
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("ui/static"))))
|
||||
|
||||
mux.HandleFunc("/favicon.ico", favicon)
|
||||
|
||||
@ -82,10 +98,10 @@ func main() {
|
||||
mux.HandleFunc("/login", login)
|
||||
mux.HandleFunc("/logout", logout)
|
||||
mux.HandleFunc("/register", register)
|
||||
mux.HandleFunc("/account", require_authenticated_user(account))
|
||||
mux.HandleFunc("/deleteaccount", require_authenticated_user(deleteaccount))
|
||||
mux.HandleFunc("/subscribe", require_authenticated_user(subscribe_stripe))
|
||||
mux.HandleFunc("/managebilling", require_authenticated_user(managebilling))
|
||||
mux.HandleFunc("/account", requireAuthenticatedUser(account))
|
||||
mux.HandleFunc("/deleteaccount", requireAuthenticatedUser(deleteAccount))
|
||||
mux.HandleFunc("/subscribe", requireAuthenticatedUser(subscribeStripe))
|
||||
mux.HandleFunc("/managebilling", requireAuthenticatedUser(manageBilling))
|
||||
mux.HandleFunc("/webhook", webhooks)
|
||||
|
||||
if *production {
|
||||
@ -106,8 +122,8 @@ func main() {
|
||||
// }
|
||||
|
||||
// log.Fatal(server.ListenAndServeTLS("", ""))
|
||||
log.Fatal(http.ListenAndServe(*prodaddr, log_request(secure_headers(mux))))
|
||||
log.Fatal(http.ListenAndServe(*prodAddr, logRequest(secureHeaders(mux))))
|
||||
} else {
|
||||
log.Fatal(http.ListenAndServe(*addr, log_request(secure_headers(mux))))
|
||||
log.Fatal(http.ListenAndServe(*addr, logRequest(secureHeaders(mux))))
|
||||
}
|
||||
}
|
||||
@ -5,7 +5,7 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func secure_headers(next http.Handler) http.Handler {
|
||||
func secureHeaders(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||
w.Header().Set("X-Frame-Options", "deny")
|
||||
@ -16,7 +16,7 @@ func secure_headers(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func log_request(next http.Handler) http.Handler {
|
||||
func logRequest(next http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL)
|
||||
|
||||
@ -26,12 +26,12 @@ func log_request(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
|
||||
func require_authenticated_user(next http.HandlerFunc) http.HandlerFunc {
|
||||
func requireAuthenticatedUser(next http.HandlerFunc) http.HandlerFunc {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// If the user is not authenticated, redirect them to the login page and
|
||||
// return from the middleware chain so that no subsequent handlers in
|
||||
// the chain are executed.
|
||||
if authenticated_user(w, r) == 0 {
|
||||
if authenticatedUser(w, r) == 0 {
|
||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
BIN
favicon.ico
|
Before Width: | Height: | Size: 83 KiB |
20
go.mod
@ -1,18 +1,22 @@
|
||||
module alfheimgame.com/alfheim
|
||||
|
||||
go 1.22.2
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/alexedwards/argon2id v1.0.0
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/lib/pq v1.10.9
|
||||
github.com/stripe/stripe-go/v78 v78.7.0
|
||||
golang.org/x/crypto v0.33.0
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/stripe/stripe-go/v83 v83.0.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||
golang.org/x/net v0.21.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/text v0.22.0 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
)
|
||||
|
||||
53
go.sum
@ -1,56 +1,63 @@
|
||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
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/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||
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/stripe/stripe-go/v78 v78.7.0 h1:TdTkzBn0wB0ntgOI74YHpvsNyHPBijX83n4ljsjXh6o=
|
||||
github.com/stripe/stripe-go/v78 v78.7.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ=
|
||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stripe/stripe-go/v83 v83.0.0 h1:00HYu/n80zH6ugy88bWI5sBLbJZ7WmhCXCRQ1N1tuqI=
|
||||
github.com/stripe/stripe-go/v83 v83.0.0/go.mod h1:nRyDcLrJtwPPQUnKAFs9Bt1NnQvNhNiF6V19XHmPISE=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
|
||||
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -58,20 +65,18 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
||||
310
models.go
@ -1,310 +0,0 @@
|
||||
//
|
||||
// Created by vfs on 02.05.2024.
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
//import "golang.org/x/crypto/bcrypt"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
_ "github.com/lib/pq"
|
||||
"github.com/stripe/stripe-go/v78"
|
||||
"github.com/stripe/stripe-go/v78/customer"
|
||||
)
|
||||
|
||||
var ErrNoRecord = errors.New("no matching record found")
|
||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
var ErrDuplicateEmail = errors.New("duplicate email")
|
||||
var ErrDuplicateUsername = errors.New("duplicate username")
|
||||
|
||||
type Account struct {
|
||||
ID int32
|
||||
Username string
|
||||
Password []byte
|
||||
Color int32
|
||||
Firstname string
|
||||
Lastname string
|
||||
Email string
|
||||
Created time.Time
|
||||
StripeID string
|
||||
}
|
||||
|
||||
type SubscriptionStatus string
|
||||
|
||||
const (
|
||||
incomplete SubscriptionStatus = "incomplete"
|
||||
incomplete_expired SubscriptionStatus = "incomplete_expired"
|
||||
trialing SubscriptionStatus = "trialing"
|
||||
active SubscriptionStatus = "active"
|
||||
past_due SubscriptionStatus = "past_due"
|
||||
canceled SubscriptionStatus = "canceled"
|
||||
unpaid SubscriptionStatus = "unpaid"
|
||||
paused SubscriptionStatus = "paused"
|
||||
)
|
||||
|
||||
type Subscription struct {
|
||||
ID int32
|
||||
AccountID int32
|
||||
StripeSubscriptionID string
|
||||
StripeCheckoutID string
|
||||
Status SubscriptionStatus
|
||||
}
|
||||
|
||||
type Usermodel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
type SubscriptionModel struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {
|
||||
|
||||
//hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//log.Println(hashedpassword)
|
||||
stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id`
|
||||
|
||||
var insertid int32
|
||||
|
||||
row := m.DB.QueryRow(stmt, username, string(hashedpassword), firstname, lastname, email)
|
||||
if row.Err() != nil {
|
||||
log.Println(row.Err())
|
||||
return 0, row.Err()
|
||||
}
|
||||
|
||||
err = row.Scan(&insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
params := &stripe.CustomerParams{
|
||||
Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)),
|
||||
Email: stripe.String(email),
|
||||
}
|
||||
customer, err := customer.New(params)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`
|
||||
|
||||
//log.Println(customer.ID, insertid)
|
||||
|
||||
_, err = m.DB.Exec(stmt, customer.ID, insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return insertid, nil
|
||||
}
|
||||
|
||||
func (m *Usermodel) Delete(id int32) error {
|
||||
account, err := users.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if account.StripeID != "" {
|
||||
/*result*/ _, err := customer.Del(account.StripeID, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
//log.Println(result)
|
||||
}
|
||||
|
||||
stmt := `DELETE FROM accounts WHERE id = $1`
|
||||
|
||||
_, err = m.DB.Exec(stmt, id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Usermodel) GetAccount(id int32) (Account, error) {
|
||||
if id == 0 {
|
||||
return Account{}, ErrNoRecord
|
||||
}
|
||||
stmt := `SELECT id, username, password, color, firstname, lastname, email, created, stripe_id FROM accounts WHERE id = $1`
|
||||
row := m.DB.QueryRow(stmt, id)
|
||||
|
||||
var account Account
|
||||
err := row.Scan(&account.ID, &account.Username, &account.Password, &account.Color, &account.Firstname, &account.Lastname, &account.Email, &account.Created, &account.StripeID)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return Account{}, sql.ErrNoRows
|
||||
} else if err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *Usermodel) Authenticate(username string, password string) (int32, error) {
|
||||
var id int32
|
||||
var hashedpassword string
|
||||
row := m.DB.QueryRow("SELECT id, password FROM accounts WHERE username = $1", username)
|
||||
err := row.Scan(&id, &hashedpassword)
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, ErrInvalidCredentials
|
||||
}
|
||||
|
||||
match, err := argon2id.ComparePasswordAndHash(password, hashedpassword)
|
||||
if !match {
|
||||
return 0, ErrInvalidCredentials
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (m *Usermodel) ExistsAccount(id int32) bool {
|
||||
var exists bool
|
||||
stmt := `SELECT EXISTS(SELECT 1 FROM accounts WHERE id = $1)`
|
||||
row := m.DB.QueryRow(stmt, id)
|
||||
if row.Err() != nil {
|
||||
log.Println(row.Err())
|
||||
}
|
||||
row.Scan(&exists)
|
||||
|
||||
//log.Println(exists)
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) Insert(stripeid string, stripesubscriptionid string, stripecheckoutid string, status stripe.SubscriptionStatus) (int32, error) {
|
||||
var id int32
|
||||
stmt := `SELECT id FROM accounts WHERE stripe_id = $1`
|
||||
|
||||
row := m.DB.QueryRow(stmt, stripeid)
|
||||
if row.Err() != nil {
|
||||
log.Println(row.Err())
|
||||
return 0, row.Err()
|
||||
}
|
||||
|
||||
err := row.Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stmt = `INSERT INTO subscriptions (account_id, stripe_subscription_id, stripe_checkout_id, status) VALUES ($1, $2, $3, $4::subscription_status) RETURNING id`
|
||||
|
||||
var insertid int32
|
||||
|
||||
row = m.DB.QueryRow(stmt, id, string(stripesubscriptionid), string(stripecheckoutid), string(status))
|
||||
if row.Err() != nil {
|
||||
log.Println(row.Err())
|
||||
return 0, row.Err()
|
||||
}
|
||||
|
||||
err = row.Scan(&insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return insertid, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) Delete(id int32) error {
|
||||
stmt := `DELETE FROM accounts WHERE id = $1`
|
||||
|
||||
_, err := m.DB.Exec(stmt, id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) GetSubscription(id int32) (Subscription, error) {
|
||||
if id == 0 {
|
||||
return Subscription{}, ErrNoRecord
|
||||
}
|
||||
|
||||
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE id = $1`
|
||||
row := m.DB.QueryRow(stmt, id)
|
||||
|
||||
var subscription Subscription
|
||||
err := row.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
|
||||
|
||||
if err == sql.ErrNoRows {
|
||||
return Subscription{}, sql.ErrNoRows
|
||||
} else if err != nil {
|
||||
return Subscription{}, err
|
||||
}
|
||||
|
||||
//log.Println(subscription.Status)
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) GetSubscriptionsFromAccount(accountid int32) ([]Subscription, error) {
|
||||
if accountid == 0 {
|
||||
return nil, ErrNoRecord
|
||||
}
|
||||
|
||||
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE account_id = $1`
|
||||
rows, err := m.DB.Query(stmt, accountid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var subscriptions []Subscription
|
||||
for rows.Next() {
|
||||
var subscription Subscription
|
||||
err := rows.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, sql.ErrNoRows
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, subscription)
|
||||
}
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) HasActiveSubscription(accountid int32) bool {
|
||||
subscriptions, err := m.GetSubscriptionsFromAccount(accountid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range subscriptions {
|
||||
if v.Status == active {
|
||||
return true
|
||||
} else if v.Status == trialing {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
48
pkg/models/models.go
Normal file
@ -0,0 +1,48 @@
|
||||
//
|
||||
// Created by vfs on 02.05.2024.
|
||||
//
|
||||
|
||||
package models
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var ErrNoRecord = errors.New("no matching record found")
|
||||
var ErrInvalidCredentials = errors.New("invalid credentials")
|
||||
var ErrDuplicateEmail = errors.New("duplicate email")
|
||||
var ErrDuplicateUsername = errors.New("duplicate username")
|
||||
|
||||
type Account struct {
|
||||
ID int32
|
||||
Username string
|
||||
Password []byte
|
||||
Colour int32
|
||||
Firstname string
|
||||
Lastname string
|
||||
Email string
|
||||
Created time.Time
|
||||
StripeID string
|
||||
}
|
||||
|
||||
type SubscriptionStatus string
|
||||
|
||||
const (
|
||||
Incomplete SubscriptionStatus = "incomplete"
|
||||
IncompleteExpired SubscriptionStatus = "incomplete_expired"
|
||||
Trialing SubscriptionStatus = "trialing"
|
||||
Active SubscriptionStatus = "active"
|
||||
PastDue SubscriptionStatus = "past_due"
|
||||
Canceled SubscriptionStatus = "canceled"
|
||||
Unpaid SubscriptionStatus = "unpaid"
|
||||
Paused SubscriptionStatus = "paused"
|
||||
)
|
||||
|
||||
type Subscription struct {
|
||||
ID int32
|
||||
AccountID int32
|
||||
StripeSubscriptionID string
|
||||
StripeCheckoutID string
|
||||
Status SubscriptionStatus
|
||||
}
|
||||
138
pkg/models/postgresql/accounts.go
Normal file
@ -0,0 +1,138 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"github.com/alexedwards/argon2id"
|
||||
"github.com/stripe/stripe-go/v83"
|
||||
"github.com/stripe/stripe-go/v83/customer"
|
||||
|
||||
"alfheimgame.com/alfheim/pkg/models"
|
||||
)
|
||||
|
||||
type AccountModel struct {
|
||||
DB *pgx.Conn
|
||||
}
|
||||
|
||||
func (m *AccountModel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {
|
||||
|
||||
//hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
|
||||
hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
//log.Println(hashedpassword)
|
||||
stmt := `INSERT INTO accounts (username, password, firstname, lastname, email) VALUES ($1, $2, $3, $4, $5) RETURNING id`
|
||||
|
||||
var insertid int32
|
||||
|
||||
err = m.DB.QueryRow(context.Background(), stmt, username, string(hashedpassword), firstname, lastname, email).Scan(&insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
params := &stripe.CustomerParams{
|
||||
Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)),
|
||||
Email: stripe.String(email),
|
||||
}
|
||||
customer, err := customer.New(params)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`
|
||||
|
||||
_, err = m.DB.Exec(context.Background(), stmt, customer.ID, insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return insertid, nil
|
||||
}
|
||||
|
||||
func (m *AccountModel) Delete(id int32) error {
|
||||
account, err := m.GetAccount(id)
|
||||
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
if account.StripeID != "" {
|
||||
/*result*/ _, err := customer.Del(account.StripeID, nil)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
//log.Println(result)
|
||||
}
|
||||
|
||||
stmt := `DELETE FROM accounts WHERE id = $1`
|
||||
|
||||
_, err = m.DB.Exec(context.Background(), stmt, id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AccountModel) GetAccount(id int32) (models.Account, error) {
|
||||
if id == 0 {
|
||||
return models.Account{}, models.ErrNoRecord
|
||||
}
|
||||
|
||||
var account models.Account
|
||||
stmt := `SELECT id, username, password, colour, firstname, lastname, email, created, stripe_id FROM accounts WHERE id = $1`
|
||||
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&account.ID, &account.Username, &account.Password, &account.Colour, &account.Firstname, &account.Lastname, &account.Email, &account.Created, &account.StripeID)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return models.Account{}, pgx.ErrNoRows
|
||||
} else if err != nil {
|
||||
return models.Account{}, err
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (m *AccountModel) Authenticate(username string, password string) (int32, error) {
|
||||
var id int32
|
||||
var hashedpassword string
|
||||
stmt := `SELECT id, password FROM accounts WHERE username = $1`
|
||||
err := m.DB.QueryRow(context.Background(), stmt, username).Scan(&id, &hashedpassword)
|
||||
if err == pgx.ErrNoRows {
|
||||
return 0, models.ErrInvalidCredentials
|
||||
}
|
||||
|
||||
match, err := argon2id.ComparePasswordAndHash(password, hashedpassword)
|
||||
if !match {
|
||||
return 0, models.ErrInvalidCredentials
|
||||
} else if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return id, nil
|
||||
}
|
||||
|
||||
func (m *AccountModel) ExistsAccount(id int32) bool {
|
||||
var exists bool
|
||||
stmt := `SELECT EXISTS(SELECT 1 FROM accounts WHERE id = $1)`
|
||||
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&exists)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
|
||||
//log.Println(exists)
|
||||
|
||||
return exists
|
||||
}
|
||||
116
pkg/models/postgresql/subscriptions.go
Normal file
@ -0,0 +1,116 @@
|
||||
package postgresql
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
|
||||
"log"
|
||||
|
||||
"github.com/stripe/stripe-go/v83"
|
||||
|
||||
"alfheimgame.com/alfheim/pkg/models"
|
||||
)
|
||||
|
||||
type SubscriptionModel struct {
|
||||
DB *pgx.Conn
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) Insert(stripeid string, stripesubscriptionid string, stripecheckoutid string, status stripe.SubscriptionStatus) (int32, error) {
|
||||
var id int32
|
||||
stmt := `SELECT id FROM accounts WHERE stripe_id = $1`
|
||||
|
||||
err := m.DB.QueryRow(context.Background(), stmt, stripeid).Scan(&id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
stmt = `INSERT INTO subscriptions (account_id, stripe_subscription_id, stripe_checkout_id, status) VALUES ($1, $2, $3, $4::subscription_status) RETURNING id`
|
||||
|
||||
var insertid int32
|
||||
|
||||
err = m.DB.QueryRow(context.Background(), stmt, id, string(stripesubscriptionid), string(stripecheckoutid), string(status)).Scan(&insertid)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return insertid, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) Delete(id int32) error {
|
||||
stmt := `DELETE FROM accounts WHERE id = $1`
|
||||
|
||||
_, err := m.DB.Exec(context.Background(), stmt, id)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) GetSubscription(id int32) (models.Subscription, error) {
|
||||
if id == 0 {
|
||||
return models.Subscription{}, models.ErrNoRecord
|
||||
}
|
||||
|
||||
var subscription models.Subscription
|
||||
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE id = $1`
|
||||
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return models.Subscription{}, pgx.ErrNoRows
|
||||
} else if err != nil {
|
||||
return models.Subscription{}, err
|
||||
}
|
||||
|
||||
return subscription, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) GetSubscriptionsFromAccount(accountid int32) ([]models.Subscription, error) {
|
||||
if accountid == 0 {
|
||||
return nil, models.ErrNoRecord
|
||||
}
|
||||
|
||||
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE account_id = $1`
|
||||
rows, err := m.DB.Query(context.Background(), stmt, accountid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var subscriptions []models.Subscription
|
||||
for rows.Next() {
|
||||
var subscription models.Subscription
|
||||
err := rows.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, pgx.ErrNoRows
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subscriptions = append(subscriptions, subscription)
|
||||
}
|
||||
|
||||
return subscriptions, nil
|
||||
}
|
||||
|
||||
func (m *SubscriptionModel) HasActiveSubscription(accountid int32) bool {
|
||||
subscriptions, err := m.GetSubscriptionsFromAccount(accountid)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range subscriptions {
|
||||
switch v.Status {
|
||||
case models.Active:
|
||||
return true
|
||||
case models.Trialing:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@ -15,8 +15,8 @@ CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||
EnvironmentFile=/etc/environment
|
||||
WorkingDirectory=/home/alfheim/linux_amd64
|
||||
#ExecStart=/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheim-website -- -production
|
||||
ExecStart=/home/alfheim/linux_amd64/alfheim-website -production
|
||||
ExecStart=/home/alfheim/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheim-website -- -production
|
||||
#ExecStart=/home/alfheim/linux_amd64/alfheim-website -production
|
||||
|
||||
Restart=on-failure
|
||||
RestartSec=5
|
||||
BIN
static/image.png
|
Before Width: | Height: | Size: 25 KiB |
@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M480-120v-80h280v-560H480v-80h280q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 173 B |
202
static/style.css
@ -1,202 +0,0 @@
|
||||
html {
|
||||
/*background: no-repeat url(/static/image.png);
|
||||
background-size: cover;
|
||||
background-position: center;*/
|
||||
height: 100vh;
|
||||
background-color: black;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background-color: #3475CB;
|
||||
font-family: "Vollkorn";
|
||||
color: white;
|
||||
margin-top: 0px;
|
||||
margin-bottom: 0px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
max-width: 900px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
|
||||
}
|
||||
|
||||
nav {
|
||||
padding-left: 8px;
|
||||
padding-right: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbuttons a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.maintitle {
|
||||
margin: 0px;
|
||||
font-size: 6rem;
|
||||
color: white;
|
||||
}
|
||||
|
||||
@media (max-width: 1080px) {
|
||||
.maintitle {
|
||||
font-size: 4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loginbutton {
|
||||
align-self: center;
|
||||
display: flex;
|
||||
border: 12px solid;
|
||||
border-image-source: url("/static/panel-000.png");
|
||||
border-image-slice: 12 fill;
|
||||
padding-top: 15px;
|
||||
padding-right: 20px;
|
||||
padding-bottom: 15px;
|
||||
padding-left: 20px;
|
||||
color: black;
|
||||
/*box-shadow: 10px 10px 5px lightblue;*/
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 8px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100%;
|
||||
max-height: 100%;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.account-wrapper {
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
padding: 30px 40px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: inline-block;
|
||||
background: transparent;
|
||||
border: 2px solid white;
|
||||
backdrop-filter: blur(20px);
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
|
||||
border-radius: 15px;
|
||||
padding: 30px 40px;
|
||||
width: 80%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper h1 {
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.wrapper .input-box {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.input-box input {
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: 2px solid rgba(255, 255, 255, .2);
|
||||
border-radius: 25px;
|
||||
padding: 10px 45px 10px 20px
|
||||
}
|
||||
|
||||
.input-box input::placeholder {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.wrapper .input-error {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.error {
|
||||
color: rgba(240, 0, 0, .8);
|
||||
}
|
||||
|
||||
.input-error input {
|
||||
box-sizing: border-box;
|
||||
background: transparent;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
outline: none;
|
||||
border: 2px solid rgba(255, 0, 0, .2);
|
||||
border-radius: 25px;
|
||||
padding: 10px 45px 10px 20px
|
||||
}
|
||||
|
||||
.input-error input::placeholder {
|
||||
color: red;
|
||||
}
|
||||
|
||||
.login-btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.wrapper .btn {
|
||||
width: 100%;
|
||||
outline: none;
|
||||
border: none;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.wrapper .register-link {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
footer {
|
||||
/*margin-top: auto;*/
|
||||
width: 100%;
|
||||
background-color: #132123;
|
||||
color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:link {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:visited {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.wrapper a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
BIN
static/video.mp4
@ -1,22 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="account-wrapper">
|
||||
<div>Username: {{.Account.Username}}</div>
|
||||
<div>First name: {{.Account.Firstname}}</div>
|
||||
<div>Last name: {{.Account.Lastname}}</div>
|
||||
<div>Color: {{.Account.Color}}</div>
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
<form action="/deleteaccount" method="post">
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Delete Account" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form method="POST" action="/managebilling">
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Manage Billing" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
48
ui/base.html
@ -1,48 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Alfheim</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="description" content="A handcrafted economy and politics MMO." />
|
||||
<meta name="author" content="Vicente Ferrari Smith" />
|
||||
<meta name="keywords" content="Alfheim, indie, video game, mmo, colony, colony simulator, vicente ferrari smith, game, economy, politics, alfheim" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<style>
|
||||
@font-face {
|
||||
font-family: "Vollkorn";
|
||||
src: url(/static/Vollkorn-VariableFont_wght.ttf);
|
||||
}
|
||||
</style>
|
||||
<link href="/static/style.css" rel="stylesheet" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<nav>
|
||||
<a href="/"><h1 class="maintitle">Alfheim</h1></a>
|
||||
<div class="navbuttons">
|
||||
{{if not .ActiveSubscription}}
|
||||
<a href="/subscribe"><div class="loginbutton"><strong>Subscribe</strong></div></a>
|
||||
{{end}}
|
||||
|
||||
{{if .AuthenticatedUser}}
|
||||
<a href="/account"><div class="loginbutton"><strong>Account</strong></div></a>
|
||||
<a href="/logout"><div class="loginbutton"><strong>Log out</strong></div></a>
|
||||
{{else}}
|
||||
<a href="/login"><div class="loginbutton"><strong>Log in</strong><img src="/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg" alt=""/></div></a>
|
||||
<a href="/register"><div class="loginbutton"><strong>Register</strong></div></a>
|
||||
{{end}}
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{{template "body" .}}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Alfheim © 2025, Vicente Ferrari Smith, Los Andes Games, Vienna, Austria. All rights reserved.
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
@ -3,7 +3,7 @@
|
||||
<div>Username: {{.Account.Username}}</div>
|
||||
<div>First name: {{.Account.Firstname}}</div>
|
||||
<div>Last name: {{.Account.Lastname}}</div>
|
||||
<div>Color: {{.Account.Color}}</div>
|
||||
<div>Colour: {{.Account.Colour}}</div>
|
||||
</div>
|
||||
|
||||
<div class="wrapper">
|
||||
@ -1,9 +0,0 @@
|
||||
{{define "body"}}
|
||||
<p>Alfheim is a game about humanity: our own will, and our place in the world at large.</p>
|
||||
<p>Alfheim is an MMO where you—the player—administer a medieval settlement of elves.
|
||||
In an infinite world, and with thousands of other players, you will have to design an efficient command economy, if you wish to be able to compete with your enemies... Or your friends?</p>
|
||||
<br \>
|
||||
<video playsinline loop autoplay muted><source src="/static/video.mp4" type="video/mp4">Your browser does not support the video tag.</video>
|
||||
<br \>
|
||||
<img src="/static/image.png" alt="Alfheim" style="height: 100%; width: 100%; object-fit: contain">
|
||||
{{end}}
|
||||
@ -1,39 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Log in</h1>
|
||||
|
||||
{{with .FormErrors.generic}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
{{with .FormErrors.username}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div {{if .FormErrors.username}}class="input-error"{{else}}class="input-box"{{end}}>
|
||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{{with .FormErrors.password}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div {{if .FormErrors.password}}class="input-error"{{else}}class="input-box"{{end}}>
|
||||
<input type="password" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Log in" class="btn">
|
||||
</div>
|
||||
<br />
|
||||
<a href="/forgotten">Have you forgotten your password?</a>
|
||||
|
||||
<div class="register-link">
|
||||
<p>Don't have an account? <a href="/register">Register</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,11 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Log out</h1>
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Log out" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
@ -1,45 +0,0 @@
|
||||
{{define "body"}}
|
||||
<div class="wrapper">
|
||||
<form method="POST">
|
||||
<h1>Register</h1>
|
||||
|
||||
{{with .FormErrors.username}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div class="input-box" {{with .FormErrors.username}}class="input-error"{{end}}>
|
||||
<input type="text" id="username" name="username" placeholder="Username" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
|
||||
<div class="input-box">
|
||||
<input type="email" id="email" name="email" placeholder="Email" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="input-box">
|
||||
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="input-box">
|
||||
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
{{with .FormErrors.password}}
|
||||
<label class="error">{{.}}</label>
|
||||
{{end}}
|
||||
|
||||
<div class="input-box" {{with .FormErrors.password}}class="input-error"{{end}}>
|
||||
<input type="password" id="password" name="password" placeholder="Password" required>
|
||||
</div>
|
||||
<br />
|
||||
|
||||
<div class="login-btn-wrapper">
|
||||
<input type="submit" value="Register" class="btn">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 173 B After Width: | Height: | Size: 173 B |
@ -1,8 +0,0 @@
|
||||
{{define "body"}}
|
||||
{{range .Prices}}
|
||||
<div class="wrapper">
|
||||
{{$price := divide .UnitAmountDecimal 100}}
|
||||
{{.Currency}} {{printf "%.2f" $price}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
@ -1,5 +0,0 @@
|
||||
{{define "body"}}
|
||||
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
|
||||
<stripe-pricing-table pricing-table-id="prctbl_1PIaJpKUHKCjyTmcy1ONKQiT" publishable-key="pk_test_51PGebgKUHKCjyTmcyuhj7C5lfHfKFLCxRJ2opoqxL3mGEHSRaMvOHYKKgX4MdFfESW78dssjyunboUcFhg3LTwmn005PmzVIXw" customer-session-client-secret={{.ClientSecret}}>
|
||||
</stripe-pricing-table>
|
||||
{{end}}
|
||||