- 原生 Next.js App Router + 严格 TypeScript 实现花魁短剧 PC/H5 页面,按 src/features/{catalog,content,playback} 组织,保留完整 SEO(metadata、JSON-LD、canonical、robots、llms、sitemap、IndexNow)。
- 主域名通过 SITE_URL=https://huakuidj.com 强制覆盖后端 foreverUrlAddr(config/*.env、src/server/config.ts、src/server/seo/shared.ts、src/server/services/shared.ts),支持邮箱改为 support@huakuidj.com。
- 移除登录与账户体系:删除 /account 全部页面、登录/注册/充值弹窗、收藏点赞、评论、金币解锁及对应 BFF 接口;旧账户地址 GET/HEAD 统一 301 到首页(src/lib/routes.ts REMOVED_ACCOUNT_PATHS),保留游客会话、反馈、图片上传、观看上报与搜索联想。
- 播放页:评论位改为同类推荐 + 热播榜单,锁定集改为 App 观看遮罩与底部弹层,新增简介标签、分集图例与独立视觉皮肤(src/features/playback、src/styles/pages/playback.css)。
- 卡片:短剧/视频卡状态与时长移至信息行胶囊标签,封面角标移至左上角(src/components/catalog/Cards.tsx、src/styles/foundations/base.css)。
- 同步更新单元测试、浏览器冒烟脚本、路由清单与 README。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
47 lines
2.3 KiB
TypeScript
47 lines
2.3 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { createContext } from '../src/server/context';
|
|
import type { Data } from '../src/server/services/model';
|
|
import { loadSharedData } from '../src/server/services/shared';
|
|
|
|
const sharedFixture = (): Data => ({
|
|
appConfig: { config: {} }, categoryList: [{ name: '首页', urlValue: '/', categoryType: 1 }],
|
|
themeList: [], homeConfigData: [], searchTrends: [],
|
|
});
|
|
const context = (avatar: string, cookieAvatar = avatar) => {
|
|
const ctx = createContext(new Request('http://site.test/profile/', {
|
|
headers: { cookie: `token=guest-session; avatar=${encodeURIComponent(cookieAvatar)}` },
|
|
}));
|
|
ctx.user = { userId: 7, nickName: '游客', avatar, type: 0, token: 'guest-session' };
|
|
return ctx;
|
|
};
|
|
|
|
test('shared avatars preserve API image keys and usable URLs, reject other relative paths, and never rewrite auth values', async () => {
|
|
const relative = 'v3/image/guest/avatar.enc';
|
|
for (const [avatar, cookieAvatar, expected] of [
|
|
[relative, relative, relative],
|
|
['/v3/image/guest/avatar.enc', '/v3/image/guest/avatar.enc', '/v3/image/guest/avatar.enc'],
|
|
['ordinary-relative-avatar.png', 'ordinary-relative-avatar.png', ''],
|
|
['//cdn.test/avatar.png', '//cdn.test/avatar.png', ''],
|
|
['javascript:alert(1)', 'javascript:alert(1)', ''],
|
|
['https://', 'https://', ''],
|
|
[' https://cdn.test/avatar.enc?key=abc ', relative, 'https://cdn.test/avatar.enc?key=abc'],
|
|
['http://cdn.test/avatar.png', relative, 'http://cdn.test/avatar.png'],
|
|
['/static/images/profile/avatar.png', relative, '/static/images/profile/avatar.png'],
|
|
[relative, 'https://cdn.test/cookie-avatar.png', relative],
|
|
['ordinary-relative-avatar.png', 'https://cdn.test/cookie-avatar.png', 'https://cdn.test/cookie-avatar.png'],
|
|
] as const) {
|
|
const ctx = context(avatar, cookieAvatar);
|
|
const requestCookie = ctx.headers.get('cookie');
|
|
const data = await loadSharedData(ctx, sharedFixture());
|
|
assert.equal(data.avatar, expected, avatar);
|
|
assert.equal(ctx.user?.avatar, avatar);
|
|
assert.equal(ctx.user?.token, 'guest-session');
|
|
assert.equal(ctx.token, 'guest-session');
|
|
assert.equal(ctx.getCookie('avatar'), cookieAvatar);
|
|
assert.equal(ctx.headers.get('cookie'), requestCookie);
|
|
assert.deepEqual(ctx.responseHeaders.getSetCookie(), []);
|
|
}
|
|
});
|
|
|