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/

@ -15,13 +15,13 @@ const (
CodeInternal ErrorCode = "INTERNAL" CodeInternal ErrorCode = "INTERNAL"
// Game session errors // Game session errors
CodeSessionExpired ErrorCode = "SESSION_EXPIRED" CodeSessionExpired ErrorCode = "SESSION_EXPIRED"
CodeGameInProgress ErrorCode = "GAME_IN_PROGRESS" CodeGameInProgress ErrorCode = "GAME_IN_PROGRESS"
CodeMaxAttemptsReached ErrorCode = "MAX_ATTEMPTS_REACHED" CodeMaxAttemptsReached ErrorCode = "MAX_ATTEMPTS_REACHED"
CodeSessionNotActive ErrorCode = "SESSION_NOT_ACTIVE" CodeSessionNotActive ErrorCode = "SESSION_NOT_ACTIVE"
// Question errors // Question errors
CodeQuestionNotFound ErrorCode = "QUESTION_NOT_FOUND" CodeQuestionNotFound ErrorCode = "QUESTION_NOT_FOUND"
CodeNoQuestionsAvailable ErrorCode = "NO_QUESTIONS_AVAILABLE" CodeNoQuestionsAvailable ErrorCode = "NO_QUESTIONS_AVAILABLE"
// User errors // User errors
@ -30,14 +30,14 @@ const (
CodeEmailNotVerified ErrorCode = "EMAIL_NOT_VERIFIED" CodeEmailNotVerified ErrorCode = "EMAIL_NOT_VERIFIED"
// Validation errors // Validation errors
CodeValidationFailed ErrorCode = "VALIDATION_FAILED" CodeValidationFailed ErrorCode = "VALIDATION_FAILED"
CodeInvalidPlayerName ErrorCode = "INVALID_PLAYER_NAME" CodeInvalidPlayerName ErrorCode = "INVALID_PLAYER_NAME"
CodeInvalidAnswer ErrorCode = "INVALID_ANSWER" CodeInvalidAnswer ErrorCode = "INVALID_ANSWER"
// Authentication errors // Authentication errors
CodeInvalidToken ErrorCode = "INVALID_TOKEN" CodeInvalidToken ErrorCode = "INVALID_TOKEN"
CodeTokenExpired ErrorCode = "TOKEN_EXPIRED" CodeTokenExpired ErrorCode = "TOKEN_EXPIRED"
CodeMFARequired ErrorCode = "MFA_REQUIRED" CodeMFARequired ErrorCode = "MFA_REQUIRED"
// Rate limiting errors // Rate limiting errors
CodeRateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED" CodeRateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"

@ -20,10 +20,10 @@ type Event interface {
// BaseEvent provides a base implementation of the Event interface. // BaseEvent provides a base implementation of the Event interface.
type BaseEvent struct { type BaseEvent struct {
Type EventType `json:"type"` Type EventType `json:"type"`
Timestamp time.Time `json:"timestamp"` Timestamp time.Time `json:"timestamp"`
AggrID string `json:"aggregate_id"` AggrID string `json:"aggregate_id"`
AggrType string `json:"aggregate_type"` AggrType string `json:"aggregate_type"`
} }
// EventType returns the type of the event. // EventType returns the type of the event.

@ -16,8 +16,8 @@ type Permission string
// Game permissions // Game permissions
const ( const (
PermissionPlayGame Permission = "game:play" PermissionPlayGame Permission = "game:play"
PermissionViewGame Permission = "game:view" PermissionViewGame Permission = "game:view"
) )
// Question permissions // Question permissions
@ -44,9 +44,9 @@ const (
// Admin permissions // Admin permissions
const ( const (
PermissionViewAuditLog Permission = "audit:view" PermissionViewAuditLog Permission = "audit:view"
PermissionViewDashboard Permission = "dashboard:view" PermissionViewDashboard Permission = "dashboard:view"
PermissionManageSystem Permission = "system:manage" PermissionManageSystem Permission = "system:manage"
) )
// rolePermissions maps roles to their permissions. // rolePermissions maps roles to their permissions.

@ -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
} }

@ -26,12 +26,12 @@ const (
// JWTMiddlewareConfig holds configuration for the JWT middleware. // JWTMiddlewareConfig holds configuration for the JWT middleware.
type JWTMiddlewareConfig struct { type JWTMiddlewareConfig struct {
Client TokenValidator Client TokenValidator
Issuer string Issuer string
Audience string Audience string
RequiredClaims []string RequiredClaims []string
AdminEndpoints []string AdminEndpoints []string
SkipPaths []string SkipPaths []string
} }
// TokenValidator defines the interface for validating JWT tokens. // TokenValidator defines the interface for validating JWT tokens.

@ -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)
} }

@ -26,30 +26,30 @@ type Metrics struct {
config Config config Config
// HTTP metrics // HTTP metrics
HTTPRequestsTotal *prometheus.CounterVec HTTPRequestsTotal *prometheus.CounterVec
HTTPRequestDuration *prometheus.HistogramVec HTTPRequestDuration *prometheus.HistogramVec
// Database metrics // Database metrics
DBConnectionsActive *prometheus.GaugeVec DBConnectionsActive *prometheus.GaugeVec
DBQueryDuration *prometheus.HistogramVec DBQueryDuration *prometheus.HistogramVec
DBErrors *prometheus.CounterVec DBErrors *prometheus.CounterVec
// Cache metrics // Cache metrics
CacheOperations *prometheus.CounterVec CacheOperations *prometheus.CounterVec
CacheKeyCount *prometheus.GaugeVec CacheKeyCount *prometheus.GaugeVec
// Authentication metrics // Authentication metrics
AuthAttempts *prometheus.CounterVec AuthAttempts *prometheus.CounterVec
TokenOperations *prometheus.CounterVec TokenOperations *prometheus.CounterVec
// Game metrics // Game metrics
GamesStarted *prometheus.CounterVec GamesStarted *prometheus.CounterVec
GamesCompleted *prometheus.CounterVec GamesCompleted *prometheus.CounterVec
SessionDuration *prometheus.HistogramVec SessionDuration *prometheus.HistogramVec
QuestionsAsked *prometheus.CounterVec QuestionsAsked *prometheus.CounterVec
AnswersSubmitted *prometheus.CounterVec AnswersSubmitted *prometheus.CounterVec
HintsRequested *prometheus.CounterVec HintsRequested *prometheus.CounterVec
ScoreDistribution *prometheus.HistogramVec ScoreDistribution *prometheus.HistogramVec
} }
// NewMetrics creates a new Metrics instance with all metrics registered. // NewMetrics creates a new Metrics instance with all metrics registered.

@ -81,12 +81,14 @@ type Span interface {
// noopSpan is a no-op implementation of Span. // noopSpan is a no-op implementation of Span.
type noopSpan struct{} type noopSpan struct{}
func (s *noopSpan) End() {} func (s *noopSpan) End() {}
func (s *noopSpan) SetAttribute(key string, value interface{}) {} 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()

@ -10,20 +10,20 @@ import (
// SanitizeOptions configures sanitization behavior. // SanitizeOptions configures sanitization behavior.
type SanitizeOptions struct { type SanitizeOptions struct {
TrimWhitespace bool TrimWhitespace bool
RemoveMultipleSpaces bool RemoveMultipleSpaces bool
HTMLEscape bool HTMLEscape bool
MaxLength int MaxLength int
AllowedPattern *regexp.Regexp AllowedPattern *regexp.Regexp
} }
// DefaultSanitizeOptions returns default sanitization options. // DefaultSanitizeOptions returns default sanitization options.
func DefaultSanitizeOptions() SanitizeOptions { func DefaultSanitizeOptions() SanitizeOptions {
return SanitizeOptions{ return SanitizeOptions{
TrimWhitespace: true, TrimWhitespace: true,
RemoveMultipleSpaces: true, RemoveMultipleSpaces: true,
HTMLEscape: true, HTMLEscape: true,
MaxLength: 0, // No limit MaxLength: 0, // No limit
} }
} }
@ -63,11 +63,11 @@ func Sanitize(input string, opts SanitizeOptions) string {
// SanitizePlayerName sanitizes a player name. // SanitizePlayerName sanitizes a player name.
func SanitizePlayerName(input string) string { func SanitizePlayerName(input string) string {
opts := SanitizeOptions{ opts := SanitizeOptions{
TrimWhitespace: true, TrimWhitespace: true,
RemoveMultipleSpaces: true, RemoveMultipleSpaces: true,
HTMLEscape: true, HTMLEscape: true,
MaxLength: 50, MaxLength: 50,
AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`), AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_.]+$`),
} }
return Sanitize(input, opts) return Sanitize(input, opts)
} }
@ -75,10 +75,10 @@ func SanitizePlayerName(input string) string {
// SanitizeAnswer sanitizes an answer submission. // SanitizeAnswer sanitizes an answer submission.
func SanitizeAnswer(input string) string { func SanitizeAnswer(input string) string {
opts := SanitizeOptions{ opts := SanitizeOptions{
TrimWhitespace: true, TrimWhitespace: true,
RemoveMultipleSpaces: true, RemoveMultipleSpaces: true,
HTMLEscape: true, HTMLEscape: true,
MaxLength: 500, MaxLength: 500,
} }
result := Sanitize(input, opts) result := Sanitize(input, opts)
@ -92,10 +92,10 @@ func SanitizeAnswer(input string) string {
// SanitizeQuestionText sanitizes question text (admin input). // SanitizeQuestionText sanitizes question text (admin input).
func SanitizeQuestionText(input string) string { func SanitizeQuestionText(input string) string {
opts := SanitizeOptions{ opts := SanitizeOptions{
TrimWhitespace: true, TrimWhitespace: true,
RemoveMultipleSpaces: true, RemoveMultipleSpaces: true,
HTMLEscape: true, HTMLEscape: true,
MaxLength: 1000, MaxLength: 1000,
} }
result := Sanitize(input, opts) result := Sanitize(input, opts)
@ -110,11 +110,11 @@ func SanitizeQuestionText(input string) string {
// SanitizeTheme sanitizes a theme name. // SanitizeTheme sanitizes a theme name.
func SanitizeTheme(input string) string { func SanitizeTheme(input string) string {
opts := SanitizeOptions{ opts := SanitizeOptions{
TrimWhitespace: true, TrimWhitespace: true,
RemoveMultipleSpaces: true, RemoveMultipleSpaces: true,
HTMLEscape: true, HTMLEscape: true,
MaxLength: 100, MaxLength: 100,
AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`), AllowedPattern: regexp.MustCompile(`^[a-zA-Z0-9\s\-_]+$`),
} }
result := Sanitize(input, opts) result := Sanitize(input, opts)

@ -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"},
} }

@ -77,11 +77,11 @@ func SortingFromQuery(c fiber.Ctx, defaultField string, allowedFields []string)
// FilterParams holds common filter parameters. // FilterParams holds common filter parameters.
type FilterParams struct { type FilterParams struct {
Search string Search string
Status string Status string
DateFrom string DateFrom string
DateTo string DateTo string
Custom map[string]string Custom map[string]string
} }
// FiltersFromQuery extracts common filter parameters from query string. // FiltersFromQuery extracts common filter parameters from query string.

@ -83,10 +83,10 @@ func Message(c fiber.Ctx, message string) error {
// HealthResponse represents a health check response. // HealthResponse represents a health check response.
type HealthResponse struct { type HealthResponse struct {
Status string `json:"status"` Status string `json:"status"`
Service string `json:"service"` Service string `json:"service"`
Version string `json:"version"` Version string `json:"version"`
Checks map[string]string `json:"checks,omitempty"` Checks map[string]string `json:"checks,omitempty"`
} }
// Health sends a health check response. // Health sends a health check response.

@ -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}
} }
@ -164,9 +176,9 @@ func validatePlayerName(fl validator.FieldLevel) bool {
// Common validation tags for reuse // Common validation tags for reuse
const ( const (
TagRequired = "required" TagRequired = "required"
TagPlayerName = "required,player_name" TagPlayerName = "required,player_name"
TagEmail = "required,email" TagEmail = "required,email"
TagUUID = "required,uuid" TagUUID = "required,uuid"
TagOptionalUUID = "omitempty,uuid" TagOptionalUUID = "omitempty,uuid"
) )

@ -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