feat : update

This commit is contained in:
2026-10-02 17:33:14 +07:00 Unverified
parent 63898fb90d
commit a966d996a9
34 changed files with 4678 additions and 147 deletions
+12
View File
@@ -0,0 +1,12 @@
import { describe, expect, it } from "vitest";
import { isSingleEmoji } from "./reaction";
describe("commission reactions", () => {
it("accepts emoji outside the old six choices, including sequences", () => {
for (const emoji of ["🔥", "🇹🇭", "👨‍👩‍👧‍👦", "1️⃣"]) expect(isSingleEmoji(emoji)).toBe(true);
});
it("rejects text and multiple reactions", () => {
for (const value of ["", "hello", "1", "🔥🔥", "🔥 hello"]) expect(isSingleEmoji(value)).toBe(false);
});
});
+7
View File
@@ -0,0 +1,7 @@
import emojiRegex from "emoji-regex";
export function isSingleEmoji(value: string): boolean {
if (!value || value.length > 64) return false;
const matches = [...value.matchAll(emojiRegex())];
return matches.length === 1 && matches[0][0] === value;
}
+9 -3
View File
@@ -18,7 +18,7 @@ export async function getCommissionTicket(id: string) {
if (!row || (user.role !== "admin" || !user.emailVerified) && row.ticket.userId !== user.id)
throw new HttpError(404, "ticket-not-found");
const [messages, reactions] = await Promise.all([
getDb().select({ message: commissionMessages, authorName: users.name }).from(commissionMessages)
getDb().select({ message: commissionMessages, authorName: users.name, authorImage: users.image }).from(commissionMessages)
.innerJoin(users, eq(commissionMessages.authorId, users.id))
.where(eq(commissionMessages.ticketId, id)).orderBy(asc(commissionMessages.createdAt)),
getDb().select({ reaction: commissionReactions, messageId: commissionMessages.id })
@@ -26,8 +26,14 @@ export async function getCommissionTicket(id: string) {
.innerJoin(commissionMessages, eq(commissionReactions.messageId, commissionMessages.id))
.where(eq(commissionMessages.ticketId, id)),
]);
return { ...row, messages: messages.map(({ message, authorName }) => ({ ...message, authorName,
reactions: reactions.filter(({ messageId }) => messageId === message.id).map(({ reaction }) => reaction) })),
const reactionsByMessage = new Map<string, typeof commissionReactions.$inferSelect[]>();
for (const { reaction, messageId } of reactions) {
const group = reactionsByMessage.get(messageId) ?? [];
group.push(reaction);
reactionsByMessage.set(messageId, group);
}
return { ...row, messages: messages.map(({ message, authorName, authorImage }) => ({ ...message, authorName,
authorImage, reactions: reactionsByMessage.get(message.id) ?? [] })),
currentUserId: user.id };
}
+2 -2
View File
@@ -1,4 +1,4 @@
export function securityHeaders(production: boolean) {
export function securityHeaders(production: boolean, emojiImageCdn: string) {
const ads = "https://*.googlesyndication.com https://*.doubleclick.net https://*.googleadservices.com https://*.googletagservices.com https://www.google.com";
const cloudflareAnalytics = "https://static.cloudflareinsights.com";
const adConnections = "https://*.adtrafficquality.google";
@@ -6,7 +6,7 @@ export function securityHeaders(production: boolean) {
"default-src 'self'",
`script-src 'self' 'unsafe-inline' ${production ? "" : "'unsafe-eval'"} https://challenges.cloudflare.com ${cloudflareAnalytics} ${ads}`,
"style-src 'self' 'unsafe-inline'",
`img-src 'self' data: blob: https://buzz-cdn.astrxl.dev ${ads}`,
`img-src 'self' data: blob: https://buzz-cdn.astrxl.dev ${emojiImageCdn} ${ads}`,
"font-src 'self' data:",
`connect-src 'self' https://buzz-cdn.astrxl.dev https://challenges.cloudflare.com ${adConnections} ${ads}${production ? "" : " ws: wss:"}`,
`frame-src https://challenges.cloudflare.com https://www.youtube-nocookie.com ${ads}`,