Compare commits
2 Commits
8137f121ed
...
2ffd1f439f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ffd1f439f | ||
|
|
ad599ae3f4 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -40,7 +40,6 @@ go.work
|
|||||||
!go.sum
|
!go.sum
|
||||||
!go.mod
|
!go.mod
|
||||||
#!*.sql
|
#!*.sql
|
||||||
|
|
||||||
!README.md
|
!README.md
|
||||||
!LICENSE
|
!LICENSE
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build: .
|
build: ./go-backend
|
||||||
container_name: carsharingBackend
|
container_name: carsharingBackend
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
volumes:
|
volumes:
|
||||||
- ./configs/config.json:/root/configs/config.json:ro
|
- ./go-backend/configs/config.json:/root/configs/config.json:ro
|
||||||
- ./data/db.sqlite3:/root/data/db.sqlite3
|
- ./go-backend/data/db.sqlite3:/root/data/db.sqlite3
|
||||||
- ./templates:/root/templates:ro
|
- ./go-backend/templates:/root/templates:ro
|
||||||
|
|||||||
@@ -127,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#if isUpdating}
|
{#if isUpdating}
|
||||||
<SmallLoader width={30} message={'Aktualisiere...'} />
|
<SmallLoader width={30} message={$t('loading.updating')} />
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
|
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
|
||||||
{$t('cancel')}</button
|
{$t('cancel')}</button
|
||||||
|
|||||||
@@ -234,7 +234,7 @@
|
|||||||
options={userStatusOptions}
|
options={userStatusOptions}
|
||||||
readonly={role_id === 0}
|
readonly={role_id === 0}
|
||||||
/>
|
/>
|
||||||
{#if localUser.role_id === 8}
|
{#if role_id === 8}
|
||||||
<InputField
|
<InputField
|
||||||
name="user[role_id]"
|
name="user[role_id]"
|
||||||
type="select"
|
type="select"
|
||||||
@@ -261,7 +261,7 @@
|
|||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="user[first_name]"
|
name="user[first_name]"
|
||||||
label={$t('first_name')}
|
label={$t('user.first_name')}
|
||||||
bind:value={localUser.first_name}
|
bind:value={localUser.first_name}
|
||||||
placeholder={$t('placeholder.first_name')}
|
placeholder={$t('placeholder.first_name')}
|
||||||
required={true}
|
required={true}
|
||||||
@@ -322,7 +322,7 @@
|
|||||||
bind:value={localUser.city}
|
bind:value={localUser.city}
|
||||||
placeholder={$t('placeholder.city')}
|
placeholder={$t('placeholder.city')}
|
||||||
/>
|
/>
|
||||||
{#if localUser.role_id > 0}
|
{#if role_id > 0}
|
||||||
<InputField
|
<InputField
|
||||||
name="user[notes]"
|
name="user[notes]"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -528,7 +528,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#if isUpdating}
|
{#if isUpdating}
|
||||||
<SmallLoader width={30} message={'Aktualisiere...'} />
|
<SmallLoader width={30} message={$t('loading.updating')} />
|
||||||
{:else}
|
{:else}
|
||||||
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
|
<button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
|
||||||
{$t('cancel')}</button
|
{$t('cancel')}</button
|
||||||
|
|||||||
@@ -134,7 +134,9 @@ export default {
|
|||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
user_data: 'Lade Nutzerdaten',
|
user_data: 'Lade Nutzerdaten',
|
||||||
subscription_data: 'Lade Modelldaten'
|
subscription_data: 'Lade Modelldaten',
|
||||||
|
please_wait: 'Bitte warten...',
|
||||||
|
updating: 'Aktualisiere...'
|
||||||
},
|
},
|
||||||
dialog: {
|
dialog: {
|
||||||
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',
|
user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?',
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
<script>
|
<script>
|
||||||
import { applyAction, enhance } from '$app/forms';
|
import { applyAction, enhance } from '$app/forms';
|
||||||
|
import SmallLoader from '$lib/components/SmallLoader.svelte';
|
||||||
import { receive, send } from '$lib/utils/helpers';
|
import { receive, send } from '$lib/utils/helpers';
|
||||||
import { t } from 'svelte-i18n';
|
import { t } from 'svelte-i18n';
|
||||||
|
|
||||||
/** @type {import('./$types').ActionData} */
|
/** @type {import('./$types').ActionData} */
|
||||||
export let form;
|
export let form;
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
/** @type {import('./$types').SubmitFunction} */
|
/** @type {import('./$types').SubmitFunction} */
|
||||||
const handleRequestChange = async () => {
|
const handleRequestChange = async () => {
|
||||||
|
loading = true;
|
||||||
return async ({ result }) => {
|
return async ({ result }) => {
|
||||||
await applyAction(result);
|
await applyAction(result);
|
||||||
|
loading = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -40,6 +44,10 @@
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button class="button-dark">{$t('confirm')}</button>
|
{#if loading}
|
||||||
|
<SmallLoader width={30} message={$t('loading.please_wait')} />
|
||||||
|
{:else}
|
||||||
|
<button class="button-dark" disabled={loading}>{$t('confirm')}</button>
|
||||||
|
{/if}
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
56
go-backend/cmd/membership/main.go
Normal file
56
go-backend/cmd/membership/main.go
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"GoMembership/internal/config"
|
||||||
|
"GoMembership/internal/database"
|
||||||
|
"GoMembership/internal/server"
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
logger.Info.Println("startup...")
|
||||||
|
|
||||||
|
config.LoadConfig()
|
||||||
|
|
||||||
|
err := database.Open(config.DB.Path, config.Recipients.AdminEmail)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error.Fatalf("Couldn't init database: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if err := database.Close(); err != nil {
|
||||||
|
logger.Error.Fatalf("Failed to close database: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go server.Run()
|
||||||
|
|
||||||
|
gracefulShutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
func gracefulShutdown() {
|
||||||
|
// Create a channel to listen for OS signals
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
||||||
|
|
||||||
|
// Block until a signal is received
|
||||||
|
<-stop
|
||||||
|
logger.Info.Println("Received shutdown signal")
|
||||||
|
|
||||||
|
// Create a context with a timeout for the shutdown process
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
// Call the server's shutdown function
|
||||||
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
|
logger.Error.Fatalf("Error during Server shutdown: %#v", err)
|
||||||
|
} else {
|
||||||
|
logger.Info.Println("Server gracefully stopped")
|
||||||
|
}
|
||||||
|
}
|
||||||
37
go-backend/configs/config.template.json
Normal file
37
go-backend/configs/config.template.json
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"site": {
|
||||||
|
"WebsiteTitle": "My Carsharing Site",
|
||||||
|
"BaseUrl": "https://domain.de",
|
||||||
|
"AllowOrigins": "https://domain.de"
|
||||||
|
},
|
||||||
|
"Environment": "dev",
|
||||||
|
"db": {
|
||||||
|
"Path": "data/db.sqlite3"
|
||||||
|
},
|
||||||
|
"smtp": {
|
||||||
|
"Host": "mail.server.com",
|
||||||
|
"User": "username",
|
||||||
|
"Password": "password",
|
||||||
|
"Port": 465
|
||||||
|
},
|
||||||
|
"templates": {
|
||||||
|
"MailPath": "templates/email",
|
||||||
|
"HTMLPath": "templates/html",
|
||||||
|
"StaticPath": "templates/css",
|
||||||
|
"LogoURI": "/assets/LOGO.png"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"APIKey": ""
|
||||||
|
},
|
||||||
|
"recipients": {
|
||||||
|
"ContactForm": "contacts@server.com",
|
||||||
|
"UserRegistration": "registration@server.com",
|
||||||
|
"AdminEmail": "admin@server.com"
|
||||||
|
},
|
||||||
|
"security": {
|
||||||
|
"RateLimits": {
|
||||||
|
"Limit": 1,
|
||||||
|
"Burst": 60
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
go-backend/data/README.md
Normal file
1
go-backend/data/README.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Database Folder according to the project structure and the template configuration
|
||||||
@@ -72,7 +72,9 @@ func (uc *UserController) ChangePassword(c *gin.Context) {
|
|||||||
verification, err := uc.Service.VerifyUser(&input.Token, &constants.VerificationTypes.Password)
|
verification, err := uc.Service.VerifyUser(&input.Token, &constants.VerificationTypes.Password)
|
||||||
if err != nil || uint(userIDint) != verification.UserID {
|
if err != nil || uint(userIDint) != verification.UserID {
|
||||||
if err == errors.ErrAlreadyVerified {
|
if err == errors.ErrAlreadyVerified {
|
||||||
utils.RespondWithError(c, err, "User already verified", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
|
utils.RespondWithError(c, err, "User already changed password", http.StatusConflict, errors.Responses.Fields.User, errors.Responses.Keys.PasswordAlreadyChanged)
|
||||||
|
} else if err.Error() == "record not found" {
|
||||||
|
utils.RespondWithError(c, err, "Couldn't find verification. This is most probably a outdated token.", http.StatusGone, errors.Responses.Fields.User, errors.Responses.Keys.NoAuthToken)
|
||||||
} else {
|
} else {
|
||||||
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
utils.RespondWithError(c, err, "Couldn't verify user", http.StatusInternalServerError, errors.Responses.Fields.General, errors.Responses.Keys.InternalServerError)
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ func (uc *UserController) LoginHandler(c *gin.Context) {
|
|||||||
func (uc *UserController) RegisterUser(c *gin.Context) {
|
func (uc *UserController) RegisterUser(c *gin.Context) {
|
||||||
|
|
||||||
var regData RegistrationData
|
var regData RegistrationData
|
||||||
|
logger.Error.Printf("registering user...")
|
||||||
if err := c.ShouldBindJSON(®Data); err != nil {
|
if err := c.ShouldBindJSON(®Data); err != nil {
|
||||||
utils.HandleValidationError(c, err)
|
utils.HandleValidationError(c, err)
|
||||||
return
|
return
|
||||||
@@ -13,7 +13,7 @@ func CORSMiddleware() gin.HandlerFunc {
|
|||||||
logger.Info.Print("Applying CORS")
|
logger.Info.Print("Applying CORS")
|
||||||
return cors.New(cors.Config{
|
return cors.New(cors.Config{
|
||||||
AllowOrigins: strings.Split(config.Site.AllowOrigins, ","),
|
AllowOrigins: strings.Split(config.Site.AllowOrigins, ","),
|
||||||
AllowMethods: []string{"GET", "POST", "PATCH", "PUT"},
|
AllowMethods: []string{"GET", "POST", "PATCH", "PUT", "OPTIONS"},
|
||||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-CSRF-Token"},
|
AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization", "X-Requested-With", "X-CSRF-Token"},
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func CSPMiddleware() gin.HandlerFunc {
|
func CSPMiddleware() gin.HandlerFunc {
|
||||||
|
logger.Error.Printf("applying CSP")
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
policy := "default-src 'self'; " +
|
policy := "default-src 'self'; " +
|
||||||
"script-src 'self' 'unsafe-inline'" +
|
"script-src 'self' 'unsafe-inline'" +
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import "github.com/gin-gonic/gin"
|
import (
|
||||||
|
"GoMembership/pkg/logger"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
func SecurityHeadersMiddleware() gin.HandlerFunc {
|
func SecurityHeadersMiddleware() gin.HandlerFunc {
|
||||||
|
logger.Error.Printf("applying headers")
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
c.Header("X-Frame-Options", "DENY")
|
c.Header("X-Frame-Options", "DENY")
|
||||||
c.Header("X-Content-Type-Options", "nosniff")
|
c.Header("X-Content-Type-Options", "nosniff")
|
||||||
@@ -14,6 +14,7 @@ type ValidationKeys struct {
|
|||||||
InvalidUserID string
|
InvalidUserID string
|
||||||
PasswordAlreadyChanged string
|
PasswordAlreadyChanged string
|
||||||
UserDisabled string
|
UserDisabled string
|
||||||
|
NoAuthToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
type ValidationFields struct {
|
type ValidationFields struct {
|
||||||
@@ -63,6 +64,7 @@ var Responses = struct {
|
|||||||
Duplicate: "server.validation.duplicate",
|
Duplicate: "server.validation.duplicate",
|
||||||
UserDisabled: "server.validation.user_disabled",
|
UserDisabled: "server.validation.user_disabled",
|
||||||
PasswordAlreadyChanged: "server.validation.password_already_changed",
|
PasswordAlreadyChanged: "server.validation.password_already_changed",
|
||||||
|
NoAuthToken: "server.error.no_auth_token",
|
||||||
},
|
},
|
||||||
Fields: ValidationFields{
|
Fields: ValidationFields{
|
||||||
General: "general",
|
General: "general",
|
||||||
Reference in New Issue
Block a user