8 Commits

10 changed files with 3676 additions and 16 deletions

View File

@@ -31,6 +31,7 @@
"@prisma/client": "^5.19.1",
"bcryptjs": "^2.4.3",
"fastify": "^4.28.1",
"fastify-plugin": "^4.5.1",
"fastify-type-provider-zod": "^2.0.0",
"nanoid": "^5.0.7",
"node-cron": "^3.0.3",

View File

@@ -16,6 +16,13 @@ interface DownloadResult {
contentType: string;
}
function getOgImageUrl(ogImage: unknown): string | undefined {
const entry = Array.isArray(ogImage) ? ogImage[0] : ogImage;
if (!entry || typeof entry !== 'object') return undefined;
const { url } = entry as { url?: unknown };
return typeof url === 'string' ? url : undefined;
}
async function downloadImage(url: string): Promise<DownloadResult | null> {
try {
const controller = new AbortController();
@@ -27,7 +34,7 @@ async function downloadImage(url: string): Promise<DownloadResult | null> {
headers: { 'user-agent': 'FamilyWishlistBot/1.0 (+image-fetch)' },
});
if (res.statusCode >= 400) return null;
const contentType = (res.headers['content-type']?.toString() ?? '').split(';')[0].trim();
const contentType = ((res.headers['content-type']?.toString() ?? '').split(';')[0] ?? '').trim();
if (!ALLOWED_MIME.has(contentType)) return null;
const chunks: Buffer[] = [];
let total = 0;
@@ -60,8 +67,7 @@ export async function fetchOgImageForWish(
try {
const parsed = await ogs({ url: pageUrl, timeout: FETCH_TIMEOUT_MS });
if (parsed.error || !parsed.result) return;
const imageEntry = parsed.result.ogImage;
const imageUrl = Array.isArray(imageEntry) ? imageEntry[0]?.url : imageEntry?.url;
const imageUrl = getOgImageUrl(parsed.result.ogImage);
if (!imageUrl) return;
const absolute = new URL(imageUrl, pageUrl).toString();

View File

@@ -1,6 +1,7 @@
import fp from 'fastify-plugin';
import fastifyJwt from '@fastify/jwt';
import fastifyCookie from '@fastify/cookie';
import type { FastifyReply } from 'fastify';
import { env } from '../config/env.js';
import { UnauthorizedError } from '../utils/errors.js';
@@ -28,7 +29,7 @@ export default fp(async (app) => {
});
// helpers for routes
app.decorate('setAuthCookie', ((reply, token) => {
app.decorate('setAuthCookie', ((reply: FastifyReply, token: string) => {
reply.setCookie(AUTH_COOKIE, token, {
httpOnly: true,
secure: env.NODE_ENV === 'production',
@@ -38,7 +39,7 @@ export default fp(async (app) => {
});
}) as never);
app.decorate('clearAuthCookie', ((reply) => {
app.decorate('clearAuthCookie', ((reply: FastifyReply) => {
reply.clearCookie(AUTH_COOKIE, { path: '/' });
}) as never);
});

View File

@@ -2,9 +2,9 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": false,
"rootDir": "./src",
"rootDir": "./",
"outDir": "./dist"
},
"include": ["src/**/*"],
"include": ["src/**/*", "prisma/seed.ts"],
"exclude": ["**/*.test.ts", "dist"]
}

View File

@@ -1,4 +1,4 @@
import { createBrowserRouter } from 'react-router-dom';
import { createBrowserRouter, type RouterProviderProps } from 'react-router-dom';
import { ProtectedRoute } from './components/Layout/ProtectedRoute';
import { AppShell } from './components/Layout/AppShell';
import { LoginPage } from './pages/LoginPage';
@@ -10,7 +10,7 @@ import { ProfileSettingsPage } from './pages/ProfileSettingsPage';
import { PublicProfilePage } from './pages/PublicProfilePage';
import { NotFoundPage } from './pages/NotFoundPage';
export const router = createBrowserRouter([
export const router: RouterProviderProps['router'] = createBrowserRouter([
{ path: '/login', element: <LoginPage /> },
{ path: '/u/:slug', element: <PublicProfilePage /> },
{

View File

@@ -26,6 +26,7 @@ FROM deps AS build
WORKDIR /app
COPY packages/shared packages/shared
COPY apps/backend apps/backend
RUN pnpm --filter @family-wishlist/shared build
RUN pnpm --filter @family-wishlist/backend build
# ---------- runtime ----------
@@ -48,4 +49,4 @@ WORKDIR /app/apps/backend
EXPOSE 3000
# Apply schema (idempotent; uses `db push` so no prior migrations required) +
# seed env users + start server.
CMD ["sh", "-c", "pnpm exec prisma db push --accept-data-loss --skip-generate && pnpm seed && node dist/index.js"]
CMD ["sh", "-c", "pnpm exec prisma db push --accept-data-loss --skip-generate && node dist/prisma/seed.js && node dist/src/index.js"]

View File

@@ -20,6 +20,7 @@ FROM deps AS build
WORKDIR /app
COPY packages/shared packages/shared
COPY apps/frontend apps/frontend
RUN pnpm --filter @family-wishlist/shared build
RUN pnpm --filter @family-wishlist/frontend build
# ---------- runtime (nginx) ----------

View File

@@ -3,16 +3,19 @@
"version": "0.1.0",
"private": true,
"type": "module",
"main": "./src/index.ts",
"types": "./src/index.ts",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": "./src/index.ts"
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
}
},
"scripts": {
"typecheck": "tsc --noEmit",
"build": "echo 'shared is source-only'",
"build": "tsc -p tsconfig.json",
"lint": "echo 'skip'",
"dev": "echo 'shared is source-only'"
"dev": "tsc -p tsconfig.json --watch"
},
"dependencies": {
"zod": "^3.23.8"

View File

@@ -3,7 +3,7 @@
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"noEmit": true
"noEmit": false
},
"include": ["src/**/*"]
}

3647
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff