Compare commits

...

10 Commits

Author SHA1 Message Date
65e87129a6 git-svn-id: svn://losandesgames.com/alfheim-website@53 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-04 08:59:53 +00:00
eb222d8677 git-svn-id: svn://losandesgames.com/alfheim-website@52 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-04 06:14:41 +00:00
b69b4e25bf git-svn-id: svn://losandesgames.com/alfheim-website@51 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 23:20:07 +00:00
45e568a824 git-svn-id: svn://losandesgames.com/alfheim-website@50 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 23:19:06 +00:00
391becf14e git-svn-id: svn://losandesgames.com/alfheim-website@49 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 23:10:29 +00:00
afb402223f git-svn-id: svn://losandesgames.com/alfheim-website@48 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 22:54:37 +00:00
cb3199966c git-svn-id: svn://losandesgames.com/alfheim-website@47 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 22:52:43 +00:00
db8c61da56 git-svn-id: svn://losandesgames.com/alfheim-website@46 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 18:31:45 +00:00
c15256a9d4 git-svn-id: svn://losandesgames.com/alfheim-website@45 15359d88-9307-4e75-a9c1-e5686e5897df 2025-10-03 18:30:52 +00:00
5b32cd34a2 git-svn-id: svn://losandesgames.com/alfheim-website@44 15359d88-9307-4e75-a9c1-e5686e5897df 2025-03-26 22:05:28 +00:00
61 changed files with 459 additions and 1220 deletions

19
.vscode/launch.json vendored
View File

@ -13,10 +13,27 @@
{
"name": "Attach to Remote",
"type": "go",
"debugAdapter": "dlv-dap",
"request": "attach",
"mode": "remote",
"port": 4321,
"host": "45.76.84.7",
"host": "losandesgames.com",
// "substitutePath": [
// { "from": "${workspaceFolder}", "to": "/home/alfheim/linux_amd64" },
// ]
},
{
"name": "Launch Remote",
"type": "go",
"debugAdapter": "dlv-dap",
"request": "launch",
"mode": "exec",
"program": "/home/alfheim/linux_amd64/alfheim-website",
"port": 4321,
"host": "losandesgames.com",
// "substitutePath": [
// { "from": "${workspaceFolder}", "to": "/home/alfheim/linux_amd64" },
// ]
}
]
}

View File

@ -1,32 +1,29 @@
svn_revision = $(shell svn info --show-item revision)
#LDFLAGS = "-X main.version=$(svn_revision)"
build:
@echo "Building the website..."
rm -rf bin
mkdir bin
mkdir bin/static
mkdir bin/ui
go build -o bin/alfheim-website
go build -ldflags=$(LDFLAGS) -o bin/alfheim-website ./cmd/web
cp -r ui bin
cp -r static bin
cp favicon.ico bin
cp Caddyfile bin
mkdir bin/linux_amd64
mkdir bin/linux_amd64/static
mkdir bin/linux_amd64/ui
GOOS=linux GOARCH=amd64 go build -o bin/linux_amd64/alfheim-website
GOOS=linux GOARCH=amd64 go build -ldflags=$(LDFLAGS) -o bin/linux_amd64/alfheim-website ./cmd/web
cp -r ui bin/linux_amd64
cp -r static bin/linux_amd64
cp favicon.ico bin/linux_amd64
cp Caddyfile bin/linux_amd64
run:
@echo "Running the website..."
go run ./*.go
go run ./cmd/web/*.go
.PHONY: deploy
deploy:
.PHONY: production/deploy
production/deploy:
rsync -rP --delete bin/linux_amd64 alfheim@alfheimgame.com:/home/alfheim
.PHONY: service
service:
rsync -P alfheim-website.service alfheim@alfheimgame.com:/home/alfheim
rsync -P remote/production/alfheim-website.service alfheim@alfheimgame.com:/home/alfheim
ssh -t root@alfheimgame.com 'mv /home/alfheim/alfheim-website.service /etc/systemd/system && systemctl enable alfheim-website && systemctl restart alfheim-website'
.PHONY: production/connect
production/connect:
ssh alfheim@losandesgames.com

View File

@ -1,7 +0,0 @@
{
email vicenteferrarismith@gmail.com
}
alfheimgame.com {
reverse_proxy localhost:4000
}

Binary file not shown.

View File

@ -1,7 +0,0 @@
{
email vicenteferrarismith@gmail.com
}
alfheimgame.com {
reverse_proxy localhost:4000
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M480-120v-80h280v-560H480v-80h280q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z"/></svg>

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

View File

@ -1,202 +0,0 @@
html {
/*background: no-repeat url(/static/image.png);
background-size: cover;
background-position: center;*/
height: 100vh;
background-color: black;
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #3475CB;
font-family: "Vollkorn";
color: white;
margin-top: 0px;
margin-bottom: 0px;
margin-left: auto;
margin-right: auto;
max-width: 900px;
min-height: 100%;
}
header {
}
nav {
padding-left: 8px;
padding-right: 8px;
display: flex;
align-items: center;
justify-content: space-between;
}
.navbuttons a {
display: inline-block;
}
.maintitle {
margin: 0px;
font-size: 6rem;
color: white;
}
@media (max-width: 1080px) {
.maintitle {
font-size: 4rem;
}
}
.loginbutton {
align-self: center;
display: flex;
border: 12px solid;
border-image-source: url("/static/panel-000.png");
border-image-slice: 12 fill;
padding-top: 15px;
padding-right: 20px;
padding-bottom: 15px;
padding-left: 20px;
color: black;
/*box-shadow: 10px 10px 5px lightblue;*/
}
main {
padding: 8px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100%;
max-height: 100%;
margin-left: auto;
margin-right: auto;
}
p {
text-align: center;
font-size: 1rem;
}
.account-wrapper {
background: transparent;
border: 2px solid white;
backdrop-filter: blur(20px);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 15px;
padding: 30px 40px;
font-size: 1.5rem;
}
.wrapper {
display: inline-block;
background: transparent;
border: 2px solid white;
backdrop-filter: blur(20px);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 15px;
padding: 30px 40px;
width: 80%;
text-align: center;
}
.wrapper h1 {
font-size: 1.5rem;
text-align: center;
}
.wrapper .input-box {
width: 100%;
}
.input-box input {
box-sizing: border-box;
background: transparent;
width: 100%;
height: 100%;
outline: none;
border: 2px solid rgba(255, 255, 255, .2);
border-radius: 25px;
padding: 10px 45px 10px 20px
}
.input-box input::placeholder {
color: white;
}
.wrapper .input-error {
width: 100%;
}
.error {
color: rgba(240, 0, 0, .8);
}
.input-error input {
box-sizing: border-box;
background: transparent;
width: 100%;
height: 100%;
outline: none;
border: 2px solid rgba(255, 0, 0, .2);
border-radius: 25px;
padding: 10px 45px 10px 20px
}
.input-error input::placeholder {
color: red;
}
.login-btn-wrapper {
display: flex;
justify-content: center;
}
.wrapper .btn {
width: 100%;
outline: none;
border: none;
border-radius: 20px;
}
.wrapper .register-link {
display: flex;
justify-content: center;
}
input {
font-family: inherit;
}
footer {
/*margin-top: auto;*/
width: 100%;
background-color: #132123;
color: white;
text-align: center;
}
a:link {
text-decoration: none;
}
.wrapper a:link {
text-decoration: none;
}
.wrapper a:visited {
text-decoration: none;
}
.wrapper a:hover {
text-decoration: underline;
}
video {
width: 100%;
height: auto;
}

Binary file not shown.

View File

@ -1,22 +0,0 @@
{{define "body"}}
<div class="account-wrapper">
<div>Username: {{.Account.Username}}</div>
<div>First name: {{.Account.Firstname}}</div>
<div>Last name: {{.Account.Lastname}}</div>
<div>Color: {{.Account.Color}}</div>
</div>
<div class="wrapper">
<form action="/deleteaccount" method="post">
<div class="login-btn-wrapper">
<input type="submit" value="Delete Account" class="btn">
</div>
</form>
<form method="POST" action="/managebilling">
<div class="login-btn-wrapper">
<input type="submit" value="Manage Billing" class="btn">
</div>
</form>
</div>
{{end}}

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Alfheim</title>
<meta charset="utf-8" />
<meta name="description" content="A handcrafted economy and politics MMO." />
<meta name="author" content="Vicente Ferrari Smith" />
<meta name="keywords" content="Alfheim, indie, video game, mmo, colony, colony simulator, vicente ferrari smith, game, economy, politics, alfheim" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@font-face {
font-family: "Vollkorn";
src: url(/static/Vollkorn-VariableFont_wght.ttf);
}
</style>
<link href="/static/style.css" rel="stylesheet" />
</head>
<body>
<header>
<nav>
<a href="/"><h1 class="maintitle">Alfheim</h1></a>
<div class="navbuttons">
{{if not .ActiveSubscription}}
<a href="/subscribe"><div class="loginbutton"><strong>Subscribe</strong></div></a>
{{end}}
{{if .AuthenticatedUser}}
<a href="/account"><div class="loginbutton"><strong>Account</strong></div></a>
<a href="/logout"><div class="loginbutton"><strong>Log out</strong></div></a>
{{else}}
<a href="/login"><div class="loginbutton"><strong>Log in</strong><img src="/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg" alt=""/></div></a>
<a href="/register"><div class="loginbutton"><strong>Register</strong></div></a>
{{end}}
</div>
</nav>
</header>
<main>
{{template "body" .}}
</main>
<footer>
Alfheim &copy; 2025, Vicente Ferrari Smith, Los Andes Games, Vienna, Austria. All rights reserved.
</footer>
</body>
</html>

View File

@ -1,9 +0,0 @@
{{define "body"}}
<p>Alfheim is a game about humanity: our own will, and our place in the world at large.</p>
<p>Alfheim is an MMO where you&mdash;the player&mdash;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}}

View File

@ -1,39 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Log in</h1>
{{with .FormErrors.generic}}
<label class="error">{{.}}</label>
{{end}}
{{with .FormErrors.username}}
<label class="error">{{.}}</label>
{{end}}
<div {{if .FormErrors.username}}class="input-error"{{else}}class="input-box"{{end}}>
<input type="text" id="username" name="username" placeholder="Username" required>
</div>
<br />
{{with .FormErrors.password}}
<label class="error">{{.}}</label>
{{end}}
<div {{if .FormErrors.password}}class="input-error"{{else}}class="input-box"{{end}}>
<input type="password" id="password" name="password" placeholder="Password" required>
</div>
<br />
<div class="login-btn-wrapper">
<input type="submit" value="Log in" class="btn">
</div>
<br />
<a href="/forgotten">Have you forgotten your password?</a>
<div class="register-link">
<p>Don't have an account? <a href="/register">Register</a></p>
</div>
</form>
</div>
{{end}}

View File

@ -1,11 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Log out</h1>
<div class="login-btn-wrapper">
<input type="submit" value="Log out" class="btn">
</div>
</form>
</div>
{{end}}

View File

@ -1,45 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Register</h1>
{{with .FormErrors.username}}
<label class="error">{{.}}</label>
{{end}}
<div class="input-box" {{with .FormErrors.username}}class="input-error"{{end}}>
<input type="text" id="username" name="username" placeholder="Username" required>
</div>
<br />
<div class="input-box">
<input type="email" id="email" name="email" placeholder="Email" required>
</div>
<br />
<div class="input-box">
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
</div>
<br />
<div class="input-box">
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
</div>
<br />
{{with .FormErrors.password}}
<label class="error">{{.}}</label>
{{end}}
<div class="input-box" {{with .FormErrors.password}}class="input-error"{{end}}>
<input type="password" id="password" name="password" placeholder="Password" required>
</div>
<br />
<div class="login-btn-wrapper">
<input type="submit" value="Register" class="btn">
</div>
</form>
</div>
{{end}}

View File

@ -1,8 +0,0 @@
{{define "body"}}
{{range .Prices}}
<div class="wrapper">
{{$price := divide .UnitAmountDecimal 100}}
{{.Currency}} {{printf "%.2f" $price}}
</div>
{{end}}
{{end}}

View File

@ -1,5 +0,0 @@
{{define "body"}}
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
<stripe-pricing-table pricing-table-id="prctbl_1PIaJpKUHKCjyTmcy1ONKQiT" publishable-key="pk_test_51PGebgKUHKCjyTmcyuhj7C5lfHfKFLCxRJ2opoqxL3mGEHSRaMvOHYKKgX4MdFfESW78dssjyunboUcFhg3LTwmn005PmzVIXw" customer-session-client-secret={{.ClientSecret}}>
</stripe-pricing-table>
{{end}}

View File

@ -16,13 +16,15 @@ import (
"strings"
"unicode/utf8"
"github.com/stripe/stripe-go/v78"
"github.com/stripe/stripe-go/v78/billingportal/session"
"github.com/stripe/stripe-go/v78/customer"
"github.com/stripe/stripe-go/v78/customersession"
"github.com/stripe/stripe-go/v78/price"
"github.com/stripe/stripe-go/v78/subscription"
"github.com/stripe/stripe-go/v78/webhook"
"github.com/stripe/stripe-go/v83"
"github.com/stripe/stripe-go/v83/billingportal/session"
"github.com/stripe/stripe-go/v83/customer"
"github.com/stripe/stripe-go/v83/customersession"
"github.com/stripe/stripe-go/v83/price"
"github.com/stripe/stripe-go/v83/subscription"
"github.com/stripe/stripe-go/v83/webhook"
"alfheimgame.com/alfheim/pkg/models"
)
//import "strconv"
@ -30,7 +32,7 @@ import (
type TemplateData struct {
AuthenticatedUser int32
FormErrors map[string]string
Account Account
Account models.Account
Prices []stripe.Price
ClientSecret string
ActiveSubscription bool
@ -40,7 +42,7 @@ func favicon(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "favicon.ico")
}
func authenticated_user(w http.ResponseWriter, r *http.Request) int32 {
func authenticatedUser(w http.ResponseWriter, r *http.Request) int32 {
session, err := store.Get(r, "id")
if err != nil {
log.Println(err)
@ -77,12 +79,12 @@ func home(w http.ResponseWriter, r *http.Request) {
return
}
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
account, _ := users.GetAccount(id)
active_subscription := subscriptions.HasActiveSubscription(id)
text, err := template.ParseFiles("ui/base.html", "ui/index.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/index.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
@ -106,7 +108,7 @@ func home(w http.ResponseWriter, r *http.Request) {
}
func login(w http.ResponseWriter, r *http.Request) {
text, err := template.ParseFiles("ui/base.html", "ui/login.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/login.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Fatal(err)
@ -141,7 +143,7 @@ func login(w http.ResponseWriter, r *http.Request) {
if len(errors) > 0 {
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", 500)
@ -151,9 +153,9 @@ func login(w http.ResponseWriter, r *http.Request) {
}
id, err := users.Authenticate(username, password)
if err == ErrInvalidCredentials {
if err == models.ErrInvalidCredentials {
errors["generic"] = "Email or Password is incorrect"
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", 500)
@ -172,13 +174,13 @@ func login(w http.ResponseWriter, r *http.Request) {
}
func logout(w http.ResponseWriter, r *http.Request) {
text, err := template.ParseFiles("ui/base.html", "ui/logout.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/logout.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
}
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
account, err := users.GetAccount(id)
if err != nil {
@ -207,13 +209,13 @@ func logout(w http.ResponseWriter, r *http.Request) {
}
func register(w http.ResponseWriter, r *http.Request) {
text, err := template.ParseFiles("ui/base.html", "ui/register.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/register.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
}
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
account, _ := users.GetAccount(id)
// if err != nil {
@ -231,7 +233,7 @@ func register(w http.ResponseWriter, r *http.Request) {
}
case http.MethodPost:
account := Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")}
account := models.Account{Username: r.FormValue("username"), Password: []byte(r.FormValue("password")), Firstname: r.FormValue("firstname"), Lastname: r.FormValue("lastname"), Email: r.FormValue("email")}
errors := make(map[string]string)
@ -249,7 +251,7 @@ func register(w http.ResponseWriter, r *http.Request) {
if len(errors) > 0 {
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticated_user(w, r), FormErrors: errors})
err := text.Execute(w, TemplateData{AuthenticatedUser: authenticatedUser(w, r), FormErrors: errors})
if err != nil {
log.Println(err)
http.Error(w, "Internal Server Error", 500)
@ -258,7 +260,7 @@ func register(w http.ResponseWriter, r *http.Request) {
_, err := users.Insert(account.Username, string(account.Password), account.Firstname, account.Lastname, account.Email)
if err == ErrDuplicateEmail || err == ErrDuplicateUsername {
if err == models.ErrDuplicateEmail || err == models.ErrDuplicateUsername {
} else if err != nil {
@ -279,7 +281,7 @@ func account(w http.ResponseWriter, r *http.Request) {
// log.Fatal(err)
//}
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
account, err := users.GetAccount(id)
if err != nil {
@ -289,7 +291,7 @@ func account(w http.ResponseWriter, r *http.Request) {
//log.Println(id, account)
text, err := template.ParseFiles("ui/base.html", "ui/account.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/account.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
@ -312,9 +314,9 @@ func account(w http.ResponseWriter, r *http.Request) {
// }
}
func deleteaccount(w http.ResponseWriter, r *http.Request) {
func deleteAccount(w http.ResponseWriter, r *http.Request) {
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
switch r.Method {
case http.MethodPost:
@ -335,7 +337,7 @@ func deleteaccount(w http.ResponseWriter, r *http.Request) {
}
func subscribe(w http.ResponseWriter, r *http.Request) {
id := authenticated_user(w, r)
id := authenticatedUser(w, r)
account, err := users.GetAccount(id)
if err != nil {
@ -361,7 +363,7 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
},
}
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/base.html", "ui/subscribe.html")
text, err := template.New("base.html").Funcs(fm).ParseFiles("ui/html/base.html", "ui/html/subscribe.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
@ -375,8 +377,8 @@ func subscribe(w http.ResponseWriter, r *http.Request) {
}
}
func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
id := authenticated_user(w, r)
func subscribeStripe(w http.ResponseWriter, r *http.Request) {
id := authenticatedUser(w, r)
account, err := users.GetAccount(id)
if err != nil {
@ -397,7 +399,7 @@ func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
log.Println(err)
}
text, err := template.ParseFiles("ui/base.html", "ui/subscribe_stripe.html")
text, err := template.ParseFiles("ui/html/base.html", "ui/html/subscribe_stripe.html")
if err != nil {
http.Error(w, "Internal Server Error", 500)
log.Println(err)
@ -410,8 +412,8 @@ func subscribe_stripe(w http.ResponseWriter, r *http.Request) {
}
}
func managebilling(w http.ResponseWriter, r *http.Request) {
id := authenticated_user(w, r)
func manageBilling(w http.ResponseWriter, r *http.Request) {
id := authenticatedUser(w, r)
account, err := users.GetAccount(id)
if err != nil {
log.Println(err)
@ -419,7 +421,7 @@ func managebilling(w http.ResponseWriter, r *http.Request) {
params := &stripe.BillingPortalSessionParams{
Customer: stripe.String(account.StripeID),
ReturnURL: stripe.String("http://localhost:8080/account"),
ReturnURL: stripe.String("https://alfheimgame.com/account"),
}
result, err := session.New(params)
@ -508,11 +510,11 @@ func webhooks(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}
func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) error {
func handle_checkout_session_completed(checkoutSession stripe.CheckoutSession) error {
//toprint, _ := json.MarshalIndent(checkoutSession, "", " ")
//log.Println(string(toprint))
subscription, err := subscription.Get(checkoutsession.Subscription.ID, nil)
subscription, err := subscription.Get(checkoutSession.Subscription.ID, nil)
if err != nil {
log.Println(err)
return err
@ -540,19 +542,19 @@ func handle_checkout_session_completed(checkoutsession stripe.CheckoutSession) e
// status = paused
//}
subscriptions.Insert(checkoutsession.Customer.ID, subscription.ID, checkoutsession.ID, subscription.Status)
subscriptions.Insert(checkoutSession.Customer.ID, subscription.ID, checkoutSession.ID, subscription.Status)
return nil
}
func handle_payment_method_attached(paymentmethod stripe.PaymentMethod) error {
func handlePaymentMethodAttached(paymentMethod stripe.PaymentMethod) error {
//toprint, _ := json.MarshalIndent(setupintent, "", " ")
//log.Println(string(toprint))
// make this the new customer's default payment method
params := &stripe.CustomerParams{}
params.DefaultSource = &paymentmethod.ID
/*result*/ _, err := customer.Update(paymentmethod.Customer.ID, params)
params.DefaultSource = &paymentMethod.ID
/*result*/ _, err := customer.Update(paymentMethod.Customer.ID, params)
//log.Println(result)
if err != nil {
log.Println(err)

View File

@ -5,48 +5,64 @@
package main
import (
"context"
"flag"
"fmt"
"log"
"net/http"
"database/sql"
"regexp"
"github.com/jackc/pgx/v5"
"github.com/gorilla/sessions"
_ "github.com/lib/pq"
"github.com/stripe/stripe-go/v78"
"github.com/stripe/stripe-go/v83"
"alfheimgame.com/alfheim/pkg/models/postgresql"
)
var users *Usermodel
var subscriptions *SubscriptionModel
var users *postgresql.AccountModel
var subscriptions *postgresql.SubscriptionModel
var key = []byte("super-secret-key")
var store = sessions.NewCookieStore(key)
var emailrx = regexp.MustCompile("/^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/")
var version string
func main() {
addr := flag.String("addr", "127.0.0.1:8080", "HTTP network addr")
prodaddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr")
prodAddr := flag.String("prodaddr", "127.0.0.1:4000", "HTTP network addr")
//prodaddr := flag.String("prodaddr", "45.76.84.7:443", "HTTP network addr")
production := flag.Bool("production", false, "Whether to use production port and TLS")
displayVersion := flag.Bool("version", false, "Display version and exit")
_ = addr
flag.Parse()
if *displayVersion {
fmt.Printf("Version: %s\n", version)
return
}
log.Println("Hello, Sailor!")
stripe.Key = "sk_test_51PGebgKUHKCjyTmc97rfDPcvew6EhqDz2qp3U7XoAMIilAU9IVo2NO4P7ylkTvbBafFVr94trha1VYY32jRWMw2K00Yq7YJXFf"
store.MaxAge(0)
db, err := sql.Open("postgres", "postgres://elves_database:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@80.240.25.87/elves_database")
db, err := pgx.Connect(context.Background(), "postgres://alfheim:iK2SoVbDhdCki5n3LxGyP6zKpLspt4@losandesgames.com/alfheim")
if err != nil {
log.Fatal(err)
}
defer db.Close()
users = &Usermodel{db}
subscriptions = &SubscriptionModel{db}
defer db.Close(context.Background())
users = &postgresql.AccountModel{DB: db}
subscriptions = &postgresql.SubscriptionModel{DB: db}
mux := http.NewServeMux()
@ -59,7 +75,7 @@ func main() {
//accounts := make([]*Account, 0)
//for rows.Next() {
// acc := new(Account)
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Color)
// err := rows.Scan(&acc.id, &acc.Username, &acc.password, &acc.Colour)
// if err != nil {
// log.Fatal(err)
// }
@ -74,7 +90,7 @@ func main() {
// log.Println(acc)
//}
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("ui/static"))))
mux.HandleFunc("/favicon.ico", favicon)
@ -82,10 +98,10 @@ func main() {
mux.HandleFunc("/login", login)
mux.HandleFunc("/logout", logout)
mux.HandleFunc("/register", register)
mux.HandleFunc("/account", require_authenticated_user(account))
mux.HandleFunc("/deleteaccount", require_authenticated_user(deleteaccount))
mux.HandleFunc("/subscribe", require_authenticated_user(subscribe_stripe))
mux.HandleFunc("/managebilling", require_authenticated_user(managebilling))
mux.HandleFunc("/account", requireAuthenticatedUser(account))
mux.HandleFunc("/deleteaccount", requireAuthenticatedUser(deleteAccount))
mux.HandleFunc("/subscribe", requireAuthenticatedUser(subscribeStripe))
mux.HandleFunc("/managebilling", requireAuthenticatedUser(manageBilling))
mux.HandleFunc("/webhook", webhooks)
if *production {
@ -106,8 +122,8 @@ func main() {
// }
// log.Fatal(server.ListenAndServeTLS("", ""))
log.Fatal(http.ListenAndServe(*prodaddr, log_request(secure_headers(mux))))
log.Fatal(http.ListenAndServe(*prodAddr, logRequest(secureHeaders(mux))))
} else {
log.Fatal(http.ListenAndServe(*addr, log_request(secure_headers(mux))))
log.Fatal(http.ListenAndServe(*addr, logRequest(secureHeaders(mux))))
}
}

View File

@ -5,7 +5,7 @@ import (
"net/http"
)
func secure_headers(next http.Handler) http.Handler {
func secureHeaders(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-XSS-Protection", "1; mode=block")
w.Header().Set("X-Frame-Options", "deny")
@ -16,7 +16,7 @@ func secure_headers(next http.Handler) http.Handler {
return http.HandlerFunc(fn)
}
func log_request(next http.Handler) http.Handler {
func logRequest(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s - %s %s %s", r.RemoteAddr, r.Proto, r.Method, r.URL)
@ -26,12 +26,12 @@ func log_request(next http.Handler) http.Handler {
return http.HandlerFunc(fn)
}
func require_authenticated_user(next http.HandlerFunc) http.HandlerFunc {
func requireAuthenticatedUser(next http.HandlerFunc) http.HandlerFunc {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// If the user is not authenticated, redirect them to the login page and
// return from the middleware chain so that no subsequent handlers in
// the chain are executed.
if authenticated_user(w, r) == 0 {
if authenticatedUser(w, r) == 0 {
http.Redirect(w, r, "/login", http.StatusSeeOther)
return
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

20
go.mod
View File

@ -1,18 +1,22 @@
module alfheimgame.com/alfheim
go 1.22.2
go 1.24.2
require (
github.com/alexedwards/argon2id v1.0.0
github.com/gorilla/sessions v1.2.2
github.com/lib/pq v1.10.9
github.com/stripe/stripe-go/v78 v78.7.0
golang.org/x/crypto v0.33.0
github.com/gorilla/sessions v1.4.0
github.com/jackc/pgx/v5 v5.7.6
github.com/stripe/stripe-go/v83 v83.0.0
)
require (
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
golang.org/x/text v0.29.0 // indirect
)
require (
github.com/gorilla/securecookie v1.1.2 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.22.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
)

53
go.sum
View File

@ -1,56 +1,63 @@
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stripe/stripe-go/v78 v78.7.0 h1:TdTkzBn0wB0ntgOI74YHpvsNyHPBijX83n4ljsjXh6o=
github.com/stripe/stripe-go/v78 v78.7.0/go.mod h1:GjncxVLUc1xoIOidFqVwq+y3pYiG7JLVWiVQxTsLrvQ=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stripe/stripe-go/v83 v83.0.0 h1:00HYu/n80zH6ugy88bWI5sBLbJZ7WmhCXCRQ1N1tuqI=
github.com/stripe/stripe-go/v83 v83.0.0/go.mod h1:nRyDcLrJtwPPQUnKAFs9Bt1NnQvNhNiF6V19XHmPISE=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
@ -58,20 +65,18 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

310
models.go
View File

@ -1,310 +0,0 @@
//
// Created by vfs on 02.05.2024.
//
package main
import (
"database/sql"
"errors"
"fmt"
"time"
//import "golang.org/x/crypto/bcrypt"
"log"
"github.com/alexedwards/argon2id"
_ "github.com/lib/pq"
"github.com/stripe/stripe-go/v78"
"github.com/stripe/stripe-go/v78/customer"
)
var ErrNoRecord = errors.New("no matching record found")
var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrDuplicateEmail = errors.New("duplicate email")
var ErrDuplicateUsername = errors.New("duplicate username")
type Account struct {
ID int32
Username string
Password []byte
Color int32
Firstname string
Lastname string
Email string
Created time.Time
StripeID string
}
type SubscriptionStatus string
const (
incomplete SubscriptionStatus = "incomplete"
incomplete_expired SubscriptionStatus = "incomplete_expired"
trialing SubscriptionStatus = "trialing"
active SubscriptionStatus = "active"
past_due SubscriptionStatus = "past_due"
canceled SubscriptionStatus = "canceled"
unpaid SubscriptionStatus = "unpaid"
paused SubscriptionStatus = "paused"
)
type Subscription struct {
ID int32
AccountID int32
StripeSubscriptionID string
StripeCheckoutID string
Status SubscriptionStatus
}
type Usermodel struct {
DB *sql.DB
}
type SubscriptionModel struct {
DB *sql.DB
}
func (m *Usermodel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {
//hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
log.Println(err)
return 0, err
}
//log.Println(hashedpassword)
stmt := `INSERT INTO accounts (username, password, firstname, lastname, email, created) VALUES ($1, $2, $3, $4, $5, NOW()) RETURNING id`
var insertid int32
row := m.DB.QueryRow(stmt, username, string(hashedpassword), firstname, lastname, email)
if row.Err() != nil {
log.Println(row.Err())
return 0, row.Err()
}
err = row.Scan(&insertid)
if err != nil {
log.Println(err)
return 0, err
}
params := &stripe.CustomerParams{
Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)),
Email: stripe.String(email),
}
customer, err := customer.New(params)
if err != nil {
log.Println(err)
return 0, err
}
stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`
//log.Println(customer.ID, insertid)
_, err = m.DB.Exec(stmt, customer.ID, insertid)
if err != nil {
log.Println(err)
return 0, err
}
return insertid, nil
}
func (m *Usermodel) Delete(id int32) error {
account, err := users.GetAccount(id)
if err != nil {
log.Println(err)
return err
}
if account.StripeID != "" {
/*result*/ _, err := customer.Del(account.StripeID, nil)
if err != nil {
log.Println(err)
}
//log.Println(result)
}
stmt := `DELETE FROM accounts WHERE id = $1`
_, err = m.DB.Exec(stmt, id)
if err != nil {
log.Println(err)
}
return nil
}
func (m *Usermodel) GetAccount(id int32) (Account, error) {
if id == 0 {
return Account{}, ErrNoRecord
}
stmt := `SELECT id, username, password, color, firstname, lastname, email, created, stripe_id FROM accounts WHERE id = $1`
row := m.DB.QueryRow(stmt, id)
var account Account
err := row.Scan(&account.ID, &account.Username, &account.Password, &account.Color, &account.Firstname, &account.Lastname, &account.Email, &account.Created, &account.StripeID)
if err == sql.ErrNoRows {
return Account{}, sql.ErrNoRows
} else if err != nil {
return Account{}, err
}
return account, nil
}
func (m *Usermodel) Authenticate(username string, password string) (int32, error) {
var id int32
var hashedpassword string
row := m.DB.QueryRow("SELECT id, password FROM accounts WHERE username = $1", username)
err := row.Scan(&id, &hashedpassword)
if err == sql.ErrNoRows {
return 0, ErrInvalidCredentials
}
match, err := argon2id.ComparePasswordAndHash(password, hashedpassword)
if !match {
return 0, ErrInvalidCredentials
} else if err != nil {
return 0, err
}
return id, nil
}
func (m *Usermodel) ExistsAccount(id int32) bool {
var exists bool
stmt := `SELECT EXISTS(SELECT 1 FROM accounts WHERE id = $1)`
row := m.DB.QueryRow(stmt, id)
if row.Err() != nil {
log.Println(row.Err())
}
row.Scan(&exists)
//log.Println(exists)
return exists
}
func (m *SubscriptionModel) Insert(stripeid string, stripesubscriptionid string, stripecheckoutid string, status stripe.SubscriptionStatus) (int32, error) {
var id int32
stmt := `SELECT id FROM accounts WHERE stripe_id = $1`
row := m.DB.QueryRow(stmt, stripeid)
if row.Err() != nil {
log.Println(row.Err())
return 0, row.Err()
}
err := row.Scan(&id)
if err != nil {
log.Println(err)
return 0, err
}
stmt = `INSERT INTO subscriptions (account_id, stripe_subscription_id, stripe_checkout_id, status) VALUES ($1, $2, $3, $4::subscription_status) RETURNING id`
var insertid int32
row = m.DB.QueryRow(stmt, id, string(stripesubscriptionid), string(stripecheckoutid), string(status))
if row.Err() != nil {
log.Println(row.Err())
return 0, row.Err()
}
err = row.Scan(&insertid)
if err != nil {
log.Println(err)
return 0, err
}
return insertid, nil
}
func (m *SubscriptionModel) Delete(id int32) error {
stmt := `DELETE FROM accounts WHERE id = $1`
_, err := m.DB.Exec(stmt, id)
if err != nil {
log.Println(err)
return err
}
return nil
}
func (m *SubscriptionModel) GetSubscription(id int32) (Subscription, error) {
if id == 0 {
return Subscription{}, ErrNoRecord
}
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE id = $1`
row := m.DB.QueryRow(stmt, id)
var subscription Subscription
err := row.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
if err == sql.ErrNoRows {
return Subscription{}, sql.ErrNoRows
} else if err != nil {
return Subscription{}, err
}
//log.Println(subscription.Status)
return subscription, nil
}
func (m *SubscriptionModel) GetSubscriptionsFromAccount(accountid int32) ([]Subscription, error) {
if accountid == 0 {
return nil, ErrNoRecord
}
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE account_id = $1`
rows, err := m.DB.Query(stmt, accountid)
if err != nil {
return nil, err
}
defer rows.Close()
var subscriptions []Subscription
for rows.Next() {
var subscription Subscription
err := rows.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
if err == sql.ErrNoRows {
return nil, sql.ErrNoRows
} else if err != nil {
return nil, err
}
subscriptions = append(subscriptions, subscription)
}
return subscriptions, nil
}
func (m *SubscriptionModel) HasActiveSubscription(accountid int32) bool {
subscriptions, err := m.GetSubscriptionsFromAccount(accountid)
if err != nil {
return false
}
for _, v := range subscriptions {
if v.Status == active {
return true
} else if v.Status == trialing {
return true
}
}
return false
}

48
pkg/models/models.go Normal file
View File

@ -0,0 +1,48 @@
//
// Created by vfs on 02.05.2024.
//
package models
import (
"errors"
"time"
)
var ErrNoRecord = errors.New("no matching record found")
var ErrInvalidCredentials = errors.New("invalid credentials")
var ErrDuplicateEmail = errors.New("duplicate email")
var ErrDuplicateUsername = errors.New("duplicate username")
type Account struct {
ID int32
Username string
Password []byte
Colour int32
Firstname string
Lastname string
Email string
Created time.Time
StripeID string
}
type SubscriptionStatus string
const (
Incomplete SubscriptionStatus = "incomplete"
IncompleteExpired SubscriptionStatus = "incomplete_expired"
Trialing SubscriptionStatus = "trialing"
Active SubscriptionStatus = "active"
PastDue SubscriptionStatus = "past_due"
Canceled SubscriptionStatus = "canceled"
Unpaid SubscriptionStatus = "unpaid"
Paused SubscriptionStatus = "paused"
)
type Subscription struct {
ID int32
AccountID int32
StripeSubscriptionID string
StripeCheckoutID string
Status SubscriptionStatus
}

View File

@ -0,0 +1,138 @@
package postgresql
import (
"context"
"fmt"
"log"
"github.com/jackc/pgx/v5"
"github.com/alexedwards/argon2id"
"github.com/stripe/stripe-go/v83"
"github.com/stripe/stripe-go/v83/customer"
"alfheimgame.com/alfheim/pkg/models"
)
type AccountModel struct {
DB *pgx.Conn
}
func (m *AccountModel) Insert(username string, password string, firstname string, lastname string, email string) (int32, error) {
//hashedpassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
hashedpassword, err := argon2id.CreateHash(password, argon2id.DefaultParams)
if err != nil {
log.Println(err)
return 0, err
}
//log.Println(hashedpassword)
stmt := `INSERT INTO accounts (username, password, firstname, lastname, email) VALUES ($1, $2, $3, $4, $5) RETURNING id`
var insertid int32
err = m.DB.QueryRow(context.Background(), stmt, username, string(hashedpassword), firstname, lastname, email).Scan(&insertid)
if err != nil {
log.Println(err)
return 0, err
}
params := &stripe.CustomerParams{
Name: stripe.String(fmt.Sprintf("%s %s", firstname, lastname)),
Email: stripe.String(email),
}
customer, err := customer.New(params)
if err != nil {
log.Println(err)
return 0, err
}
stmt = `UPDATE accounts SET stripe_id = $1 WHERE id = $2`
_, err = m.DB.Exec(context.Background(), stmt, customer.ID, insertid)
if err != nil {
log.Println(err)
return 0, err
}
return insertid, nil
}
func (m *AccountModel) Delete(id int32) error {
account, err := m.GetAccount(id)
if err != nil {
log.Println(err)
return err
}
if account.StripeID != "" {
/*result*/ _, err := customer.Del(account.StripeID, nil)
if err != nil {
log.Println(err)
}
//log.Println(result)
}
stmt := `DELETE FROM accounts WHERE id = $1`
_, err = m.DB.Exec(context.Background(), stmt, id)
if err != nil {
log.Println(err)
}
return nil
}
func (m *AccountModel) GetAccount(id int32) (models.Account, error) {
if id == 0 {
return models.Account{}, models.ErrNoRecord
}
var account models.Account
stmt := `SELECT id, username, password, colour, firstname, lastname, email, created, stripe_id FROM accounts WHERE id = $1`
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&account.ID, &account.Username, &account.Password, &account.Colour, &account.Firstname, &account.Lastname, &account.Email, &account.Created, &account.StripeID)
if err == pgx.ErrNoRows {
return models.Account{}, pgx.ErrNoRows
} else if err != nil {
return models.Account{}, err
}
return account, nil
}
func (m *AccountModel) Authenticate(username string, password string) (int32, error) {
var id int32
var hashedpassword string
stmt := `SELECT id, password FROM accounts WHERE username = $1`
err := m.DB.QueryRow(context.Background(), stmt, username).Scan(&id, &hashedpassword)
if err == pgx.ErrNoRows {
return 0, models.ErrInvalidCredentials
}
match, err := argon2id.ComparePasswordAndHash(password, hashedpassword)
if !match {
return 0, models.ErrInvalidCredentials
} else if err != nil {
return 0, err
}
return id, nil
}
func (m *AccountModel) ExistsAccount(id int32) bool {
var exists bool
stmt := `SELECT EXISTS(SELECT 1 FROM accounts WHERE id = $1)`
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&exists)
if err != nil {
log.Println(err)
}
//log.Println(exists)
return exists
}

View File

@ -0,0 +1,116 @@
package postgresql
import (
"context"
"github.com/jackc/pgx/v5"
"log"
"github.com/stripe/stripe-go/v83"
"alfheimgame.com/alfheim/pkg/models"
)
type SubscriptionModel struct {
DB *pgx.Conn
}
func (m *SubscriptionModel) Insert(stripeid string, stripesubscriptionid string, stripecheckoutid string, status stripe.SubscriptionStatus) (int32, error) {
var id int32
stmt := `SELECT id FROM accounts WHERE stripe_id = $1`
err := m.DB.QueryRow(context.Background(), stmt, stripeid).Scan(&id)
if err != nil {
log.Println(err)
return 0, err
}
stmt = `INSERT INTO subscriptions (account_id, stripe_subscription_id, stripe_checkout_id, status) VALUES ($1, $2, $3, $4::subscription_status) RETURNING id`
var insertid int32
err = m.DB.QueryRow(context.Background(), stmt, id, string(stripesubscriptionid), string(stripecheckoutid), string(status)).Scan(&insertid)
if err != nil {
log.Println(err)
return 0, err
}
return insertid, nil
}
func (m *SubscriptionModel) Delete(id int32) error {
stmt := `DELETE FROM accounts WHERE id = $1`
_, err := m.DB.Exec(context.Background(), stmt, id)
if err != nil {
log.Println(err)
return err
}
return nil
}
func (m *SubscriptionModel) GetSubscription(id int32) (models.Subscription, error) {
if id == 0 {
return models.Subscription{}, models.ErrNoRecord
}
var subscription models.Subscription
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE id = $1`
err := m.DB.QueryRow(context.Background(), stmt, id).Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
if err == pgx.ErrNoRows {
return models.Subscription{}, pgx.ErrNoRows
} else if err != nil {
return models.Subscription{}, err
}
return subscription, nil
}
func (m *SubscriptionModel) GetSubscriptionsFromAccount(accountid int32) ([]models.Subscription, error) {
if accountid == 0 {
return nil, models.ErrNoRecord
}
stmt := `SELECT id, account_id, stripe_subscription_id, stripe_checkout_id, status FROM subscriptions WHERE account_id = $1`
rows, err := m.DB.Query(context.Background(), stmt, accountid)
if err != nil {
return nil, err
}
defer rows.Close()
var subscriptions []models.Subscription
for rows.Next() {
var subscription models.Subscription
err := rows.Scan(&subscription.ID, &subscription.AccountID, &subscription.StripeSubscriptionID, &subscription.StripeCheckoutID, &subscription.Status)
if err == pgx.ErrNoRows {
return nil, pgx.ErrNoRows
} else if err != nil {
return nil, err
}
subscriptions = append(subscriptions, subscription)
}
return subscriptions, nil
}
func (m *SubscriptionModel) HasActiveSubscription(accountid int32) bool {
subscriptions, err := m.GetSubscriptionsFromAccount(accountid)
if err != nil {
return false
}
for _, v := range subscriptions {
switch v.Status {
case models.Active:
return true
case models.Trialing:
return true
}
}
return false
}

View File

@ -15,8 +15,8 @@ CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
EnvironmentFile=/etc/environment
WorkingDirectory=/home/alfheim/linux_amd64
#ExecStart=/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheim-website -- -production
ExecStart=/home/alfheim/linux_amd64/alfheim-website -production
ExecStart=/home/alfheim/go/bin/dlv --listen=:4321 --headless=true --log=true exec /home/alfheim/linux_amd64/alfheim-website -- -production
#ExecStart=/home/alfheim/linux_amd64/alfheim-website -production
Restart=on-failure
RestartSec=5

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#000000"><path d="M480-120v-80h280v-560H480v-80h280q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H480Zm-80-160-55-58 102-102H120v-80h327L345-622l55-58 200 200-200 200Z"/></svg>

Before

Width:  |  Height:  |  Size: 279 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 173 B

View File

@ -1,202 +0,0 @@
html {
/*background: no-repeat url(/static/image.png);
background-size: cover;
background-position: center;*/
height: 100vh;
background-color: black;
box-sizing: border-box;
}
body {
display: flex;
flex-direction: column;
justify-content: space-between;
background-color: #3475CB;
font-family: "Vollkorn";
color: white;
margin-top: 0px;
margin-bottom: 0px;
margin-left: auto;
margin-right: auto;
max-width: 900px;
min-height: 100%;
}
header {
}
nav {
padding-left: 8px;
padding-right: 8px;
display: flex;
align-items: center;
justify-content: space-between;
}
.navbuttons a {
display: inline-block;
}
.maintitle {
margin: 0px;
font-size: 6rem;
color: white;
}
@media (max-width: 1080px) {
.maintitle {
font-size: 4rem;
}
}
.loginbutton {
align-self: center;
display: flex;
border: 12px solid;
border-image-source: url("/static/panel-000.png");
border-image-slice: 12 fill;
padding-top: 15px;
padding-right: 20px;
padding-bottom: 15px;
padding-left: 20px;
color: black;
/*box-shadow: 10px 10px 5px lightblue;*/
}
main {
padding: 8px;
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100%;
max-height: 100%;
margin-left: auto;
margin-right: auto;
}
p {
text-align: center;
font-size: 1rem;
}
.account-wrapper {
background: transparent;
border: 2px solid white;
backdrop-filter: blur(20px);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 15px;
padding: 30px 40px;
font-size: 1.5rem;
}
.wrapper {
display: inline-block;
background: transparent;
border: 2px solid white;
backdrop-filter: blur(20px);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
border-radius: 15px;
padding: 30px 40px;
width: 80%;
text-align: center;
}
.wrapper h1 {
font-size: 1.5rem;
text-align: center;
}
.wrapper .input-box {
width: 100%;
}
.input-box input {
box-sizing: border-box;
background: transparent;
width: 100%;
height: 100%;
outline: none;
border: 2px solid rgba(255, 255, 255, .2);
border-radius: 25px;
padding: 10px 45px 10px 20px
}
.input-box input::placeholder {
color: white;
}
.wrapper .input-error {
width: 100%;
}
.error {
color: rgba(240, 0, 0, .8);
}
.input-error input {
box-sizing: border-box;
background: transparent;
width: 100%;
height: 100%;
outline: none;
border: 2px solid rgba(255, 0, 0, .2);
border-radius: 25px;
padding: 10px 45px 10px 20px
}
.input-error input::placeholder {
color: red;
}
.login-btn-wrapper {
display: flex;
justify-content: center;
}
.wrapper .btn {
width: 100%;
outline: none;
border: none;
border-radius: 20px;
}
.wrapper .register-link {
display: flex;
justify-content: center;
}
input {
font-family: inherit;
}
footer {
/*margin-top: auto;*/
width: 100%;
background-color: #132123;
color: white;
text-align: center;
}
a:link {
text-decoration: none;
}
.wrapper a:link {
text-decoration: none;
}
.wrapper a:visited {
text-decoration: none;
}
.wrapper a:hover {
text-decoration: underline;
}
video {
width: 100%;
height: auto;
}

Binary file not shown.

View File

@ -1,22 +0,0 @@
{{define "body"}}
<div class="account-wrapper">
<div>Username: {{.Account.Username}}</div>
<div>First name: {{.Account.Firstname}}</div>
<div>Last name: {{.Account.Lastname}}</div>
<div>Color: {{.Account.Color}}</div>
</div>
<div class="wrapper">
<form action="/deleteaccount" method="post">
<div class="login-btn-wrapper">
<input type="submit" value="Delete Account" class="btn">
</div>
</form>
<form method="POST" action="/managebilling">
<div class="login-btn-wrapper">
<input type="submit" value="Manage Billing" class="btn">
</div>
</form>
</div>
{{end}}

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Alfheim</title>
<meta charset="utf-8" />
<meta name="description" content="A handcrafted economy and politics MMO." />
<meta name="author" content="Vicente Ferrari Smith" />
<meta name="keywords" content="Alfheim, indie, video game, mmo, colony, colony simulator, vicente ferrari smith, game, economy, politics, alfheim" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
@font-face {
font-family: "Vollkorn";
src: url(/static/Vollkorn-VariableFont_wght.ttf);
}
</style>
<link href="/static/style.css" rel="stylesheet" />
</head>
<body>
<header>
<nav>
<a href="/"><h1 class="maintitle">Alfheim</h1></a>
<div class="navbuttons">
{{if not .ActiveSubscription}}
<a href="/subscribe"><div class="loginbutton"><strong>Subscribe</strong></div></a>
{{end}}
{{if .AuthenticatedUser}}
<a href="/account"><div class="loginbutton"><strong>Account</strong></div></a>
<a href="/logout"><div class="loginbutton"><strong>Log out</strong></div></a>
{{else}}
<a href="/login"><div class="loginbutton"><strong>Log in</strong><img src="/static/login_24dp_FILL0_wght400_GRAD0_opsz24.svg" alt=""/></div></a>
<a href="/register"><div class="loginbutton"><strong>Register</strong></div></a>
{{end}}
</div>
</nav>
</header>
<main>
{{template "body" .}}
</main>
<footer>
Alfheim &copy; 2025, Vicente Ferrari Smith, Los Andes Games, Vienna, Austria. All rights reserved.
</footer>
</body>
</html>

View File

@ -3,7 +3,7 @@
<div>Username: {{.Account.Username}}</div>
<div>First name: {{.Account.Firstname}}</div>
<div>Last name: {{.Account.Lastname}}</div>
<div>Color: {{.Account.Color}}</div>
<div>Colour: {{.Account.Colour}}</div>
</div>
<div class="wrapper">

View File

@ -1,9 +0,0 @@
{{define "body"}}
<p>Alfheim is a game about humanity: our own will, and our place in the world at large.</p>
<p>Alfheim is an MMO where you&mdash;the player&mdash;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}}

View File

@ -1,39 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Log in</h1>
{{with .FormErrors.generic}}
<label class="error">{{.}}</label>
{{end}}
{{with .FormErrors.username}}
<label class="error">{{.}}</label>
{{end}}
<div {{if .FormErrors.username}}class="input-error"{{else}}class="input-box"{{end}}>
<input type="text" id="username" name="username" placeholder="Username" required>
</div>
<br />
{{with .FormErrors.password}}
<label class="error">{{.}}</label>
{{end}}
<div {{if .FormErrors.password}}class="input-error"{{else}}class="input-box"{{end}}>
<input type="password" id="password" name="password" placeholder="Password" required>
</div>
<br />
<div class="login-btn-wrapper">
<input type="submit" value="Log in" class="btn">
</div>
<br />
<a href="/forgotten">Have you forgotten your password?</a>
<div class="register-link">
<p>Don't have an account? <a href="/register">Register</a></p>
</div>
</form>
</div>
{{end}}

View File

@ -1,11 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Log out</h1>
<div class="login-btn-wrapper">
<input type="submit" value="Log out" class="btn">
</div>
</form>
</div>
{{end}}

View File

@ -1,45 +0,0 @@
{{define "body"}}
<div class="wrapper">
<form method="POST">
<h1>Register</h1>
{{with .FormErrors.username}}
<label class="error">{{.}}</label>
{{end}}
<div class="input-box" {{with .FormErrors.username}}class="input-error"{{end}}>
<input type="text" id="username" name="username" placeholder="Username" required>
</div>
<br />
<div class="input-box">
<input type="email" id="email" name="email" placeholder="Email" required>
</div>
<br />
<div class="input-box">
<input type="text" id="firstname" name="firstname" placeholder="First Name" required>
</div>
<br />
<div class="input-box">
<input type="text" id="lastname" name="lastname" placeholder="Last Name" required>
</div>
<br />
{{with .FormErrors.password}}
<label class="error">{{.}}</label>
{{end}}
<div class="input-box" {{with .FormErrors.password}}class="input-error"{{end}}>
<input type="password" id="password" name="password" placeholder="Password" required>
</div>
<br />
<div class="login-btn-wrapper">
<input type="submit" value="Register" class="btn">
</div>
</form>
</div>
{{end}}

View File

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 83 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 279 B

After

Width:  |  Height:  |  Size: 279 B

View File

Before

Width:  |  Height:  |  Size: 173 B

After

Width:  |  Height:  |  Size: 173 B

View File

@ -1,8 +0,0 @@
{{define "body"}}
{{range .Prices}}
<div class="wrapper">
{{$price := divide .UnitAmountDecimal 100}}
{{.Currency}} {{printf "%.2f" $price}}
</div>
{{end}}
{{end}}

View File

@ -1,5 +0,0 @@
{{define "body"}}
<script async src="https://js.stripe.com/v3/pricing-table.js"></script>
<stripe-pricing-table pricing-table-id="prctbl_1PIaJpKUHKCjyTmcy1ONKQiT" publishable-key="pk_test_51PGebgKUHKCjyTmcyuhj7C5lfHfKFLCxRJ2opoqxL3mGEHSRaMvOHYKKgX4MdFfESW78dssjyunboUcFhg3LTwmn005PmzVIXw" customer-session-client-secret={{.ClientSecret}}>
</stripe-pricing-table>
{{end}}