mirror of
https://github.com/willchen96/mike.git
synced 2026-06-14 20:55:13 +02:00
Update document UI, tabular reviews, and storage caching
This commit is contained in:
parent
2bbb628891
commit
4f3384334a
26 changed files with 856 additions and 341 deletions
|
|
@ -1,4 +1,3 @@
|
|||
import { promisify } from "util";
|
||||
import JSZip from "jszip";
|
||||
|
||||
let _convert:
|
||||
|
|
@ -8,7 +7,26 @@ let _convert:
|
|||
async function getConvert() {
|
||||
if (!_convert) {
|
||||
const libre = await import("libreoffice-convert");
|
||||
_convert = promisify(libre.default.convert.bind(libre.default));
|
||||
const convert = libre.default.convert.bind(libre.default) as (
|
||||
buf: Buffer,
|
||||
ext: string,
|
||||
filter: undefined,
|
||||
callback?: (err: Error | null, result: Buffer) => void,
|
||||
) => Promise<Buffer> | void;
|
||||
_convert = (buf, ext, filter) =>
|
||||
new Promise<Buffer>((resolve, reject) => {
|
||||
try {
|
||||
const maybePromise = convert(buf, ext, filter, (err, result) => {
|
||||
if (err) reject(err);
|
||||
else resolve(result);
|
||||
});
|
||||
if (maybePromise && typeof maybePromise.then === "function") {
|
||||
maybePromise.then(resolve, reject);
|
||||
}
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
return _convert;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,16 +17,21 @@ import {
|
|||
} from "@aws-sdk/client-s3";
|
||||
import { getSignedUrl as awsGetSignedUrl } from "@aws-sdk/s3-request-presigner";
|
||||
|
||||
let cachedClient: S3Client | undefined;
|
||||
|
||||
function getClient(): S3Client {
|
||||
return new S3Client({
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT_URL!,
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
if (!cachedClient) {
|
||||
cachedClient = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT_URL!,
|
||||
forcePathStyle: true,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
||||
},
|
||||
});
|
||||
}
|
||||
return cachedClient;
|
||||
}
|
||||
|
||||
const BUCKET = process.env.R2_BUCKET_NAME ?? "mike";
|
||||
|
|
@ -37,6 +42,14 @@ export const storageEnabled = Boolean(
|
|||
process.env.R2_SECRET_ACCESS_KEY,
|
||||
);
|
||||
|
||||
function requireStorageConfig(): void {
|
||||
if (!storageEnabled) {
|
||||
throw new Error(
|
||||
"R2_ENDPOINT_URL, R2_ACCESS_KEY_ID, and R2_SECRET_ACCESS_KEY must be set",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Upload
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -46,6 +59,7 @@ export async function uploadFile(
|
|||
content: ArrayBuffer,
|
||||
contentType: string,
|
||||
): Promise<void> {
|
||||
requireStorageConfig();
|
||||
const client = getClient();
|
||||
await client.send(
|
||||
new PutObjectCommand({
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ async function getAccessibleChat(
|
|||
chatRouter.get("/", requireAuth, async (req, res) => {
|
||||
const userId = res.locals.userId as string;
|
||||
const db = createServerSupabase();
|
||||
const requestedLimit = Number.parseInt(String(req.query.limit ?? ""), 10);
|
||||
const limit = Number.isFinite(requestedLimit)
|
||||
? Math.min(Math.max(requestedLimit, 1), 100)
|
||||
: null;
|
||||
|
||||
const { data: ownProjects, error: projErr } = await db
|
||||
.from("projects")
|
||||
|
|
@ -156,11 +160,15 @@ chatRouter.get("/", requireAuth, async (req, res) => {
|
|||
? `user_id.eq.${userId},project_id.in.(${ownProjectIds.join(",")})`
|
||||
: `user_id.eq.${userId}`;
|
||||
|
||||
const { data, error } = await db
|
||||
let query = db
|
||||
.from("chats")
|
||||
.select("*")
|
||||
.or(filter)
|
||||
.order("created_at", { ascending: false });
|
||||
|
||||
if (limit) query = query.limit(limit);
|
||||
|
||||
const { data, error } = await query;
|
||||
if (error) return void res.status(500).json({ detail: error.message });
|
||||
res.json(data ?? []);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -165,6 +165,15 @@ tabularRouter.get("/", requireAuth, async (req, res) => {
|
|||
// Fetch distinct document counts per review
|
||||
const reviewIds = reviews.map((r) => (r as { id: string }).id);
|
||||
let docCounts: Record<string, number> = {};
|
||||
const reviewsWithExplicitDocs = new Set<string>();
|
||||
for (const review of reviews) {
|
||||
const id = (review as { id: string }).id;
|
||||
if (Array.isArray(review.document_ids)) {
|
||||
const explicitDocIds = review.document_ids;
|
||||
reviewsWithExplicitDocs.add(id);
|
||||
docCounts[id] = new Set(explicitDocIds).size;
|
||||
}
|
||||
}
|
||||
if (reviewIds.length > 0) {
|
||||
const { data: cells } = await db
|
||||
.from("tabular_cells")
|
||||
|
|
@ -176,8 +185,10 @@ tabularRouter.get("/", requireAuth, async (req, res) => {
|
|||
const key = `${cell.review_id}:${cell.document_id}`;
|
||||
if (!seen.has(key)) {
|
||||
seen.add(key);
|
||||
docCounts[cell.review_id] =
|
||||
(docCounts[cell.review_id] ?? 0) + 1;
|
||||
if (!reviewsWithExplicitDocs.has(cell.review_id)) {
|
||||
docCounts[cell.review_id] =
|
||||
(docCounts[cell.review_id] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -229,6 +240,7 @@ tabularRouter.post("/", requireAuth, async (req, res) => {
|
|||
user_id: userId,
|
||||
title: title ?? null,
|
||||
columns_config,
|
||||
document_ids: allowedDocumentIds,
|
||||
project_id: project_id ?? null,
|
||||
workflow_id: workflow_id ?? null,
|
||||
})
|
||||
|
|
@ -345,17 +357,19 @@ tabularRouter.get("/:reviewId", requireAuth, async (req, res) => {
|
|||
.from("tabular_cells")
|
||||
.select("*")
|
||||
.eq("review_id", reviewId);
|
||||
const docIds = [...new Set((cells ?? []).map((c) => c.document_id))];
|
||||
const cellDocIds = [...new Set((cells ?? []).map((c) => c.document_id))];
|
||||
const hasExplicitDocIds = Array.isArray(review.document_ids);
|
||||
const explicitDocIds = hasExplicitDocIds
|
||||
? (review.document_ids as string[])
|
||||
: [];
|
||||
const docIds =
|
||||
hasExplicitDocIds
|
||||
? explicitDocIds
|
||||
: cellDocIds;
|
||||
const docsResult =
|
||||
docIds.length > 0
|
||||
? await db.from("documents").select("*").in("id", docIds)
|
||||
: review.project_id
|
||||
? await db
|
||||
.from("documents")
|
||||
.select("*")
|
||||
.eq("project_id", review.project_id)
|
||||
.order("created_at", { ascending: true })
|
||||
: { data: [] as Record<string, unknown>[] };
|
||||
: { data: [] as Record<string, unknown>[] };
|
||||
|
||||
res.json({
|
||||
review: { ...review, is_owner: access.isOwner },
|
||||
|
|
@ -517,6 +531,7 @@ tabularRouter.patch("/:reviewId", requireAuth, async (req, res) => {
|
|||
detail: updateError?.message ?? "Failed to update review",
|
||||
});
|
||||
|
||||
let persistedDocumentIds: string[] | undefined;
|
||||
if (
|
||||
Array.isArray(req.body.columns_config) ||
|
||||
Array.isArray(req.body.document_ids)
|
||||
|
|
@ -577,13 +592,21 @@ tabularRouter.patch("/:reviewId", requireAuth, async (req, res) => {
|
|||
(existingCells ?? []).map((cell) => cell.document_id),
|
||||
),
|
||||
];
|
||||
if (documentIds.length === 0 && existingReview.project_id) {
|
||||
const { data: projectDocs } = await db
|
||||
.from("documents")
|
||||
.select("id")
|
||||
.eq("project_id", existingReview.project_id);
|
||||
documentIds = (projectDocs ?? []).map((doc) => doc.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(req.body.document_ids)) {
|
||||
persistedDocumentIds = documentIds;
|
||||
const { error: documentIdsError } = await db
|
||||
.from("tabular_reviews")
|
||||
.update({
|
||||
document_ids: documentIds,
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq("id", reviewId);
|
||||
if (documentIdsError)
|
||||
return void res.status(500).json({
|
||||
detail: documentIdsError.message,
|
||||
});
|
||||
}
|
||||
|
||||
const activeColumns = Array.isArray(req.body.columns_config)
|
||||
|
|
@ -614,7 +637,10 @@ tabularRouter.patch("/:reviewId", requireAuth, async (req, res) => {
|
|||
}
|
||||
}
|
||||
|
||||
res.json(updatedReview);
|
||||
res.json({
|
||||
...updatedReview,
|
||||
...(persistedDocumentIds ? { document_ids: persistedDocumentIds } : {}),
|
||||
});
|
||||
});
|
||||
|
||||
// DELETE /tabular-review/:reviewId
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue