Finished step '1.3.3 Editor Configuration'

master
oabrivard 1 month ago
parent feabc39d02
commit 7e57460e76

@ -0,0 +1,19 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.go]
indent_style = tab
indent_size = 4
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab

43
.gitignore vendored

@ -1 +1,44 @@
# Repo-specific
.junk/ .junk/
# OS files
.DS_Store
Thumbs.db
# IDE/editor
.vscode/
.idea/
*.swp
*.swo
*.swn
*.swm
*~
# Env / secrets
.env
.env.*
!.env.example
# Node / frontend
frontend/node_modules/
frontend/dist/
frontend/build/
frontend/.cache/
frontend/.vite/
frontend/.turbo/
frontend/.eslintcache
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Go / backend
**/*.test
**/*.out
**/coverage*
**/profile.out
**/cpu.out
**/mem.out
# Go workspace caches (optional, safe)
**/.cache/

@ -6,7 +6,6 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"sync"
"time" "time"
) )
@ -34,9 +33,9 @@ type Client struct {
// JWKSCache caches the JSON Web Key Set for token validation. // JWKSCache caches the JSON Web Key Set for token validation.
type JWKSCache struct { type JWKSCache struct {
mu sync.RWMutex //mu sync.RWMutex
keys map[string]interface{} keys map[string]interface{}
expiry time.Time //expiry time.Time
duration time.Duration duration time.Duration
} }

@ -66,6 +66,7 @@ func TestJWTMiddleware_MissingHeader(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/", nil) req := httptest.NewRequest(http.MethodGet, "/", nil)
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusUnauthorized, resp.StatusCode) require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
} }
@ -80,6 +81,7 @@ func TestJWTMiddleware_InvalidHeaderFormat(t *testing.T) {
req.Header.Set("Authorization", "Token abc") req.Header.Set("Authorization", "Token abc")
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusUnauthorized, resp.StatusCode) require.Equal(t, http.StatusUnauthorized, resp.StatusCode)
} }
@ -99,6 +101,7 @@ func TestJWTMiddleware_AdminRoleRequired(t *testing.T) {
req.Header.Set("Authorization", "Bearer token") req.Header.Set("Authorization", "Bearer token")
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusForbidden, resp.StatusCode) require.Equal(t, http.StatusForbidden, resp.StatusCode)
} }
@ -118,6 +121,7 @@ func TestJWTMiddleware_MFARequiredForAdmin(t *testing.T) {
req.Header.Set("Authorization", "Bearer token") req.Header.Set("Authorization", "Bearer token")
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusForbidden, resp.StatusCode) require.Equal(t, http.StatusForbidden, resp.StatusCode)
} }
@ -131,6 +135,7 @@ func TestJWTMiddleware_SkipPath(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/public/health", nil) req := httptest.NewRequest(http.MethodGet, "/public/health", nil)
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
require.Equal(t, 0, validator.called) require.Equal(t, 0, validator.called)
} }

@ -86,7 +86,9 @@ func (s *noopSpan) SetAttribute(key string, value interface{}) {}
func (s *noopSpan) RecordError(err error) {} func (s *noopSpan) RecordError(err error) {}
// TraceServiceOperation traces a service operation. // TraceServiceOperation traces a service operation.
func TraceServiceOperation(ctx context.Context, tracer *Tracer, serviceName, operation string, fn func(context.Context) error) error { func TraceServiceOperation(ctx context.Context, tracer *Tracer, serviceName,
operation string, fn func(context.Context) error) error {
ctx, span := tracer.StartSpan(ctx, fmt.Sprintf("%s.%s", serviceName, operation)) ctx, span := tracer.StartSpan(ctx, fmt.Sprintf("%s.%s", serviceName, operation))
defer span.End() defer span.End()
@ -100,7 +102,9 @@ func TraceServiceOperation(ctx context.Context, tracer *Tracer, serviceName, ope
} }
// TraceDatabaseOperation traces a database operation. // TraceDatabaseOperation traces a database operation.
func TraceDatabaseOperation(ctx context.Context, tracer *Tracer, operation, table string, fn func(context.Context) error) error { func TraceDatabaseOperation(ctx context.Context, tracer *Tracer, operation,
table string, fn func(context.Context) error) error {
ctx, span := tracer.StartSpan(ctx, fmt.Sprintf("db.%s.%s", operation, table)) ctx, span := tracer.StartSpan(ctx, fmt.Sprintf("db.%s.%s", operation, table))
defer span.End() defer span.End()

@ -67,7 +67,7 @@ func mapErrorCodeToStatus(code errors.ErrorCode) int {
return fiber.StatusBadRequest return fiber.StatusBadRequest
case errors.CodeUnauthorized, errors.CodeInvalidToken, errors.CodeTokenExpired: case errors.CodeUnauthorized, errors.CodeInvalidToken, errors.CodeTokenExpired:
return fiber.StatusUnauthorized return fiber.StatusUnauthorized
case errors.CodeForbidden, errors.CodeMFARequired: case errors.CodeForbidden, errors.CodeMFARequired, errors.CodeEmailNotVerified:
return fiber.StatusForbidden return fiber.StatusForbidden
case errors.CodeConflict, errors.CodeUserAlreadyExists, errors.CodeGameInProgress: case errors.CodeConflict, errors.CodeUserAlreadyExists, errors.CodeGameInProgress:
return fiber.StatusConflict return fiber.StatusConflict
@ -75,8 +75,10 @@ func mapErrorCodeToStatus(code errors.ErrorCode) int {
return fiber.StatusTooManyRequests return fiber.StatusTooManyRequests
case errors.CodeSessionExpired, errors.CodeSessionNotActive: case errors.CodeSessionExpired, errors.CodeSessionNotActive:
return fiber.StatusGone return fiber.StatusGone
case errors.CodeMaxAttemptsReached: case errors.CodeMaxAttemptsReached, errors.CodeNoQuestionsAvailable:
return fiber.StatusUnprocessableEntity return fiber.StatusUnprocessableEntity
case errors.CodeInternal:
return fiber.StatusInternalServerError
default: default:
return fiber.StatusInternalServerError return fiber.StatusInternalServerError
} }

@ -29,9 +29,11 @@ func TestMapError(t *testing.T) {
{"unauthorized", errorspkg.Wrap(errorspkg.CodeUnauthorized, "no", nil), http.StatusUnauthorized, "UNAUTHORIZED"}, {"unauthorized", errorspkg.Wrap(errorspkg.CodeUnauthorized, "no", nil), http.StatusUnauthorized, "UNAUTHORIZED"},
{"forbidden", errorspkg.Wrap(errorspkg.CodeForbidden, "no", nil), http.StatusForbidden, "FORBIDDEN"}, {"forbidden", errorspkg.Wrap(errorspkg.CodeForbidden, "no", nil), http.StatusForbidden, "FORBIDDEN"},
{"conflict", errorspkg.Wrap(errorspkg.CodeConflict, "conflict", nil), http.StatusConflict, "CONFLICT"}, {"conflict", errorspkg.Wrap(errorspkg.CodeConflict, "conflict", nil), http.StatusConflict, "CONFLICT"},
{"rate_limit", errorspkg.Wrap(errorspkg.CodeRateLimitExceeded, "slow", nil), http.StatusTooManyRequests, "RATE_LIMIT_EXCEEDED"}, {"rate_limit", errorspkg.Wrap(errorspkg.CodeRateLimitExceeded, "slow", nil),
http.StatusTooManyRequests, "RATE_LIMIT_EXCEEDED"},
{"gone", errorspkg.Wrap(errorspkg.CodeSessionExpired, "gone", nil), http.StatusGone, "SESSION_EXPIRED"}, {"gone", errorspkg.Wrap(errorspkg.CodeSessionExpired, "gone", nil), http.StatusGone, "SESSION_EXPIRED"},
{"unprocessable", errorspkg.Wrap(errorspkg.CodeMaxAttemptsReached, "max", nil), http.StatusUnprocessableEntity, "MAX_ATTEMPTS_REACHED"}, {"unprocessable", errorspkg.Wrap(errorspkg.CodeMaxAttemptsReached, "max", nil),
http.StatusUnprocessableEntity, "MAX_ATTEMPTS_REACHED"},
{"generic", errors.New("boom"), http.StatusInternalServerError, "INTERNAL"}, {"generic", errors.New("boom"), http.StatusInternalServerError, "INTERNAL"},
} }

@ -61,25 +61,30 @@ func TestResponseHelpers(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/ok", nil) req := httptest.NewRequest(http.MethodGet, "/ok", nil)
resp, err := app.Test(req) resp, err := app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
req = httptest.NewRequest(http.MethodPost, "/created", nil) req = httptest.NewRequest(http.MethodPost, "/created", nil)
resp, err = app.Test(req) resp, err = app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusCreated, resp.StatusCode) require.Equal(t, http.StatusCreated, resp.StatusCode)
req = httptest.NewRequest(http.MethodDelete, "/no-content", nil) req = httptest.NewRequest(http.MethodDelete, "/no-content", nil)
resp, err = app.Test(req) resp, err = app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusNoContent, resp.StatusCode) require.Equal(t, http.StatusNoContent, resp.StatusCode)
req = httptest.NewRequest(http.MethodGet, "/paginated", nil) req = httptest.NewRequest(http.MethodGet, "/paginated", nil)
resp, err = app.Test(req) resp, err = app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
req = httptest.NewRequest(http.MethodGet, "/message", nil) req = httptest.NewRequest(http.MethodGet, "/message", nil)
resp, err = app.Test(req) resp, err = app.Test(req)
require.NoError(t, err) require.NoError(t, err)
defer resp.Body.Close()
require.Equal(t, http.StatusOK, resp.StatusCode) require.Equal(t, http.StatusOK, resp.StatusCode)
} }

@ -31,10 +31,22 @@ func NewValidator() *Validator {
}) })
// Register custom validations // Register custom validations
v.RegisterValidation("alphanum_space", validateAlphanumSpace) err := v.RegisterValidation("alphanum_space", validateAlphanumSpace)
v.RegisterValidation("no_html", validateNoHTML) if err != nil {
v.RegisterValidation("safe_text", validateSafeText) panic(err)
v.RegisterValidation("player_name", validatePlayerName) }
err = v.RegisterValidation("no_html", validateNoHTML)
if err != nil {
panic(err)
}
err = v.RegisterValidation("safe_text", validateSafeText)
if err != nil {
panic(err)
}
err = v.RegisterValidation("player_name", validatePlayerName)
if err != nil {
panic(err)
}
return &Validator{validate: v} return &Validator{validate: v}
} }

@ -1,39 +0,0 @@
{
"root": true,
"env": {
"browser": true,
"es2022": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:solid/recommended",
"prettier"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": [
"@typescript-eslint",
"solid"
],
"rules": {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"@typescript-eslint/no-explicit-any": "warn",
"prefer-const": "error",
"no-var": "error",
"object-shorthand": "error",
"prefer-template": "error",
"no-console": ["warn", { "allow": ["warn", "error"] }]
},
"ignorePatterns": [
"dist",
"node_modules",
"*.config.js",
"*.config.ts"
]
}

@ -0,0 +1,61 @@
import { defineConfig, globalIgnores } from "eslint/config";
import typescriptEslint from "@typescript-eslint/eslint-plugin";
import globals from "globals";
import tsParser from "@typescript-eslint/parser";
import path from "node:path";
import { fileURLToPath } from "node:url";
import js from "@eslint/js";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all
});
export default defineConfig([
globalIgnores(["**/dist", "**/node_modules", "**/*.config.js", "**/*.config.ts"]),
{
extends: compat.extends(
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:solid/recommended",
"prettier",
),
plugins: {
"@typescript-eslint": typescriptEslint,
},
languageOptions: {
globals: {
...globals.browser,
...globals.node,
},
parser: tsParser,
ecmaVersion: "latest",
sourceType: "module",
},
rules: {
"@typescript-eslint/explicit-function-return-type": "warn",
"@typescript-eslint/no-unused-vars": ["error", {
argsIgnorePattern: "^_",
}],
"@typescript-eslint/no-explicit-any": "warn",
"prefer-const": "error",
"no-var": "error",
"object-shorthand": "error",
"prefer-template": "error",
"no-console": ["warn", {
allow: ["warn", "error"],
}],
},
},
]);

File diff suppressed because it is too large Load Diff

@ -0,0 +1,12 @@
{
"devDependencies": {
"@eslint/eslintrc": "^3.3.3",
"@eslint/js": "^9.39.2",
"eslint": "^9.39.2",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-solid": "^0.14.5",
"globals": "^17.3.0",
"jiti": "^2.6.1",
"typescript-eslint": "^8.54.0"
}
}
Loading…
Cancel
Save