|
6 | 6 | groupPagesListQuerySchema, |
7 | 7 | pageBacklinkCreateRequestSchema, |
8 | 8 | pageBumpRequestSchema, |
| 9 | + pageCollabUpdatesAppendRequestSchema, |
9 | 10 | pageMoveRequestSchema, |
10 | 11 | pageIdPathSchema, |
11 | 12 | pageSnapshotCreateResponseSchema, |
@@ -1577,6 +1578,130 @@ app.post("/api/pages/:pageId/bump", async (c) => { |
1577 | 1578 | } |
1578 | 1579 | }); |
1579 | 1580 |
|
| 1581 | +app.get("/api/pages/:pageId/collab-updates", async (c) => { |
| 1582 | + const sessionEnv = getSessionEnv(c.env); |
| 1583 | + if (sessionEnv == null) { |
| 1584 | + return c.json(serviceUnavailableBody, 503); |
| 1585 | + } |
| 1586 | + const hyper = c.env.HYPERDRIVE; |
| 1587 | + if (hyper == null) { |
| 1588 | + return c.json( |
| 1589 | + { |
| 1590 | + code: "SERVICE_UNAVAILABLE" as const, |
| 1591 | + message: "HYPERDRIVE binding is not configured.", |
| 1592 | + }, |
| 1593 | + 503, |
| 1594 | + ); |
| 1595 | + } |
| 1596 | + |
| 1597 | + const pParams = pageIdPathSchema.safeParse({ pageId: c.req.param("pageId") }); |
| 1598 | + if (!pParams.success) { |
| 1599 | + return c.json( |
| 1600 | + { code: "VALIDATION_ERROR", message: pParams.error.message }, |
| 1601 | + 400, |
| 1602 | + ); |
| 1603 | + } |
| 1604 | + |
| 1605 | + const db = getDbForConnectionString(hyper.connectionString); |
| 1606 | + const cookieHeader = c.req.header("Cookie"); |
| 1607 | + |
| 1608 | + try { |
| 1609 | + const { performGetPageCollabUpdates } = await import("@deepnotes/session"); |
| 1610 | + const out = await performGetPageCollabUpdates({ |
| 1611 | + db, |
| 1612 | + env: sessionEnv, |
| 1613 | + accessCookie: readCookieHeader(cookieHeader, "accessToken"), |
| 1614 | + pageId: pParams.data.pageId, |
| 1615 | + }); |
| 1616 | + return c.json( |
| 1617 | + { |
| 1618 | + lastIndex: out.lastIndex, |
| 1619 | + updates: out.updates.map((u) => ({ |
| 1620 | + index: u.index, |
| 1621 | + encryptedData: u.encryptedData.toString("base64"), |
| 1622 | + })), |
| 1623 | + }, |
| 1624 | + 200, |
| 1625 | + ); |
| 1626 | + } catch (e) { |
| 1627 | + const { SessionError } = await import("@deepnotes/session"); |
| 1628 | + if (e instanceof SessionError) { |
| 1629 | + return c.json( |
| 1630 | + { code: e.code, message: e.message }, |
| 1631 | + e.status as ContentfulStatusCode, |
| 1632 | + ); |
| 1633 | + } |
| 1634 | + throw e; |
| 1635 | + } |
| 1636 | +}); |
| 1637 | + |
| 1638 | +app.post("/api/pages/:pageId/collab-updates", async (c) => { |
| 1639 | + const sessionEnv = getSessionEnv(c.env); |
| 1640 | + if (sessionEnv == null) { |
| 1641 | + return c.json(serviceUnavailableBody, 503); |
| 1642 | + } |
| 1643 | + const hyper = c.env.HYPERDRIVE; |
| 1644 | + if (hyper == null) { |
| 1645 | + return c.json( |
| 1646 | + { |
| 1647 | + code: "SERVICE_UNAVAILABLE" as const, |
| 1648 | + message: "HYPERDRIVE binding is not configured.", |
| 1649 | + }, |
| 1650 | + 503, |
| 1651 | + ); |
| 1652 | + } |
| 1653 | + |
| 1654 | + let bodyJson: unknown; |
| 1655 | + try { |
| 1656 | + bodyJson = await c.req.json(); |
| 1657 | + } catch { |
| 1658 | + return c.json({ code: "BAD_REQUEST", message: "Expected JSON body." }, 400); |
| 1659 | + } |
| 1660 | + |
| 1661 | + const pParams = pageIdPathSchema.safeParse({ pageId: c.req.param("pageId") }); |
| 1662 | + if (!pParams.success) { |
| 1663 | + return c.json( |
| 1664 | + { code: "VALIDATION_ERROR", message: pParams.error.message }, |
| 1665 | + 400, |
| 1666 | + ); |
| 1667 | + } |
| 1668 | + const parsed = pageCollabUpdatesAppendRequestSchema.safeParse(bodyJson); |
| 1669 | + if (!parsed.success) { |
| 1670 | + return c.json( |
| 1671 | + { |
| 1672 | + code: "VALIDATION_ERROR", |
| 1673 | + message: parsed.error.flatten().formErrors.join("; "), |
| 1674 | + }, |
| 1675 | + 400, |
| 1676 | + ); |
| 1677 | + } |
| 1678 | + |
| 1679 | + const db = getDbForConnectionString(hyper.connectionString); |
| 1680 | + const cookieHeader = c.req.header("Cookie"); |
| 1681 | + |
| 1682 | + try { |
| 1683 | + const { performAppendPageCollabUpdates } = await import("@deepnotes/session"); |
| 1684 | + await performAppendPageCollabUpdates({ |
| 1685 | + db, |
| 1686 | + env: sessionEnv, |
| 1687 | + accessCookie: readCookieHeader(cookieHeader, "accessToken"), |
| 1688 | + pageId: pParams.data.pageId, |
| 1689 | + expectedLastIndex: parsed.data.expectedLastIndex, |
| 1690 | + updates: parsed.data.updates, |
| 1691 | + }); |
| 1692 | + return c.body(null, 204); |
| 1693 | + } catch (e) { |
| 1694 | + const { SessionError } = await import("@deepnotes/session"); |
| 1695 | + if (e instanceof SessionError) { |
| 1696 | + return c.json( |
| 1697 | + { code: e.code, message: e.message }, |
| 1698 | + e.status as ContentfulStatusCode, |
| 1699 | + ); |
| 1700 | + } |
| 1701 | + throw e; |
| 1702 | + } |
| 1703 | +}); |
| 1704 | + |
1580 | 1705 | app.post("/api/pages/:pageId/backlinks", async (c) => { |
1581 | 1706 | const sessionEnv = getSessionEnv(c.env); |
1582 | 1707 | if (sessionEnv == null) { |
|
0 commit comments