Compare commits
10 Commits
chore/shar
...
chore/upda
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
547a452097 | ||
| 3d7501f028 | |||
|
|
7c658706ea | ||
| 1c9c21d5a7 | |||
|
|
89f75e6d40 | ||
| e69f53114d | |||
|
|
793f0c3422 | ||
| d99002dc3c | |||
|
|
c49abafc61 | ||
| 4f4f9ff998 |
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
|
||||
@@ -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 /> },
|
||||
{
|
||||
|
||||
@@ -38,7 +38,7 @@ services:
|
||||
family-wishlist-backend:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "8080:80"
|
||||
- "3055:80"
|
||||
networks:
|
||||
- postgres_default
|
||||
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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) ----------
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"noEmit": true
|
||||
"noEmit": false
|
||||
},
|
||||
"include": ["src/**/*"]
|
||||
}
|
||||
|
||||
3647
pnpm-lock.yaml
generated
Normal file
3647
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user