Compare commits
No commits in common. "65e87129a6a33943b7792aba249b2c8fb65b0fc6" and "df53fb7990e1a3e1c08e2f7803a24c005b1e1a1d" have entirely different histories.
65e87129a6
...
df53fb7990
19
.vscode/launch.json
vendored
@ -13,27 +13,10 @@
|
|||||||
{
|
{
|
||||||
"name": "Attach to Remote",
|
"name": "Attach to Remote",
|
||||||
"type": "go",
|
"type": "go",
|
||||||
"debugAdapter": "dlv-dap",
|
|
||||||
"request": "attach",
|
"request": "attach",
|
||||||
"mode": "remote",
|
"mode": "remote",
|
||||||
"port": 4321,
|
"port": 4321,
|
||||||
"host": "losandesgames.com",
|
"host": "45.76.84.7",
|
||||||
// "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,29 +1,32 @@
|
|||||||
svn_revision = $(shell svn info --show-item revision)
|
|
||||||
|
|
||||||
#LDFLAGS = "-X main.version=$(svn_revision)"
|
|
||||||
|
|
||||||
build:
|
build:
|
||||||
@echo "Building the website..."
|
@echo "Building the website..."
|
||||||
rm -rf bin
|
rm -rf bin
|
||||||
mkdir bin
|
mkdir bin
|
||||||
go build -ldflags=$(LDFLAGS) -o bin/alfheim-website ./cmd/web
|
mkdir bin/static
|
||||||
|
mkdir bin/ui
|
||||||
|
go build -o bin/alfheim-website
|
||||||
cp -r ui bin
|
cp -r ui bin
|
||||||
|
cp -r static bin
|
||||||
|
cp favicon.ico bin
|
||||||
cp Caddyfile bin
|
cp Caddyfile bin
|
||||||
mkdir bin/linux_amd64
|
mkdir bin/linux_amd64
|
||||||
GOOS=linux GOARCH=amd64 go build -ldflags=$(LDFLAGS) -o bin/linux_amd64/alfheim-website ./cmd/web
|
mkdir bin/linux_amd64/static
|
||||||
|
mkdir bin/linux_amd64/ui
|
||||||
|
GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/alfheim-website
|
||||||
cp -r ui bin/linux_amd64
|
cp -r ui bin/linux_amd64
|
||||||
|
cp -r static bin/linux_amd64
|
||||||
|
cp favicon.ico bin/linux_amd64
|
||||||
cp Caddyfile bin/linux_amd64
|
cp Caddyfile bin/linux_amd64
|
||||||
|
|
||||||
run:
|
run:
|
||||||
@echo "Running the website..."
|
@echo "Running the website..."
|
||||||
go run ./cmd/web/*.go
|
go run ./*.go
|
||||||
|
|
||||||
.PHONY: production/deploy
|
.PHONY: deploy
|
||||||
production/deploy:
|
deploy:
|
||||||
rsync -rP --delete bin/linux_amd64 alfheim@alfheimgame.com:/home/alfheim
|
rsync -rP --delete bin/linux_amd64 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
|
.PHONY: service
|
||||||
production/connect:
|
service:
|
||||||
ssh alfheim@losandesgames.com
|
rsync -P 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'
|
||||||
|
|||||||
@ -15,8 +15,8 @@ CapabilityBoundingSet=CAP_NET_BIND_SERVICE
|
|||||||
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
AmbientCapabilities=CAP_NET_BIND_SERVICE
|
||||||
EnvironmentFile=/etc/environment
|
EnvironmentFile=/etc/environment
|
||||||
WorkingDirectory=/home/alfheim/linux_amd64
|
WorkingDirectory=/home/alfheim/linux_amd64
|
||||||
ExecStart=/home/alfheim/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheim-website -- -production
|
#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/linux_amd64/alfheim-website -production
|
||||||
|
|
||||||
Restart=on-failure
|
Restart=on-failure
|
||||||
RestartSec=5
|
RestartSec=5
|
||||||
7
bin/Caddyfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
email vicenteferrarismith@gmail.com
|
||||||
|
}
|
||||||
|
|
||||||
|
alfheimgame.com {
|
||||||
|
reverse_proxy localhost:4000
|
||||||
|
}
|
||||||
BIN
bin/alfheim-website
Executable file
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
7
bin/linux_amd64/Caddyfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
email vicenteferrarismith@gmail.com
|
||||||
|
}
|
||||||
|
|
||||||
|
alfheimgame.com {
|
||||||
|
reverse_proxy localhost:4000
|
||||||
|
}
|
||||||
BIN
bin/linux_amd64/alfheim-website
Executable file
BIN
bin/linux_amd64/favicon.ico
Executable file
|
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 |
@ -3,7 +3,7 @@
|
|||||||
<div>Username: {{.Account.Username}}</div>
|
<div>Username: {{.Account.Username}}</div>
|
||||||
<div>First name: {{.Account.Firstname}}</div>
|
<div>First name: {{.Account.Firstname}}</div>
|
||||||
<div>Last name: {{.Account.Lastname}}</div>
|
<div>Last name: {{.Account.Lastname}}</div>
|
||||||
<div>Colour: {{.Account.Colour}}</div>
|
<div>Color: {{.Account.Color}}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
BIN
bin/static/Vollkorn-VariableFont_wght.ttf
Normal file
BIN
bin/static/image.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
1
bin/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 279 B |
BIN
bin/static/panel-000.png
Normal file
|
After Width: | Height: | Size: 173 B |
202
bin/static/style.css
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
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
bin/static/video.mp4
Normal file
22
bin/ui/account.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{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
bin/ui/base.html
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!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>
|
||||||
9
bin/ui/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{{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}}
|
||||||
39
bin/ui/login.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{{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}}
|
||||||
11
bin/ui/logout.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{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}}
|
||||||
45
bin/ui/register.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{{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}}
|
||||||
8
bin/ui/subscribe.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{{define "body"}}
|
||||||
|
{{range .Prices}}
|
||||||
|
<div class="wrapper">
|
||||||
|
{{$price := divide .UnitAmountDecimal 100}}
|
||||||
|
{{.Currency}} {{printf "%.2f" $price}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
5
bin/ui/subscribe_stripe.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{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}}
|
||||||
BIN
favicon.ico
Executable file
|
After Width: | Height: | Size: 83 KiB |
20
go.mod
@ -1,22 +1,18 @@
|
|||||||
module alfheimgame.com/alfheim
|
module alfheimgame.com/alfheim
|
||||||
|
|
||||||
go 1.24.2
|
go 1.22.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/alexedwards/argon2id v1.0.0
|
github.com/alexedwards/argon2id v1.0.0
|
||||||
github.com/gorilla/sessions v1.4.0
|
github.com/gorilla/sessions v1.2.2
|
||||||
github.com/jackc/pgx/v5 v5.7.6
|
github.com/lib/pq v1.10.9
|
||||||
github.com/stripe/stripe-go/v83 v83.0.0
|
github.com/stripe/stripe-go/v78 v78.7.0
|
||||||
)
|
golang.org/x/crypto v0.33.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 (
|
require (
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/net v0.21.0 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
|
golang.org/x/text v0.22.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
53
go.sum
@ -1,63 +1,56 @@
|
|||||||
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
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/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.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 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
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 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
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 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
|
||||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
|
||||||
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stripe/stripe-go/v78 v78.7.0 h1:TdTkzBn0wB0ntgOI74YHpvsNyHPBijX83n4ljsjXh6o=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stripe/stripe-go/v78 v78.7.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ=
|
||||||
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=
|
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-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.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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
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/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
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/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-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-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.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.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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
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.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.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-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-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-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-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.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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.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.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
@ -65,18 +58,20 @@ 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/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.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.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.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.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.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.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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.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.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
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=
|
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/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.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=
|
|
||||||
|
|||||||
@ -16,15 +16,13 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/stripe/stripe-go/v83"
|
"github.com/stripe/stripe-go/v78"
|
||||||
"github.com/stripe/stripe-go/v83/billingportal/session"
|
"github.com/stripe/stripe-go/v78/billingportal/session"
|
||||||
"github.com/stripe/stripe-go/v83/customer"
|
"github.com/stripe/stripe-go/v78/customer"
|
||||||
"github.com/stripe/stripe-go/v83/customersession"
|
"github.com/stripe/stripe-go/v78/customersession"
|
||||||
"github.com/stripe/stripe-go/v83/price"
|
"github.com/stripe/stripe-go/v78/price"
|
||||||
"github.com/stripe/stripe-go/v83/subscription"
|
"github.com/stripe/stripe-go/v78/subscription"
|
||||||
"github.com/stripe/stripe-go/v83/webhook"
|
"github.com/stripe/stripe-go/v78/webhook"
|
||||||
|
|
||||||
"alfheimgame.com/alfheim/pkg/models"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
//import "strconv"
|
//import "strconv"
|
||||||
@ -32,7 +30,7 @@ import (
|
|||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
AuthenticatedUser int32
|
AuthenticatedUser int32
|
||||||
FormErrors map[string]string
|
FormErrors map[string]string
|
||||||
Account models.Account
|
Account Account
|
||||||
Prices []stripe.Price
|
Prices []stripe.Price
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
ActiveSubscription bool
|
ActiveSubscription bool
|
||||||
@ -42,7 +40,7 @@ func favicon(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.ServeFile(w, r, "favicon.ico")
|
http.ServeFile(w, r, "favicon.ico")
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticatedUser(w http.ResponseWriter, r *http.Request) int32 {
|
func authenticated_user(w http.ResponseWriter, r *http.Request) int32 {
|
||||||
session, err := store.Get(r, "id")
|
session, err := store.Get(r, "id")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -79,12 +77,12 @@ func home(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, _ := users.GetAccount(id)
|
account, _ := users.GetAccount(id)
|
||||||
|
|
||||||
active_subscription := subscriptions.HasActiveSubscription(id)
|
active_subscription := subscriptions.HasActiveSubscription(id)
|
||||||
|
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/index.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/index.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -108,7 +106,7 @@ func home(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func login(w http.ResponseWriter, r *http.Request) {
|
func login(w http.ResponseWriter, r *http.Request) {
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/login.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/login.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -143,7 +141,7 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
|
|
||||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
@ -153,9 +151,9 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
id, err := users.Authenticate(username, password)
|
id, err := users.Authenticate(username, password)
|
||||||
if err == models.ErrInvalidCredentials {
|
if err == ErrInvalidCredentials {
|
||||||
errors["generic"] = "Email or Password is incorrect"
|
errors["generic"] = "Email or Password is incorrect"
|
||||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
@ -174,13 +172,13 @@ func login(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func logout(w http.ResponseWriter, r *http.Request) {
|
func logout(w http.ResponseWriter, r *http.Request) {
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/logout.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/logout.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, err := users.GetAccount(id)
|
account, err := users.GetAccount(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -209,13 +207,13 @@ func logout(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func register(w http.ResponseWriter, r *http.Request) {
|
func register(w http.ResponseWriter, r *http.Request) {
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/register.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/register.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, _ := users.GetAccount(id)
|
account, _ := users.GetAccount(id)
|
||||||
|
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
@ -233,7 +231,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
account := models.Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")}
|
account := 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)
|
errors := make(map[string]string)
|
||||||
|
|
||||||
@ -251,7 +249,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
if len(errors) > 0 {
|
if len(errors) > 0 {
|
||||||
|
|
||||||
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
|
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
@ -260,7 +258,7 @@ func register(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
_, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email)
|
_, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email)
|
||||||
|
|
||||||
if err == models.ErrDuplicateEmail || err == models.ErrDuplicateUsername {
|
if err == ErrDuplicateEmail || err == ErrDuplicateUsername {
|
||||||
|
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
|
||||||
@ -281,7 +279,7 @@ func account(w http.ResponseWriter, r *http.Request) {
|
|||||||
// log.Fatal(err)
|
// log.Fatal(err)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, err := users.GetAccount(id)
|
account, err := users.GetAccount(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -291,7 +289,7 @@ func account(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
//log.Println(id, account)
|
//log.Println(id, account)
|
||||||
|
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/account.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/account.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -314,9 +312,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 := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
@ -337,7 +335,7 @@ func deleteAccount(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func subscribe(w http.ResponseWriter, r *http.Request) {
|
func subscribe(w http.ResponseWriter, r *http.Request) {
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, err := users.GetAccount(id)
|
account, err := users.GetAccount(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -363,7 +361,7 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/html/base.html", "ui/html/subscribe.html")
|
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -377,8 +375,8 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func subscribeStripe(w http.ResponseWriter, r *http.Request) {
|
func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, err := users.GetAccount(id)
|
account, err := users.GetAccount(id)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -399,7 +397,7 @@ func subscribeStripe(w http.ResponseWriter, r *http.Request) {
|
|||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
text, err := template.ParseFiles("ui/html/base.html", "ui/html/subscribe_stripe.html")
|
text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Internal Server Error", 500)
|
http.Error(w, "Internal Server Error", 500)
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -412,8 +410,8 @@ func subscribeStripe(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func manageBilling(w http.ResponseWriter, r *http.Request) {
|
func managebilling(w http.ResponseWriter, r *http.Request) {
|
||||||
id := authenticatedUser(w, r)
|
id := authenticated_user(w, r)
|
||||||
account, err := users.GetAccount(id)
|
account, err := users.GetAccount(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -421,7 +419,7 @@ func manageBilling(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
params := &stripe.BillingPortalSessionParams{
|
params := &stripe.BillingPortalSessionParams{
|
||||||
Customer: stripe.String(account.StripeID),
|
Customer: stripe.String(account.StripeID),
|
||||||
ReturnURL: stripe.String("https://alfheimgame.com/account"),
|
ReturnURL: stripe.String("http://localhost:8080/account"),
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := session.New(params)
|
result, err := session.New(params)
|
||||||
@ -510,11 +508,11 @@ func webhooks(w http.ResponseWriter, r *http.Request) {
|
|||||||
w.WriteHeader(http.StatusOK)
|
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, "", " ")
|
//toprint, _ := json.MarshalIndent(checkoutSession, "", " ")
|
||||||
//log.Println(string(toprint))
|
//log.Println(string(toprint))
|
||||||
|
|
||||||
subscription, err := subscription.Get(checkoutSession.Subscription.ID, nil)
|
subscription, err := subscription.Get(checkoutsession.Subscription.ID, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
return err
|
return err
|
||||||
@ -542,19 +540,19 @@ func handle_checkout_session_completed(checkoutSession stripe.CheckoutSession) e
|
|||||||
// status = paused
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handlePaymentMethodAttached(paymentMethod stripe.PaymentMethod) error {
|
func handle_payment_method_attached(paymentmethod stripe.PaymentMethod) error {
|
||||||
//toprint, _ := json.MarshalIndent(setupintent, "", " ")
|
//toprint, _ := json.MarshalIndent(setupintent, "", " ")
|
||||||
//log.Println(string(toprint))
|
//log.Println(string(toprint))
|
||||||
|
|
||||||
// make this the new customer's default payment method
|
// make this the new customer's default payment method
|
||||||
params := &stripe.CustomerParams{}
|
params := &stripe.CustomerParams{}
|
||||||
params.DefaultSource = &paymentMethod.ID
|
params.DefaultSource = &paymentmethod.ID
|
||||||
/*result*/ _, err := customer.Update(paymentMethod.Customer.ID, params)
|
/*result*/ _, err := customer.Update(paymentmethod.Customer.ID, params)
|
||||||
//log.Println(result)
|
//log.Println(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -5,64 +5,48 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"database/sql"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/jackc/pgx/v5"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
_ "github.com/lib/pq"
|
||||||
"github.com/stripe/stripe-go/v83"
|
"github.com/stripe/stripe-go/v78"
|
||||||
|
|
||||||
"alfheimgame.com/alfheim/pkg/models/postgresql"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var users *postgresql.AccountModel
|
var users *Usermodel
|
||||||
var subscriptions *postgresql.SubscriptionModel
|
var subscriptions *SubscriptionModel
|
||||||
|
|
||||||
var key = []byte("super-secret-key")
|
var key = []byte("super-secret-key")
|
||||||
var store = sessions.NewCookieStore(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 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() {
|
func main() {
|
||||||
addr := flag.String("addr", "127.0.0.1:8080", "HTTP network addr")
|
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")
|
//prodaddr := flag.String("prodaddr", "45.76.84.7:443", "HTTP network addr")
|
||||||
|
|
||||||
production := flag.Bool("production", false, "Whether to use production port and TLS")
|
production := flag.Bool("production", false, "Whether to use production port and TLS")
|
||||||
|
|
||||||
displayVersion := flag.Bool("version", false, "Display version and exit")
|
|
||||||
|
|
||||||
_ = addr
|
_ = addr
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if *displayVersion {
|
|
||||||
fmt.Printf("Version: %s\n", version)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("Hello, Sailor!")
|
log.Println("Hello, Sailor!")
|
||||||
|
|
||||||
stripe.Key = "sk_test_51PGebgKUHKCjyTmc97rfDPcvew6EhqDz2qp3U7XoAMIilAU9IVo2NO4P7ylkTvbBafFVr94trha1VYY32jRWMw2K00Yq7YJXFf"
|
stripe.Key = "sk_test_51PGebgKUHKCjyTmc97rfDPcvew6EhqDz2qp3U7XoAMIilAU9IVo2NO4P7ylkTvbBafFVr94trha1VYY32jRWMw2K00Yq7YJXFf"
|
||||||
|
|
||||||
store.MaxAge(0)
|
store.MaxAge(0)
|
||||||
|
|
||||||
db, err := pgx.Connect(context.Background(), "postgres://alfheim:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com/alfheim")
|
db, err := sql.Open("postgres", "postgres://elves_database:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@80.240.25.87/elves_database")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
defer db.Close(context.Background())
|
users = &Usermodel{db}
|
||||||
|
subscriptions = &SubscriptionModel{db}
|
||||||
users = &postgresql.AccountModel{DB: db}
|
|
||||||
subscriptions = &postgresql.SubscriptionModel{DB: db}
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
|
|
||||||
@ -75,7 +59,7 @@ func main() {
|
|||||||
//accounts := make([]*Account, 0)
|
//accounts := make([]*Account, 0)
|
||||||
//for rows.Next() {
|
//for rows.Next() {
|
||||||
// acc := new(Account)
|
// acc := new(Account)
|
||||||
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Colour)
|
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Color)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Fatal(err)
|
// log.Fatal(err)
|
||||||
// }
|
// }
|
||||||
@ -90,7 +74,7 @@ func main() {
|
|||||||
// log.Println(acc)
|
// log.Println(acc)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("ui/static"))))
|
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
|
||||||
|
|
||||||
mux.HandleFunc("/favicon.ico", favicon)
|
mux.HandleFunc("/favicon.ico", favicon)
|
||||||
|
|
||||||
@ -98,10 +82,10 @@ func main() {
|
|||||||
mux.HandleFunc("/login", login)
|
mux.HandleFunc("/login", login)
|
||||||
mux.HandleFunc("/logout", logout)
|
mux.HandleFunc("/logout", logout)
|
||||||
mux.HandleFunc("/register", register)
|
mux.HandleFunc("/register", register)
|
||||||
mux.HandleFunc("/account", requireAuthenticatedUser(account))
|
mux.HandleFunc("/account", require_authenticated_user(account))
|
||||||
mux.HandleFunc("/deleteaccount", requireAuthenticatedUser(deleteAccount))
|
mux.HandleFunc("/deleteaccount", require_authenticated_user(deleteaccount))
|
||||||
mux.HandleFunc("/subscribe", requireAuthenticatedUser(subscribeStripe))
|
mux.HandleFunc("/subscribe", require_authenticated_user(subscribe_stripe))
|
||||||
mux.HandleFunc("/managebilling", requireAuthenticatedUser(manageBilling))
|
mux.HandleFunc("/managebilling", require_authenticated_user(managebilling))
|
||||||
mux.HandleFunc("/webhook", webhooks)
|
mux.HandleFunc("/webhook", webhooks)
|
||||||
|
|
||||||
if *production {
|
if *production {
|
||||||
@ -122,8 +106,8 @@ func main() {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
// log.Fatal(server.ListenAndServeTLS("", ""))
|
// log.Fatal(server.ListenAndServeTLS("", ""))
|
||||||
log.Fatal(http.ListenAndServe(*prodAddr, logRequest(secureHeaders(mux))))
|
log.Fatal(http.ListenAndServe(*prodaddr, log_request(secure_headers(mux))))
|
||||||
} else {
|
} else {
|
||||||
log.Fatal(http.ListenAndServe(*addr, logRequest(secureHeaders(mux))))
|
log.Fatal(http.ListenAndServe(*addr, log_request(secure_headers(mux))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -5,7 +5,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
func secureHeaders(next http.Handler) http.Handler {
|
func secure_headers(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
w.Header().Set("X-XSS-Protection", "1; mode=block")
|
||||||
w.Header().Set("X-Frame-Options", "deny")
|
w.Header().Set("X-Frame-Options", "deny")
|
||||||
@ -16,7 +16,7 @@ func secureHeaders(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func logRequest(next http.Handler) http.Handler {
|
func log_request(next http.Handler) http.Handler {
|
||||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL)
|
log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL)
|
||||||
|
|
||||||
@ -26,12 +26,12 @@ func logRequest(next http.Handler) http.Handler {
|
|||||||
return http.HandlerFunc(fn)
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requireAuthenticatedUser(next http.HandlerFunc) http.HandlerFunc {
|
func require_authenticated_user(next http.HandlerFunc) http.HandlerFunc {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
// If the user is not authenticated, redirect them to the login page and
|
// If the user is not authenticated, redirect them to the login page and
|
||||||
// return from the middleware chain so that no subsequent handlers in
|
// return from the middleware chain so that no subsequent handlers in
|
||||||
// the chain are executed.
|
// the chain are executed.
|
||||||
if authenticatedUser(w, r) == 0 {
|
if authenticated_user(w, r) == 0 {
|
||||||
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
310
models.go
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
}
|
||||||
@ -1,48 +0,0 @@
|
|||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@ -1,116 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
BIN
static/Vollkorn-VariableFont_wght.ttf
Normal file
BIN
static/image.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
1
static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 279 B |
BIN
static/panel-000.png
Normal file
|
After Width: | Height: | Size: 173 B |
202
static/style.css
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
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
Normal file
22
ui/account.html
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{{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
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<!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>
|
||||||
9
ui/index.html
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{{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}}
|
||||||
39
ui/login.html
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
{{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}}
|
||||||
11
ui/logout.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{{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}}
|
||||||
45
ui/register.html
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
{{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}}
|
||||||
8
ui/subscribe.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{{define "body"}}
|
||||||
|
{{range .Prices}}
|
||||||
|
<div class="wrapper">
|
||||||
|
{{$price := divide .UnitAmountDecimal 100}}
|
||||||
|
{{.Currency}} {{printf "%.2f" $price}}
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
5
ui/subscribe_stripe.html
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{{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}}
|
||||||