Add activation-codes and registration. Added tooltips. Added player-score-cache-display in mainpage.
This commit is contained in:
27
auth.go
27
auth.go
@@ -2,9 +2,12 @@ package main
|
||||
|
||||
import (
|
||||
"InfrantrySkillCalculator/models"
|
||||
"InfrantrySkillCalculator/utils"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/gorm"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
@@ -25,7 +28,9 @@ func getUserPassword(username string) (string, error) {
|
||||
var user models.User
|
||||
|
||||
if err := models.DB.Where("username = ?", username).First(&user).Error; err != nil {
|
||||
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -58,3 +63,25 @@ func AuthRequired() gin.HandlerFunc {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func isValidCode(code string) bool {
|
||||
var activationCode models.ActivationCode
|
||||
if err := models.DB.Where("code = ?", code).First(&activationCode).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if activationCode.Code == code && !activationCode.Used {
|
||||
models.DB.Model(&activationCode).Updates(map[string]interface{}{
|
||||
"Code": code,
|
||||
"Used": true,
|
||||
})
|
||||
|
||||
newCode := utils.GenerateActivationCode()
|
||||
newCodeObj := models.ActivationCode{Code: newCode, Used: false}
|
||||
models.DB.Create(&newCodeObj)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func GetPlayersByClanHTML(c *gin.Context) {
|
||||
|
||||
var htmlOptions string
|
||||
for _, player := range players {
|
||||
htmlOptions += fmt.Sprintf(playerItem, player.Name, player.ID, player.ID)
|
||||
htmlOptions += fmt.Sprintf(playerItem, player.Name, "----", player.ID, player.ID)
|
||||
}
|
||||
|
||||
c.Header("Content-Type", "text/html")
|
||||
|
||||
6
main.go
6
main.go
@@ -20,6 +20,10 @@ func main() {
|
||||
//gin.SetMode(gin.ReleaseMode) // uncomment upon release
|
||||
|
||||
router := gin.New()
|
||||
err := router.SetTrustedProxies([]string{"127.0.0.1"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
router.LoadHTMLGlob("templates/**/*")
|
||||
protected := router.Group("/")
|
||||
protected.Use(AuthRequired())
|
||||
@@ -38,6 +42,8 @@ func main() {
|
||||
router.GET("/login", loginPage)
|
||||
router.POST("/login", loginPost)
|
||||
router.GET("/logout", logout)
|
||||
router.GET("/register", registerPage)
|
||||
router.POST("/register", registerPost)
|
||||
|
||||
protected.GET("/", mainPage)
|
||||
|
||||
|
||||
6
models/code.go
Normal file
6
models/code.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package models
|
||||
|
||||
type ActivationCode struct {
|
||||
Code string
|
||||
Used bool
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"InfrantrySkillCalculator/utils"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/logger"
|
||||
@@ -29,7 +30,21 @@ func ConnectDatabase() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
//database.AutoMigrate(&PlayerCache{})
|
||||
//database.AutoMigrate(&User{})
|
||||
err = database.AutoMigrate(&User{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = database.AutoMigrate(&ActivationCode{})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
var code ActivationCode
|
||||
if err := database.First(&code).Error; err != nil {
|
||||
firstCode := utils.GenerateActivationCode()
|
||||
database.Create(&ActivationCode{Code: firstCode, Used: false})
|
||||
log.Println("Created first activation code: " + firstCode)
|
||||
}
|
||||
}
|
||||
//database.AutoMigrate(&Game{})
|
||||
//database.AutoMigrate(&MetricSettings{})
|
||||
|
||||
|
||||
45
pages.go
45
pages.go
@@ -94,3 +94,48 @@ func logout(c *gin.Context) {
|
||||
|
||||
c.Redirect(http.StatusFound, "/login")
|
||||
}
|
||||
|
||||
func registerPage(c *gin.Context) {
|
||||
session, _ := store.Get(c.Request, LoginSessionName)
|
||||
|
||||
if auth, ok := session.Values["authenticated"].(bool); ok && auth {
|
||||
c.Redirect(http.StatusFound, "/")
|
||||
return
|
||||
}
|
||||
|
||||
tmpl, err := template.ParseFiles([]string{"./templates/register.html", "./templates/components/header.html"}...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = tmpl.Execute(c.Writer, nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func registerPost(c *gin.Context) {
|
||||
username := c.PostForm("username")
|
||||
password := c.PostForm("password")
|
||||
code := c.PostForm("code")
|
||||
|
||||
if !isValidCode(code) {
|
||||
c.HTML(http.StatusOK, "login_error.html", gin.H{"message": "Ungültiger Aktivierungscode!"})
|
||||
return
|
||||
}
|
||||
|
||||
user := models.User{Username: username, Password: password, Enabled: true}
|
||||
models.DB.Create(&user)
|
||||
|
||||
session, _ := store.Get(c.Request, LoginSessionName)
|
||||
session.Values["authenticated"] = true
|
||||
session.Values["username"] = username
|
||||
err := session.Save(c.Request, c.Writer)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, nil)
|
||||
return
|
||||
}
|
||||
|
||||
c.Header("HX-Redirect", "/")
|
||||
c.String(http.StatusOK, "")
|
||||
}
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]');
|
||||
tooltipTriggerList.forEach((elem) => {
|
||||
new bootstrap.Tooltip(elem);
|
||||
});
|
||||
});
|
||||
|
||||
function setupClanButtons(dropdownId, delBtnId, editBtnId) {
|
||||
const dropdown = document.getElementById(dropdownId);
|
||||
const deleteButton = document.getElementById(delBtnId);
|
||||
|
||||
@@ -1,14 +1,26 @@
|
||||
{{ define "bottom_controls" }}
|
||||
|
||||
<div class="row justify-content-between border-top pt-4">
|
||||
<div class="col-md-auto">
|
||||
<button class="btn btn-lg btn-outline-secondary text-secondary-emphasis w-100"><i class="bi bi-gear-fill me-2"></i>Einstellungen</button>
|
||||
<div class="row justify-content-between border-top pt-4 position-relative mb-5">
|
||||
<div class="col-auto position-absolute start-0">
|
||||
<a class="btn btn-lg btn-outline-secondary text-secondary-emphasis" href="/logout" data-bs-toggle="tooltip" data-bs-title="Abmelden">
|
||||
<i class="bi bi-door-closed"></i>
|
||||
</a>
|
||||
<button class="btn btn-lg btn-outline-secondary text-secondary-emphasis">
|
||||
<i class="bi bi-gear-fill me-2"></i>
|
||||
Einstellungen
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<button class="btn btn-lg btn-outline-primary w-100"><i class="bi bi-calculator-fill me-2"></i>Berechnen</button>
|
||||
<div class="col-auto position-absolute start-50 translate-middle-x">
|
||||
<button class="btn btn-lg btn-outline-primary">
|
||||
<i class="bi bi-calculator-fill me-2"></i>
|
||||
Berechnen
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<button class="btn btn-lg btn-outline-secondary text-secondary-emphasis w-100"><i class="bi bi-person me-2"></i>Einzel-Abfrage</button>
|
||||
<div class="col-auto position-absolute end-0">
|
||||
<button class="btn btn-lg btn-outline-secondary text-secondary-emphasis">
|
||||
<i class="bi bi-person me-2"></i>
|
||||
Einzel-Abfrage
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<!-- Bootstrap 5 CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<link rel="stylesheet" href="../static/index.css">
|
||||
|
||||
{{ end }}
|
||||
@@ -11,13 +11,13 @@
|
||||
<!-- Options will be loaded dynamically -->
|
||||
</select>
|
||||
<button class="btn btn-lg btn-outline-secondary text-danger bg-secondary-subtle" type="button" id="home-delete" data-bs-toggle="modal" data-bs-list="#home-clan" data-bs-target="#deleteClanModal" disabled>
|
||||
<i class="bi bi-trash3"></i>
|
||||
<i class="bi bi-trash3" data-bs-toggle="tooltip" data-bs-title="Clan löschen"></i>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-secondary text-primary bg-secondary-subtle" type="button" data-bs-toggle="modal" data-bs-list="#home-clan" data-bs-target="#editClanModal" id="home-edit" disabled>
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
<i class="bi bi-pencil-fill" data-bs-toggle="tooltip" data-bs-title="Clan bearbeiten"></i>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-secondary text-success" type="button" data-bs-toggle="modal" data-bs-list="#home-clan" data-bs-target="#addClanModal" id="home-add">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
<i class="bi bi-plus-lg" data-bs-toggle="tooltip" data-bs-title="Clan hinzufügen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,21 +2,25 @@
|
||||
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div id="home-player-list" class="border rounded p-1 overflow-auto" style="height: 47vh;">
|
||||
<div id="home-player-list" class="border rounded p-1 overflow-auto" style="height: 47vh;" oninput="updateTooltips(this)">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto ps-0">
|
||||
<div class="btn-group-vertical btn-group-lg" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="selectAllPlayers('home-player-list')"><i class="bi bi-check-square fs-4"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="deselectAllPlayers('home-player-list')"><i class="bi bi-square fs-4"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="selectAllPlayers('home-player-list')">
|
||||
<i class="bi bi-check-square fs-4" data-bs-toggle="tooltip" data-bs-title="Alle auswählen"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="deselectAllPlayers('home-player-list')">
|
||||
<i class="bi bi-square fs-4" data-bs-toggle="tooltip" data-bs-title="Nichts auswählen"></i>
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<button type="button" class="btn btn-outline-secondary text-success px-3 mt-2 bg-secondary-subtle" id="home-player-add" data-bs-toggle="modal" data-bs-list="#home-player-list" data-bs-target="#addPlayerModal" disabled>
|
||||
<i class="bi bi-person-add fs-4"></i>
|
||||
<i class="bi bi-person-add fs-4" data-bs-toggle="tooltip" data-bs-title="Spieler hinzufügen"></i>
|
||||
</button>
|
||||
<br>
|
||||
<div class="vstack text-center border border-secondary rounded mt-2">
|
||||
<div class="vstack text-center border border-secondary rounded mt-2" data-bs-toggle="tooltip" data-bs-title="Ausgwählte Spieler">
|
||||
<i class="bi bi-ui-checks fs-4 mt-2 mb-1"></i>
|
||||
<span class="badge fs-5 mb-2" id="home-player-selected">0</span>
|
||||
</div>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<option disabled selected value>Auswählen...</option>
|
||||
</select>
|
||||
<button class="btn btn-lg btn-outline-secondary text-danger bg-secondary-subtle" type="button" id="opponent-delete" data-bs-toggle="modal" data-bs-list="#opponent-clan" data-bs-target="#deleteClanModal" disabled>
|
||||
<i class="bi bi-trash3"></i>
|
||||
<i class="bi bi-trash3" data-bs-toggle="tooltip" data-bs-title="Clan löschen"></i>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-secondary text-primary bg-secondary-subtle" type="button" data-bs-toggle="modal" data-bs-list="#opponent-clan" data-bs-target="#editClanModal" id="opponent-edit" disabled>
|
||||
<i class="bi bi-pencil-fill"></i>
|
||||
<i class="bi bi-pencil-fill" data-bs-toggle="tooltip" data-bs-title="Clan bearbeiten"></i>
|
||||
</button>
|
||||
<button class="btn btn-lg btn-outline-secondary text-success" type="button" data-bs-toggle="modal" data-bs-list="#opponent-clan" data-bs-target="#addClanModal" id="opponent-add">
|
||||
<i class="bi bi-plus-lg"></i>
|
||||
<i class="bi bi-plus-lg" data-bs-toggle="tooltip" data-bs-title="Clan hinzufügen"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,15 +8,19 @@
|
||||
</div>
|
||||
<div class="col-auto ps-0">
|
||||
<div class="btn-group-vertical btn-group-lg" role="group">
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="selectAllPlayers('opponent-player-list')"><i class="bi bi-check-square fs-4"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="deselectAllPlayers('opponent-player-list')"><i class="bi bi-square fs-4"></i></button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="selectAllPlayers('opponent-player-list')">
|
||||
<i class="bi bi-check-square fs-4" data-bs-toggle="tooltip" data-bs-title="Alle auswählen"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-secondary text-primary-emphasis" onclick="deselectAllPlayers('opponent-player-list')">
|
||||
<i class="bi bi-square fs-4" data-bs-toggle="tooltip" data-bs-title="Nichts auswählen"></i>
|
||||
</button>
|
||||
</div>
|
||||
<br>
|
||||
<button type="button" class="btn btn-outline-secondary text-success px-3 mt-2 bg-secondary-subtle" id="opponent-player-add" data-bs-toggle="modal" data-bs-list="#opponent-player-list" data-bs-target="#addPlayerModal" disabled>
|
||||
<i class="bi bi-person-add fs-4"></i>
|
||||
<i class="bi bi-person-add fs-4" data-bs-toggle="tooltip" data-bs-title="Spieler hinzufügen"></i>
|
||||
</button>
|
||||
<br>
|
||||
<div class="vstack text-center border border-secondary rounded mt-2">
|
||||
<div class="vstack text-center border border-secondary rounded mt-2" data-bs-toggle="tooltip" data-bs-title="Spieler hinzufügen">
|
||||
<i class="bi bi-ui-checks fs-4 mt-2 mb-1"></i>
|
||||
<span class="badge fs-5 mb-2" id="opponent-player-selected">0</span>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<div class="row">
|
||||
<!-- Home-Clan Column -->
|
||||
<div class="col-md-6 d-flex flex-column border-end px-3 pb-4">
|
||||
<h4 class="text-center mt-2 pb-3 mb-3 border-bottom">Heim-Team</h4>
|
||||
<h4 class="text-center mt-2 pb-3 mb-3 border-bottom"><i class="bi bi-people me-3"></i>Heim-Team</h4>
|
||||
<!-- Clan Selection -->
|
||||
{{ template "home_clan_bar" . }}
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
<!-- Opponent-Clan Column -->
|
||||
<div class="col-md-6 d-flex flex-column px-3 pb-4">
|
||||
<h4 class="text-center mt-2 pb-3 mb-3 border-bottom">Gegner-Team</h4>
|
||||
<h4 class="text-center mt-2 pb-3 mb-3 border-bottom"><i class="bi bi-people me-3"></i>Gegner-Team</h4>
|
||||
<!-- Clan Selection -->
|
||||
{{ template "opp_clan_bar" . }}
|
||||
|
||||
@@ -48,11 +48,8 @@
|
||||
<script lang="javascript">
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadClans();
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- Bootstrap 5 JS Bundle with Popper -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<div class="container-fluid bg-dark mt-5 p-4 rounded-3 text-light text-center" style="max-width: 500px;">
|
||||
<h3>Login</h3>
|
||||
<hr>
|
||||
<form hx-post="/login" hx-target="#login-result">
|
||||
<form hx-post="/login" hx-target="#login-result" class="position-relative">
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" type="text" id="username" name="username" placeholder="Username" required>
|
||||
<label for="username">Username</label>
|
||||
@@ -16,9 +16,10 @@
|
||||
<input class="form-control form-control-lg" type="password" id="password" name="password" placeholder="Passwort" required>
|
||||
<label for="password">Passwort</label>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary mt-3" type="submit">Anmelden</button>
|
||||
<button class="btn btn-lg btn-primary mt-4" type="submit"><i class="bi bi-door-open me-3"></i>Anmelden</button>
|
||||
<a class="btn btn-lg btn-outline-primary mt-4 ms-2 position-absolute end-0" href="/register" data-bs-toggle="tooltip" data-bs-title="Zur Registrierung"><i class="bi bi-key"></i></a>
|
||||
</form>
|
||||
<div id="login-result" class="mt-4 fs-4"></div>
|
||||
<div id="login-result" class="fs-4 mt-2"></div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,8 +1,11 @@
|
||||
<div class="input-group input-group-lg mb-1">
|
||||
<div class="input-group-text py-1 px-2">
|
||||
<input class="form-check-input fs-4 border-secondary mt-0" type="checkbox" value="" onchange="updateSelectedPlayers(this)">
|
||||
<input class="form-check-input fs-4 border-secondary mt-0" type="checkbox" value="" onchange="updateSelectedPlayers(this)" data-bs-toggle="tooltip" data-bs-title="In Berechnung einbeziehen">
|
||||
</div>
|
||||
<span class="form-control py-1 px-2">%s</span>
|
||||
<span class="form-control py-2 px-3 w-50">%s</span>
|
||||
<span class="form-control text-center px-1 text-secondary">
|
||||
<i class="bi bi-trophy me-2"></i>%s
|
||||
</span>
|
||||
<button class="btn btn-outline-secondary text-secondary-emphasis dropdown-toggle py-1" type="button" data-bs-toggle="dropdown"></button>
|
||||
<ul class="dropdown-menu dropdown-menu-end">
|
||||
<li><a class="dropdown-item text-primary fs-5" href="#" data-bs-toggle="modal" data-bs-id="%d" data-bs-target="#editPlayerModal">
|
||||
|
||||
56
templates/register.html
Normal file
56
templates/register.html
Normal file
@@ -0,0 +1,56 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
{{ template "header" . }}
|
||||
</head>
|
||||
<body data-bs-theme="dark" class="h-auto">
|
||||
<div class="container-fluid bg-dark mt-5 p-4 rounded-3 text-light text-center" style="max-width: 500px;">
|
||||
<h3>Registrierung</h3>
|
||||
<hr>
|
||||
<form hx-post="/register" hx-target="#register-result" class="position-relative needs-validation" novalidate>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" type="text" minlength="4" id="username" name="username" placeholder="Username" required>
|
||||
<label for="username">Username</label>
|
||||
<div class="invalid-feedback">
|
||||
Mindestlänge: 4 Zeichen
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating mb-3">
|
||||
<input class="form-control form-control-lg" type="password" minlength="8" id="password" name="password" placeholder="Passwort" required>
|
||||
<label for="password">Passwort</label>
|
||||
<div class="invalid-feedback">
|
||||
Mindestlänge: 8 Zeichen
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-floating">
|
||||
<input class="form-control form-control-lg" type="password" minlength="32" maxlength="32" id="code" name="code" placeholder="Aktivierungscode" required>
|
||||
<label for="code">Aktivierungscode</label>
|
||||
<div class="invalid-feedback">
|
||||
Ungültiges Format
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn btn-lg btn-primary mt-4" type="submit"><i class="bi bi-key me-3"></i>Registrieren</button>
|
||||
<a class="btn btn-lg btn-outline-primary mt-4 ms-2 position-absolute end-0" href="/login" data-bs-toggle="tooltip" data-bs-title="Zurück zur Anmeldung"><i class="bi bi-door-open"></i></a>
|
||||
</form>
|
||||
<div id="register-result" class="fs-4 mt-2"></div>
|
||||
</div>
|
||||
</body>
|
||||
<script>
|
||||
(() => {
|
||||
'use strict'
|
||||
|
||||
const forms = document.querySelectorAll('.needs-validation')
|
||||
|
||||
Array.from(forms).forEach(form => {
|
||||
form.addEventListener('submit', event => {
|
||||
if (!form.checkValidity()) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
|
||||
form.classList.add('was-validated')
|
||||
}, false)
|
||||
})
|
||||
})()
|
||||
</script>
|
||||
</html>
|
||||
14
utils/misc.go
Normal file
14
utils/misc.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
func GenerateActivationCode() string {
|
||||
bytes := make([]byte, 16)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
panic(err) // Handle the error appropriately in production
|
||||
}
|
||||
return hex.EncodeToString(bytes)
|
||||
}
|
||||
Reference in New Issue
Block a user