This commit is contained in:
Wuesteon 2025-12-04 00:32:13 +01:00
parent 16cb8e753b
commit e9caa4a217
46 changed files with 1784 additions and 728 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -60,7 +60,15 @@ export interface UpdateCalendarInput {
/** /**
* Calendar view types * Calendar view types
*/ */
export type CalendarViewType = 'day' | '5day' | 'week' | '10day' | '14day' | 'month' | 'year' | 'agenda'; export type CalendarViewType =
| 'day'
| '5day'
| 'week'
| '10day'
| '14day'
| 'month'
| 'year'
| 'agenda';
/** /**
* Default calendar colors * Default calendar colors

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import path from 'path'; import path from 'path';
import { ComponentInfo } from '../types'; import { type ComponentInfo } from '../types';
import { getComponentsPath, ensureDir } from './paths'; import { getComponentsPath, ensureDir } from './paths';
/** /**

View file

@ -1,5 +1,5 @@
import fs from 'fs-extra'; import fs from 'fs-extra';
import { ComponentRegistry, ComponentInfo } from '../types'; import { type ComponentRegistry, type ComponentInfo } from '../types';
import { getRegistryPath } from './paths'; import { getRegistryPath } from './paths';
/** /**

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -1,11 +0,0 @@
// @ts-check
import { baseConfig, typescriptConfig, prettierConfig } from '@manacore/eslint-config';
export default [
{
ignores: ['dist/**', '.astro/**', 'node_modules/**'],
},
...baseConfig,
...typescriptConfig,
...prettierConfig,
];

View file

@ -12,7 +12,7 @@ export interface Toast {
function createToastStore() { function createToastStore() {
const { subscribe, update } = writable<Toast[]>([]); const { subscribe, update } = writable<Toast[]>([]);
function addToast(message: string, type: ToastType = 'info', duration: number = 3000) { function addToast(message: string, type: ToastType = 'info', duration = 3000) {
const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const id = `toast-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
const toast: Toast = { const toast: Toast = {

View file

@ -38,13 +38,27 @@ export default [
'**/*.d.ts', '**/*.d.ts',
'**/generated/**', '**/generated/**',
// Config files (tool-specific, not part of app code)
'**/drizzle.config.ts',
'playwright.config.ts',
'vitest.config.ts',
'tests/**',
// Documentation examples
'docs/test-examples/**',
// Games with specific runtime environments
'games/whopixels/**',
// Apps with their own ESLint configs (framework-specific) // Apps with their own ESLint configs (framework-specific)
// These import from @manacore/eslint-config but add framework rules // These import from @manacore/eslint-config but add framework rules
'apps/*/apps/mobile/**', 'apps/*/apps/mobile/**',
'apps/*/apps/web/**', 'apps/*/apps/web/**',
'apps/*/apps/backend/**', 'apps/*/apps/backend/**',
'apps/*/apps/landing/**', 'apps/*/apps/landing/**',
'apps/*/packages/**', // Project-specific packages
'games/*/apps/**', 'games/*/apps/**',
'games/*/packages/**', // Game-specific packages
'services/**', 'services/**',
], ],
}, },

View file

@ -315,7 +315,7 @@ class BootScene extends Phaser.Scene {
const brickWidth = 16; const brickWidth = 16;
for (let y = 0; y < tileSize; y += brickHeight) { for (let y = 0; y < tileSize; y += brickHeight) {
let offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2; const offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2;
for (let x = offset; x < tileSize; x += brickWidth) { for (let x = offset; x < tileSize; x += brickWidth) {
stoneWallGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1); stoneWallGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1);
} }
@ -330,7 +330,7 @@ class BootScene extends Phaser.Scene {
// Steinmuster für die Wand // Steinmuster für die Wand
stoneWallFlowerGraphics.fillStyle(0x555555); stoneWallFlowerGraphics.fillStyle(0x555555);
for (let y = 0; y < tileSize; y += brickHeight) { for (let y = 0; y < tileSize; y += brickHeight) {
let offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2; const offset = y % (brickHeight * 2) === 0 ? 0 : brickWidth / 2;
for (let x = offset; x < tileSize; x += brickWidth) { for (let x = offset; x < tileSize; x += brickWidth) {
stoneWallFlowerGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1); stoneWallFlowerGraphics.fillRect(x, y, brickWidth - 1, brickHeight - 1);
} }

View file

@ -246,7 +246,7 @@ const server = http.createServer((req, res) => {
// Get the file extension // Get the file extension
const extname = path.extname(filePath); const extname = path.extname(filePath);
let contentType = MIME_TYPES[extname] || 'application/octet-stream'; const contentType = MIME_TYPES[extname] || 'application/octet-stream';
// Read the file // Read the file
fs.readFile(filePath, (error, content) => { fs.readFile(filePath, (error, content) => {

View file

@ -9,8 +9,8 @@
"build": "turbo run build", "build": "turbo run build",
"test": "turbo run test", "test": "turbo run test",
"lint": "turbo run lint", "lint": "turbo run lint",
"lint:root": "eslint .", "lint:root": "eslint . --cache",
"lint:fix": "eslint . --fix", "lint:fix": "eslint . --fix --cache",
"type-check": "turbo run type-check", "type-check": "turbo run type-check",
"clean": "turbo run clean", "clean": "turbo run clean",
"format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"", "format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,js,jsx,json,md,svelte,astro}\"",

View file

@ -21,6 +21,8 @@ export const typescriptConfig = [
parserOptions: { parserOptions: {
ecmaVersion: 2022, ecmaVersion: 2022,
sourceType: 'module', sourceType: 'module',
projectService: true,
tsconfigRootDir: import.meta.dirname,
}, },
}, },
plugins: { plugins: {

View file

@ -1,4 +1,4 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
export interface JwtPayload { export interface JwtPayload {
sub: string; sub: string;

View file

@ -1,4 +1,4 @@
import { ModuleMetadata, Type } from '@nestjs/common'; import { type ModuleMetadata, type Type } from '@nestjs/common';
export interface ManaCoreModuleOptions { export interface ManaCoreModuleOptions {
/** /**

View file

@ -106,12 +106,17 @@ export class CreditClientService {
return this.getDefaultBalance(); return this.getDefaultBalance();
} }
const data = (await response.json()) as CreditBalance; const {
balance = 0,
freeCreditsRemaining = 0,
totalEarned = 0,
totalSpent = 0,
} = (await response.json()) as CreditBalance;
return { return {
balance: data.balance || 0, balance,
freeCreditsRemaining: data.freeCreditsRemaining || 0, freeCreditsRemaining,
totalEarned: data.totalEarned || 0, totalEarned,
totalSpent: data.totalSpent || 0, totalSpent,
}; };
} catch (error) { } catch (error) {
this.logger.error(`Failed to get balance for user ${userId}:`, error); this.logger.error(`Failed to get balance for user ${userId}:`, error);

View file

@ -1,4 +1,4 @@
import { drizzle, PostgresJsDatabase } from 'drizzle-orm/postgres-js'; import { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js';
import postgres from 'postgres'; import postgres from 'postgres';
import * as schema from './schema'; import * as schema from './schema';

View file

@ -3,8 +3,6 @@
* Generic types for creating auth stores with custom user types * Generic types for creating auth stores with custom user types
*/ */
import type { AuthResult as BaseAuthResult } from '@manacore/shared-types';
/** /**
* Base user interface that all app-specific user types must extend * Base user interface that all app-specific user types must extend
*/ */

View file

@ -106,10 +106,6 @@ export function createAuthService(config: AuthServiceConfig) {
*/ */
async signUp(email: string, password: string): Promise<AuthResult> { async signUp(email: string, password: string): Promise<AuthResult> {
try { try {
const storage = getStorageAdapter();
const deviceAdapter = getDeviceAdapter();
const deviceInfo = await deviceAdapter.getDeviceInfo();
const response = await fetch(`${baseUrl}${endpoints.signUp}`, { const response = await fetch(`${baseUrl}${endpoints.signUp}`, {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
@ -128,7 +124,8 @@ export function createAuthService(config: AuthServiceConfig) {
return { success: false, error: errorData.message || 'Sign up failed' }; return { success: false, error: errorData.message || 'Sign up failed' };
} }
const responseData = await response.json(); // Consume response to avoid unhandled promise
await response.json();
// Mana Core Auth returns user data immediately on registration // Mana Core Auth returns user data immediately on registration
// User needs to sign in separately to get tokens // User needs to sign in separately to get tokens

View file

@ -30,7 +30,7 @@ export function decodeToken(token: string): DecodedToken | null {
/** /**
* Check if a token is valid locally (not expired) * Check if a token is valid locally (not expired)
*/ */
export function isTokenValidLocally(token: string, bufferSeconds: number = 10): boolean { export function isTokenValidLocally(token: string, bufferSeconds = 10): boolean {
try { try {
const payload = decodeToken(token); const payload = decodeToken(token);
if (!payload || !payload.exp) { if (!payload || !payload.exp) {

View file

@ -118,7 +118,7 @@ export function getEnv(
*/ */
export function getBoolEnv( export function getBoolEnv(
key: string, key: string,
defaultValue: boolean = false, defaultValue = false,
env: NodeJS.ProcessEnv = process.env env: NodeJS.ProcessEnv = process.env
): boolean { ): boolean {
const value = env[key]; const value = env[key];

View file

@ -99,7 +99,7 @@ export function createFeatureFlags<T extends Record<string, FeatureFlag>>(
*/ */
export function isFeatureEnabled( export function isFeatureEnabled(
featureName: string, featureName: string,
defaultValue: boolean = false, defaultValue = false,
env: NodeJS.ProcessEnv = process.env env: NodeJS.ProcessEnv = process.env
): boolean { ): boolean {
const envVar = `FEATURE_${featureName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`; const envVar = `FEATURE_${featureName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}`;

View file

@ -230,10 +230,7 @@ export function createCreditService(config: CreditServiceConfig) {
/** /**
* Calculate cost for multiple units of an operation * Calculate cost for multiple units of an operation
*/ */
async function calculateCost( async function calculateCost(operation: StandardOperationType, quantity = 1): Promise<number> {
operation: StandardOperationType,
quantity: number = 1
): Promise<number> {
const unitCost = await getOperationCost(operation); const unitCost = await getOperationCost(operation);
return unitCost * quantity; return unitCost * quantity;
} }
@ -241,7 +238,7 @@ export function createCreditService(config: CreditServiceConfig) {
/** /**
* Calculate cost synchronously (uses cached values) * Calculate cost synchronously (uses cached values)
*/ */
function calculateCostSync(operation: StandardOperationType, quantity: number = 1): number { function calculateCostSync(operation: StandardOperationType, quantity = 1): number {
const unitCost = getOperationCostSync(operation); const unitCost = getOperationCostSync(operation);
return unitCost * quantity; return unitCost * quantity;
} }
@ -288,7 +285,7 @@ export function createCreditService(config: CreditServiceConfig) {
/** /**
* Format credit amount for display * Format credit amount for display
*/ */
function formatCredits(amount: number, locale: string = 'en-US'): string { function formatCredits(amount: number, locale = 'en-US'): string {
return new Intl.NumberFormat(locale).format(amount); return new Intl.NumberFormat(locale).format(amount);
} }

View file

@ -4,8 +4,6 @@
* Types for credit/mana operations across all apps * Types for credit/mana operations across all apps
*/ */
import type { OperationPricing, ManaBalance } from '@manacore/shared-subscription-types';
/** /**
* Credit balance with additional metadata * Credit balance with additional metadata
*/ */

View file

@ -1,4 +1,8 @@
import { ErrorCode, ERROR_CODE_TO_HTTP_STATUS, ERROR_CODE_RETRYABLE } from '../types/error-codes'; import {
type ErrorCode,
ERROR_CODE_TO_HTTP_STATUS,
ERROR_CODE_RETRYABLE,
} from '../types/error-codes';
/** /**
* Additional context that can be attached to errors. * Additional context that can be attached to errors.

View file

@ -7,7 +7,7 @@ import {
Logger, Logger,
} from '@nestjs/common'; } from '@nestjs/common';
import type { Request, Response } from 'express'; import type { Request, Response } from 'express';
import { AppError } from '../errors/app-error'; import { type AppError } from '../errors/app-error';
import { isAppError, isCreditError, isRateLimitError } from '../guards/type-guards'; import { isAppError, isCreditError, isRateLimitError } from '../guards/type-guards';
import { ErrorCode } from '../types/error-codes'; import { ErrorCode } from '../types/error-codes';

View file

@ -18,7 +18,6 @@
*/ */
import type { import type {
Feedback,
CreateFeedbackInput, CreateFeedbackInput,
FeedbackQueryParams, FeedbackQueryParams,
FeedbackResponse, FeedbackResponse,

View file

@ -1,5 +1,5 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common'; import { createParamDecorator, type ExecutionContext } from '@nestjs/common';
import { CurrentUserData } from '../types'; import { type CurrentUserData } from '../types';
/** /**
* Parameter decorator to extract the current user from the request. * Parameter decorator to extract the current user from the request.

View file

@ -4,7 +4,7 @@
* This package provides a unified Supabase client and common database utilities. * This package provides a unified Supabase client and common database utilities.
*/ */
import { createClient, SupabaseClient } from '@supabase/supabase-js'; import { createClient, type SupabaseClient } from '@supabase/supabase-js';
import type { SupabaseConfig } from '@manacore/shared-types'; import type { SupabaseConfig } from '@manacore/shared-types';
export { SupabaseClient } from '@supabase/supabase-js'; export { SupabaseClient } from '@supabase/supabase-js';

View file

@ -111,11 +111,7 @@ export function createUserSettingsStore(config: UserSettingsStoreConfig): UserSe
/** /**
* Make an API request to the settings endpoint * Make an API request to the settings endpoint
*/ */
async function apiRequest<T>( async function apiRequest<T>(method: string, path: string, body?: object): Promise<T | null> {
method: string,
path: string,
body?: object
): Promise<T | null> {
const token = await getAccessToken(); const token = await getAccessToken();
if (!token) { if (!token) {
console.warn('No access token available for settings API'); console.warn('No access token available for settings API');

View file

@ -17,7 +17,7 @@ export type LocaleKey = keyof typeof locales;
*/ */
export function formatDate( export function formatDate(
date: string | Date, date: string | Date,
formatStr: string = 'PPP', formatStr = 'PPP',
locale: LocaleKey = 'de' locale: LocaleKey = 'de'
): string { ): string {
const dateObj = typeof date === 'string' ? parseISO(date) : date; const dateObj = typeof date === 'string' ? parseISO(date) : date;

View file

@ -93,7 +93,7 @@ export function formatDurationHumanReadable(seconds: number, locale: 'en' | 'de'
/** /**
* Format file size from bytes to human readable string * Format file size from bytes to human readable string
*/ */
export function formatFileSize(bytes: number, decimals: number = 1): string { export function formatFileSize(bytes: number, decimals = 1): string {
if (bytes === 0) return '0 B'; if (bytes === 0) return '0 B';
if (!Number.isFinite(bytes) || bytes < 0) return '--'; if (!Number.isFinite(bytes) || bytes < 0) return '--';
@ -107,18 +107,14 @@ export function formatFileSize(bytes: number, decimals: number = 1): string {
/** /**
* Format number with thousands separator * Format number with thousands separator
*/ */
export function formatNumber(num: number, locale: string = 'de-DE'): string { export function formatNumber(num: number, locale = 'de-DE'): string {
return num.toLocaleString(locale); return num.toLocaleString(locale);
} }
/** /**
* Format currency * Format currency
*/ */
export function formatCurrency( export function formatCurrency(amount: number, currency = 'EUR', locale = 'de-DE'): string {
amount: number,
currency: string = 'EUR',
locale: string = 'de-DE'
): string {
return new Intl.NumberFormat(locale, { return new Intl.NumberFormat(locale, {
style: 'currency', style: 'currency',
currency, currency,
@ -128,11 +124,7 @@ export function formatCurrency(
/** /**
* Format percentage * Format percentage
*/ */
export function formatPercent( export function formatPercent(value: number, decimals = 0, locale = 'de-DE'): string {
value: number,
decimals: number = 0,
locale: string = 'de-DE'
): string {
return new Intl.NumberFormat(locale, { return new Intl.NumberFormat(locale, {
style: 'percent', style: 'percent',
minimumFractionDigits: decimals, minimumFractionDigits: decimals,

View file

@ -21,7 +21,7 @@ export function capitalize(str: string): string {
/** /**
* Generate a random string ID * Generate a random string ID
*/ */
export function generateId(length: number = 8): string { export function generateId(length = 8): string {
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
let result = ''; let result = '';
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {

View file

@ -30,7 +30,10 @@ function parseEnvFile(content) {
if (match) { if (match) {
let [, key, value] = match; let [, key, value] = match;
// Handle quoted values // Handle quoted values
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) { if (
(value.startsWith('"') && value.endsWith('"')) ||
(value.startsWith("'") && value.endsWith("'"))
) {
value = value.slice(1, -1); value = value.slice(1, -1);
} }
env[key.trim()] = value; env[key.trim()] = value;
@ -142,7 +145,8 @@ const APP_CONFIGS = [
{ {
path: 'apps/maerchenzauber/apps/mobile/.env', path: 'apps/maerchenzauber/apps/mobile/.env',
vars: { vars: {
EXPO_PUBLIC_STORYTELLER_BACKEND_URL: (env) => `http://localhost:${env.MAERCHENZAUBER_BACKEND_PORT || '3003'}`, EXPO_PUBLIC_STORYTELLER_BACKEND_URL: (env) =>
`http://localhost:${env.MAERCHENZAUBER_BACKEND_PORT || '3003'}`,
EXPO_ROUTER_APP_ROOT: () => 'app', EXPO_ROUTER_APP_ROOT: () => 'app',
}, },
}, },
@ -227,7 +231,8 @@ const APP_CONFIGS = [
NODE_ENV: () => 'development', NODE_ENV: () => 'development',
PORT: (env) => env.PICTURE_BACKEND_PORT || '3003', PORT: (env) => env.PICTURE_BACKEND_PORT || '3003',
BACKEND_URL: (env) => env.PICTURE_BACKEND_URL || 'http://localhost:3003', BACKEND_URL: (env) => env.PICTURE_BACKEND_URL || 'http://localhost:3003',
DATABASE_URL: (env) => env.PICTURE_DATABASE_URL || 'postgresql://picture:picturepassword@localhost:5434/picture', DATABASE_URL: (env) =>
env.PICTURE_DATABASE_URL || 'postgresql://picture:picturepassword@localhost:5434/picture',
MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL, MANA_CORE_AUTH_URL: (env) => env.MANA_CORE_AUTH_URL,
DEV_BYPASS_AUTH: () => 'true', DEV_BYPASS_AUTH: () => 'true',
DEV_USER_ID: (env) => env.DEV_USER_ID || '00000000-0000-0000-0000-000000000000', DEV_USER_ID: (env) => env.DEV_USER_ID || '00000000-0000-0000-0000-000000000000',