mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-17 20:09:41 +02:00
refactor: consolidate codebase — remove archived code, deduplicate packages, standardize middleware
- Delete 17 server-archived/ directories (consolidated into apps/api/) - Delete apps-archived/ (clock, wisekeep) and services-archived/ (it-landing, ollama-metrics-proxy) - Fix type safety: replace all `any` casts in cross-app-queries.ts with proper Local* types - Remove duplicate shared-auth-stores package (identical copy of shared-auth-ui/stores/) - Remove duplicate theme store from shared-stores (canonical version in shared-theme) - Migrate memoro-server rate-limiter to shared-hono/rateLimitMiddleware - Migrate uload-server JWT auth + error handler to shared-hono (authMiddleware, errorHandler) - Migrate arcade-server error handling to shared-hono - Merge shared-profile-ui and shared-app-onboarding into shared-ui - Unify /clock route into /times/clock, remove redirect stubs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7ee57b7afd
commit
d8ce4eaf34
309 changed files with 172 additions and 21667 deletions
|
|
@ -9,10 +9,10 @@
|
|||
"type-check": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@manacore/shared-hono": "workspace:*",
|
||||
"drizzle-orm": "^0.44.7",
|
||||
"hono": "^4.7.0",
|
||||
"stripe": "^18.4.0",
|
||||
"jose": "^6.1.2",
|
||||
"postgres": "^3.4.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
export interface Config {
|
||||
port: number;
|
||||
databaseUrl: string;
|
||||
manaAuthUrl: string;
|
||||
cors: { origins: string[] };
|
||||
stripeSecretKey: string;
|
||||
stripeWebhookSecret: string;
|
||||
|
|
@ -21,7 +20,6 @@ export function loadConfig(): Config {
|
|||
'DATABASE_URL',
|
||||
'postgresql://manacore:devpassword@localhost:5432/mana_sync'
|
||||
),
|
||||
manaAuthUrl: requiredEnv('MANA_CORE_AUTH_URL', 'http://localhost:3001'),
|
||||
cors: {
|
||||
origins: (process.env.CORS_ORIGINS || 'http://localhost:5173').split(','),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { Hono } from 'hono';
|
||||
import { cors } from 'hono/cors';
|
||||
import { authMiddleware, errorHandler, notFoundHandler } from '@manacore/shared-hono';
|
||||
import { loadConfig } from './config';
|
||||
import { getDb } from './db/connection';
|
||||
import { errorHandler } from './middleware/error-handler';
|
||||
import { jwtAuth } from './middleware/jwt-auth';
|
||||
import { RedirectService } from './services/redirect';
|
||||
import { AnalyticsService } from './services/analytics';
|
||||
import { healthRoutes } from './routes/health';
|
||||
|
|
@ -22,6 +21,7 @@ const analyticsService = new AnalyticsService(db);
|
|||
const app = new Hono();
|
||||
|
||||
app.onError(errorHandler);
|
||||
app.notFound(notFoundHandler);
|
||||
app.use('*', cors({ origin: config.cors.origins, credentials: true }));
|
||||
|
||||
// Health (no auth)
|
||||
|
|
@ -38,7 +38,7 @@ app.post('/api/v1/stripe/webhook', async (c) => {
|
|||
});
|
||||
|
||||
// Authenticated API routes
|
||||
app.use('/api/v1/*', jwtAuth(config.manaAuthUrl));
|
||||
app.use('/api/v1/*', authMiddleware());
|
||||
app.route('/api/v1/analytics', createAnalyticsRoutes(analyticsService));
|
||||
app.route('/api/v1/stripe', createStripeRoutes(config));
|
||||
app.route('/api/v1/email', createEmailRoutes());
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export class NotFoundError extends HTTPException {
|
||||
constructor(message = 'Not found') {
|
||||
super(404, { message });
|
||||
}
|
||||
}
|
||||
|
||||
export class BadRequestError extends HTTPException {
|
||||
constructor(message = 'Bad request') {
|
||||
super(400, { message });
|
||||
}
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends HTTPException {
|
||||
constructor(message = 'Unauthorized') {
|
||||
super(401, { message });
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import type { ErrorHandler } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
|
||||
export const errorHandler: ErrorHandler = (err, c) => {
|
||||
if (err instanceof HTTPException) {
|
||||
return c.json({ statusCode: err.status, message: err.message }, err.status);
|
||||
}
|
||||
|
||||
console.error('Unhandled error:', err);
|
||||
return c.json({ statusCode: 500, message: 'Internal server error' }, 500);
|
||||
};
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import type { MiddlewareHandler } from 'hono';
|
||||
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
||||
import { UnauthorizedError } from '../lib/errors';
|
||||
|
||||
export interface AuthUser {
|
||||
userId: string;
|
||||
email: string;
|
||||
role: string;
|
||||
}
|
||||
|
||||
let jwks: ReturnType<typeof createRemoteJWKSet> | null = null;
|
||||
|
||||
function getJwks(authUrl: string) {
|
||||
if (!jwks) {
|
||||
jwks = createRemoteJWKSet(new URL('/api/auth/jwks', authUrl));
|
||||
}
|
||||
return jwks;
|
||||
}
|
||||
|
||||
export function jwtAuth(authUrl: string): MiddlewareHandler {
|
||||
return async (c, next) => {
|
||||
const authHeader = c.req.header('Authorization');
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
throw new UnauthorizedError('Missing or invalid Authorization header');
|
||||
}
|
||||
|
||||
const token = authHeader.slice(7);
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, getJwks(authUrl), {
|
||||
issuer: authUrl,
|
||||
audience: 'manacore',
|
||||
});
|
||||
|
||||
const user: AuthUser = {
|
||||
userId: payload.sub || '',
|
||||
email: (payload.email as string) || '',
|
||||
role: (payload.role as string) || 'user',
|
||||
};
|
||||
|
||||
c.set('user', user);
|
||||
await next();
|
||||
} catch {
|
||||
throw new UnauthorizedError('Invalid or expired token');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Hono } from 'hono';
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import type { RedirectService } from '../services/redirect';
|
||||
import { NotFoundError } from '../lib/errors';
|
||||
|
||||
export function createRedirectRoutes(redirectService: RedirectService) {
|
||||
return new Hono().get('/:code', async (c) => {
|
||||
|
|
@ -8,7 +8,7 @@ export function createRedirectRoutes(redirectService: RedirectService) {
|
|||
const link = await redirectService.resolve(code);
|
||||
|
||||
if (!link) {
|
||||
throw new NotFoundError('Link not found or expired');
|
||||
throw new HTTPException(404, { message: 'Link not found or expired' });
|
||||
}
|
||||
|
||||
// Track click asynchronously (don't block redirect)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Hono } from 'hono';
|
||||
import Stripe from 'stripe';
|
||||
import type { AuthUser } from '../middleware/jwt-auth';
|
||||
import type { AuthVariables } from '@manacore/shared-hono';
|
||||
import type { Config } from '../config';
|
||||
|
||||
const PRICES = {
|
||||
|
|
@ -11,19 +11,20 @@ const PRICES = {
|
|||
export function createStripeRoutes(config: Config) {
|
||||
const stripe = config.stripeSecretKey ? new Stripe(config.stripeSecretKey) : null;
|
||||
|
||||
return new Hono<{ Variables: { user: AuthUser } }>()
|
||||
return new Hono<{ Variables: AuthVariables }>()
|
||||
.post('/checkout', async (c) => {
|
||||
if (!stripe) return c.json({ error: 'Stripe not configured' }, 501);
|
||||
|
||||
const user = c.get('user');
|
||||
const userId = c.get('userId');
|
||||
const userEmail = c.get('userEmail');
|
||||
const { priceType } = await c.req.json<{ priceType: keyof typeof PRICES }>();
|
||||
const price = PRICES[priceType];
|
||||
if (!price) return c.json({ error: 'Invalid price type' }, 400);
|
||||
|
||||
const session = await stripe.checkout.sessions.create({
|
||||
mode: 'subscription',
|
||||
customer_email: user.email,
|
||||
metadata: { userId: user.userId },
|
||||
customer_email: userEmail,
|
||||
metadata: { userId },
|
||||
line_items: [
|
||||
{
|
||||
price_data: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue