Skip to content

Commit f3e3cea

Browse files
committed
fix: expand allowed file upload types to support office docs and Apple formats
1 parent d9e063a commit f3e3cea

File tree

4 files changed

+80
-45
lines changed

4 files changed

+80
-45
lines changed

src/lib/validation.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,15 +105,42 @@ export const foldersQuerySchema = z
105105

106106
// Allowed MIME types for security
107107
const allowedMimeTypes = [
108+
// Images
108109
"image/jpeg",
110+
"image/jpg",
109111
"image/png",
110112
"image/gif",
111113
"image/webp",
114+
"image/svg+xml",
115+
"image/bmp",
116+
"image/tiff",
117+
"image/avif",
118+
// Apple formats (iPhone/iPad photos)
119+
"image/heic",
120+
"image/heif",
121+
"image/heic-sequence", // Live Photos
122+
"image/heif-sequence",
123+
// Documents
112124
"application/pdf",
113125
"text/plain",
114126
"text/markdown",
115127
"application/json",
116128
"text/csv",
129+
// Microsoft Office
130+
"application/msword", // .doc
131+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", // .docx
132+
"application/vnd.ms-excel", // .xls
133+
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", // .xlsx
134+
"application/vnd.ms-powerpoint", // .ppt
135+
"application/vnd.openxmlformats-officedocument.presentationml.presentation", // .pptx
136+
// Archives
137+
"application/zip",
138+
"application/x-rar-compressed",
139+
"application/x-7z-compressed",
140+
// Other common formats
141+
"text/html",
142+
"application/xml",
143+
"text/xml",
117144
] as const;
118145

119146
export const uploadFileSchema = z.object({

src/middleware/auth.ts

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@ import { verifyToken } from "@clerk/backend";
44
import { db, users, folders, type User } from "../db";
55
import { eq } from "drizzle-orm";
66
import { logger } from "../lib/logger";
7-
import type {
8-
ClerkUserData,
9-
ClerkJWTPayload,
10-
UserUpdateData,
11-
ClerkApiUser,
12-
DatabaseError,
13-
} from "../types";
7+
import type { ClerkUserData, ClerkJWTPayload, UserUpdateData, DatabaseError } from "../types";
148

159
if (!process.env.CLERK_SECRET_KEY) {
1610
throw new Error(
@@ -27,6 +21,7 @@ declare module "hono" {
2721
}
2822

2923
const extractAndVerifyClerkToken = async (c: Context): Promise<ClerkUserData | null> => {
24+
const startTime = Date.now();
3025
const authHeader = c.req.header("Authorization");
3126

3227
if (!authHeader || !authHeader.startsWith("Bearer ")) {
@@ -36,48 +31,29 @@ const extractAndVerifyClerkToken = async (c: Context): Promise<ClerkUserData | n
3631
const token = authHeader.split(" ")[1];
3732

3833
try {
34+
const verifyStart = Date.now();
3935
const payload = (await verifyToken(token, {
4036
secretKey: process.env.CLERK_SECRET_KEY!,
4137
})) as unknown as ClerkJWTPayload;
42-
43-
try {
44-
const userResponse = await fetch(`https://api.clerk.com/v1/users/${payload.sub}`, {
45-
headers: {
46-
Authorization: `Bearer ${process.env.CLERK_SECRET_KEY}`,
47-
"Content-Type": "application/json",
48-
},
49-
});
50-
51-
if (userResponse.ok) {
52-
const clerkUser: ClerkApiUser = await userResponse.json();
53-
return {
54-
id: clerkUser.id,
55-
email: clerkUser.email_addresses?.[0]?.email_address || "",
56-
firstName: clerkUser.first_name || null,
57-
lastName: clerkUser.last_name || null,
58-
};
59-
} else {
60-
return {
61-
id: payload.sub,
62-
email: "",
63-
firstName: null,
64-
lastName: null,
65-
};
66-
}
67-
} catch {
68-
return {
69-
id: payload.sub,
70-
email: "",
71-
firstName: null,
72-
lastName: null,
73-
};
74-
}
38+
console.log(`[AUTH PERF] verifyToken took: ${Date.now() - verifyStart}ms`);
39+
40+
// Security is maintained by JWT verification above
41+
// User metadata comes from our DB (updated via webhooks or on login)
42+
// No need to call Clerk API on every request - saves 150-200ms
43+
console.log(`[AUTH PERF] Total extractAndVerify took: ${Date.now() - startTime}ms`);
44+
return {
45+
id: payload.sub,
46+
email: "", // Will be populated from DB
47+
firstName: null, // Will be populated from DB
48+
lastName: null, // Will be populated from DB
49+
};
7550
} catch {
7651
return null;
7752
}
7853
};
7954

8055
export const authMiddleware = async (c: Context, next: Next) => {
56+
const middlewareStart = Date.now();
8157
const userData = await extractAndVerifyClerkToken(c);
8258

8359
if (!userData) {
@@ -86,9 +62,11 @@ export const authMiddleware = async (c: Context, next: Next) => {
8662
});
8763
}
8864

65+
const dbQueryStart = Date.now();
8966
let existingUser = await db.query.users.findFirst({
9067
where: eq(users.id, userData.id),
9168
});
69+
console.log(`[AUTH PERF] DB user lookup took: ${Date.now() - dbQueryStart}ms`);
9270

9371
if (existingUser) {
9472
try {
@@ -170,6 +148,8 @@ export const authMiddleware = async (c: Context, next: Next) => {
170148
c.set("user", existingUser);
171149
c.set("clerkUser", userData);
172150

151+
console.log(`[AUTH PERF] Total auth middleware took: ${Date.now() - middlewareStart}ms`);
152+
173153
// User context available in Hono context
174154

175155
await next();

src/routes/notes/crud.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ const listNotesRoute = createRoute({
4545

4646
// Handler function for listing notes
4747
const listNotesHandler: RouteHandler<typeof listNotesRoute> = async (c) => {
48+
const startTime = Date.now();
4849
const userId = c.get("userId");
4950
const query = c.req.valid("query");
51+
console.log(`[PERF] Start listNotes - userId: ${userId}`);
5052

5153
const conditions: SQL[] = [eq(notes.userId, userId)];
5254

@@ -86,30 +88,56 @@ const listNotesHandler: RouteHandler<typeof listNotesRoute> = async (c) => {
8688
}
8789

8890
const whereClause = conditions.length > 1 ? and(...conditions) : conditions[0];
91+
console.log(`[PERF] Building where clause took: ${Date.now() - startTime}ms`);
8992

93+
const countStart = Date.now();
9094
const [{ total }] = await db.select({ total: count() }).from(notes).where(whereClause);
95+
console.log(`[PERF] COUNT query took: ${Date.now() - countStart}ms, total: ${total}`);
9196

9297
const page = query.page || 1;
9398
const limit = query.limit || 20;
9499
const offset = (page - 1) * limit;
95100

101+
const queryStart = Date.now();
96102
const userNotes = await db.query.notes.findMany({
97103
where: whereClause,
98104
orderBy: [desc(notes.updatedAt)],
99105
limit,
100106
offset,
101107
with: {
102108
folder: true,
103-
// Removed attachments loading - was loading full encrypted file data unnecessarily
109+
attachments: {
110+
// Load attachment metadata but exclude the massive encryptedData field
111+
columns: {
112+
id: true,
113+
noteId: true,
114+
filename: true,
115+
originalName: true,
116+
mimeType: true,
117+
size: true,
118+
encryptedTitle: true,
119+
iv: true,
120+
salt: true,
121+
uploadedAt: true,
122+
encryptedData: false,
123+
},
124+
},
104125
},
105126
});
127+
console.log(
128+
`[PERF] Main query took: ${Date.now() - queryStart}ms, returned ${userNotes.length} notes`
129+
);
106130

107-
// Note: attachmentCount removed for now - can be added back with efficient subquery if needed
131+
// Add attachment counts to notes
132+
const mapStart = Date.now();
108133
const notesWithAttachmentCount = userNotes.map((note) => ({
109134
...note,
110-
attachmentCount: 0, // Placeholder - will optimize this separately if needed
111-
attachments: undefined,
135+
attachmentCount: note.attachments.length,
112136
}));
137+
console.log(`[PERF] Mapping took: ${Date.now() - mapStart}ms`);
138+
139+
const totalTime = Date.now() - startTime;
140+
console.log(`[PERF] Total endpoint time: ${totalTime}ms`);
113141

114142
return c.json(
115143
{

src/version.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
// This file is automatically updated by semantic-release
2-
export const VERSION = "1.11.10"
2+
export const VERSION = "1.11.10";

0 commit comments

Comments
 (0)