오류 수정
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
|
||||||
const dbQueryMock = vi.fn();
|
const { dbQueryMock } = vi.hoisted(() => ({
|
||||||
|
dbQueryMock: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
vi.mock('../lib/database.js', () => ({
|
vi.mock('../lib/database.js', () => ({
|
||||||
db: {
|
db: {
|
||||||
@@ -15,17 +17,25 @@ describe('resolveLinkTemplateId', () => {
|
|||||||
dbQueryMock.mockReset();
|
dbQueryMock.mockReset();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('returns null when the provided template id does not exist', async () => {
|
it('falls back to the default template when the provided id does not exist', async () => {
|
||||||
dbQueryMock.mockResolvedValueOnce({ rows: [] });
|
|
||||||
|
|
||||||
await expect(resolveLinkTemplateId('00000000-0000-0000-0000-000000000000', null)).resolves.toBeNull();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('falls back to template slug when the provided id is not usable', async () => {
|
|
||||||
dbQueryMock.mockResolvedValueOnce({ rows: [] });
|
dbQueryMock.mockResolvedValueOnce({ rows: [] });
|
||||||
dbQueryMock.mockResolvedValueOnce({ rows: [{ id: '11111111-1111-1111-1111-111111111111' }] });
|
dbQueryMock.mockResolvedValueOnce({ rows: [{ id: '11111111-1111-1111-1111-111111111111' }] });
|
||||||
|
|
||||||
|
await expect(resolveLinkTemplateId('00000000-0000-0000-0000-000000000000', null)).resolves.toBe('11111111-1111-1111-1111-111111111111');
|
||||||
|
expect(dbQueryMock).toHaveBeenNthCalledWith(2, 'SELECT id FROM link_templates WHERE is_default = true LIMIT 1');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses the template slug when it can be resolved', async () => {
|
||||||
|
dbQueryMock.mockResolvedValueOnce({ rows: [{ id: '11111111-1111-1111-1111-111111111111' }] });
|
||||||
|
|
||||||
await expect(resolveLinkTemplateId(null, 'default')).resolves.toBe('11111111-1111-1111-1111-111111111111');
|
await expect(resolveLinkTemplateId(null, 'default')).resolves.toBe('11111111-1111-1111-1111-111111111111');
|
||||||
expect(dbQueryMock).toHaveBeenNthCalledWith(2, 'SELECT id FROM link_templates WHERE slug = $1 LIMIT 1', ['default']);
|
expect(dbQueryMock).toHaveBeenCalledWith('SELECT id FROM link_templates WHERE slug = $1 LIMIT 1', ['default']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('falls back to the default template when the provided template id is invalid', async () => {
|
||||||
|
dbQueryMock.mockResolvedValueOnce({ rows: [{ id: '22222222-2222-2222-2222-222222222222' }] });
|
||||||
|
|
||||||
|
await expect(resolveLinkTemplateId('not-a-uuid', null)).resolves.toBe('22222222-2222-2222-2222-222222222222');
|
||||||
|
expect(dbQueryMock).toHaveBeenCalledWith('SELECT id FROM link_templates WHERE is_default = true LIMIT 1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+44
-2
@@ -13,6 +13,43 @@ async function getTemplateSlug(templateId: string | null): Promise<string | null
|
|||||||
return result.rows[0]?.slug ?? null;
|
return result.rows[0]?.slug ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function resolveLinkTemplateId(
|
||||||
|
templateId: string | null | undefined,
|
||||||
|
templateSlug: string | null | undefined
|
||||||
|
): Promise<string | null> {
|
||||||
|
const normalizedTemplateId = templateId?.trim();
|
||||||
|
const normalizedSlug = templateSlug?.trim();
|
||||||
|
|
||||||
|
if (normalizedTemplateId) {
|
||||||
|
const parsed = z.string().uuid().safeParse(normalizedTemplateId);
|
||||||
|
if (parsed.success) {
|
||||||
|
const result = await db.query(
|
||||||
|
'SELECT id FROM link_templates WHERE id = $1 LIMIT 1',
|
||||||
|
[normalizedTemplateId]
|
||||||
|
);
|
||||||
|
if (result.rows[0]?.id) {
|
||||||
|
return result.rows[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalizedSlug) {
|
||||||
|
const result = await db.query(
|
||||||
|
'SELECT id FROM link_templates WHERE slug = $1 LIMIT 1',
|
||||||
|
[normalizedSlug]
|
||||||
|
);
|
||||||
|
if (result.rows[0]?.id) {
|
||||||
|
return result.rows[0].id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const defaultResult = await db.query(
|
||||||
|
'SELECT id FROM link_templates WHERE is_default = true LIMIT 1'
|
||||||
|
);
|
||||||
|
|
||||||
|
return defaultResult.rows[0]?.id ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
const createLinkSchema = z.object({
|
const createLinkSchema = z.object({
|
||||||
userId: z.string().uuid().optional(),
|
userId: z.string().uuid().optional(),
|
||||||
templateId: z.string().uuid().optional(),
|
templateId: z.string().uuid().optional(),
|
||||||
@@ -182,6 +219,7 @@ export async function linkRoutes(fastify: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const originalUrl = data.originalUrl || 'https://angkorlifes.com';
|
const originalUrl = data.originalUrl || 'https://angkorlifes.com';
|
||||||
|
const resolvedTemplateId = await resolveLinkTemplateId(data.templateId, null);
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`INSERT INTO links (
|
`INSERT INTO links (
|
||||||
@@ -195,7 +233,7 @@ export async function linkRoutes(fastify: FastifyInstance) {
|
|||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
data.userId || null,
|
data.userId || null,
|
||||||
data.templateId || null,
|
resolvedTemplateId,
|
||||||
shortCode,
|
shortCode,
|
||||||
originalUrl,
|
originalUrl,
|
||||||
data.title || null,
|
data.title || null,
|
||||||
@@ -238,6 +276,10 @@ export async function linkRoutes(fastify: FastifyInstance) {
|
|||||||
const { userId } = request.query;
|
const { userId } = request.query;
|
||||||
|
|
||||||
const data = updateLinkSchema.parse(request.body);
|
const data = updateLinkSchema.parse(request.body);
|
||||||
|
const resolvedData = {
|
||||||
|
...data,
|
||||||
|
templateId: data.templateId === undefined ? undefined : await resolveLinkTemplateId(data.templateId, null),
|
||||||
|
};
|
||||||
|
|
||||||
// Capture current identifiers for cache invalidation after the update
|
// Capture current identifiers for cache invalidation after the update
|
||||||
const oldLinkResult = await db.query(
|
const oldLinkResult = await db.query(
|
||||||
@@ -250,7 +292,7 @@ export async function linkRoutes(fastify: FastifyInstance) {
|
|||||||
const values: any[] = [];
|
const values: any[] = [];
|
||||||
let paramIndex = 1;
|
let paramIndex = 1;
|
||||||
|
|
||||||
Object.entries(data).forEach(([key, value]) => {
|
Object.entries(resolvedData).forEach(([key, value]) => {
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
if (key === 'utmParameters' || key === 'targetingRules' || key === 'deepLinkParameters') {
|
if (key === 'utmParameters' || key === 'targetingRules' || key === 'deepLinkParameters') {
|
||||||
updates.push(`${key.replace(/([A-Z])/g, '_$1').toLowerCase()} = $${paramIndex}`);
|
updates.push(`${key.replace(/([A-Z])/g, '_$1').toLowerCase()} = $${paramIndex}`);
|
||||||
|
|||||||
+3
-1
@@ -12,6 +12,7 @@ import { triggerWebhooks } from '../lib/webhook.js';
|
|||||||
import { parseUserAgent, getLocationFromIP, detectDevice } from '../lib/utils.js';
|
import { parseUserAgent, getLocationFromIP, detectDevice } from '../lib/utils.js';
|
||||||
import { emitClickEvent } from '../lib/event-emitter.js';
|
import { emitClickEvent } from '../lib/event-emitter.js';
|
||||||
import { classifyBot, edgeBotSignal } from '../lib/bot-detection.js';
|
import { classifyBot, edgeBotSignal } from '../lib/bot-detection.js';
|
||||||
|
import { resolveLinkTemplateId } from './links.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SDK Routes - Mobile SDK endpoints for deferred deep linking
|
* SDK Routes - Mobile SDK endpoints for deferred deep linking
|
||||||
@@ -66,6 +67,7 @@ export async function sdkRoutes(fastify: FastifyInstance) {
|
|||||||
const originalUrl = body.originalUrl || 'https://angkorlifes.com';
|
const originalUrl = body.originalUrl || 'https://angkorlifes.com';
|
||||||
|
|
||||||
const shortCode = body.customCode || 'sdk-link';
|
const shortCode = body.customCode || 'sdk-link';
|
||||||
|
const resolvedTemplateId = await resolveLinkTemplateId(body.templateId, null);
|
||||||
|
|
||||||
const result = await db.query(
|
const result = await db.query(
|
||||||
`INSERT INTO links (
|
`INSERT INTO links (
|
||||||
@@ -79,7 +81,7 @@ export async function sdkRoutes(fastify: FastifyInstance) {
|
|||||||
RETURNING *`,
|
RETURNING *`,
|
||||||
[
|
[
|
||||||
body.userId || null,
|
body.userId || null,
|
||||||
body.templateId || null,
|
resolvedTemplateId,
|
||||||
shortCode,
|
shortCode,
|
||||||
originalUrl,
|
originalUrl,
|
||||||
body.title || null,
|
body.title || null,
|
||||||
|
|||||||
Reference in New Issue
Block a user