org export as json

This commit is contained in:
2026-01-30 00:30:50 +00:00
parent abc3568800
commit f469d43c8e
7 changed files with 168 additions and 0 deletions

View File

@@ -0,0 +1,77 @@
import {
Issue,
IssueAssignee,
IssueComment,
Organisation,
OrganisationMember,
Project,
Sprint,
TimedSession,
} from "@sprint/shared";
import { eq, inArray } from "drizzle-orm";
import { db } from "../client";
export async function exportOrganisation(organisationId: number) {
const organisation = await db
.select()
.from(Organisation)
.where(eq(Organisation.id, organisationId))
.limit(1);
if (!organisation[0]) return null;
const orgData = organisation[0];
// get members
const members = await db
.select()
.from(OrganisationMember)
.where(eq(OrganisationMember.organisationId, organisationId));
// get projects
const projects = await db.select().from(Project).where(eq(Project.organisationId, organisationId));
const projectIds = projects.map((p) => p.id);
// get sprints
const sprints =
projectIds.length > 0
? await db.select().from(Sprint).where(inArray(Sprint.projectId, projectIds))
: [];
// get issues
const issues =
projectIds.length > 0
? await db.select().from(Issue).where(inArray(Issue.projectId, projectIds))
: [];
const issueIds = issues.map((i) => i.id);
// get issue assignees
const issueAssignees =
issueIds.length > 0
? await db.select().from(IssueAssignee).where(inArray(IssueAssignee.issueId, issueIds))
: [];
// get issue comments
const issueComments =
issueIds.length > 0
? await db.select().from(IssueComment).where(inArray(IssueComment.issueId, issueIds))
: [];
// get timed sessions - limited to issues in this org
const timedSessions =
issueIds.length > 0
? await db.select().from(TimedSession).where(inArray(TimedSession.issueId, issueIds))
: [];
return {
organisation: orgData,
members,
projects,
sprints,
issues,
issueAssignees,
issueComments,
timedSessions,
};
}

View File

@@ -1,4 +1,5 @@
export * from "./email-verification";
export * from "./export";
export * from "./issue-comments";
export * from "./issues";
export * from "./organisations";

View File

@@ -65,6 +65,7 @@ const main = async () => {
"/organisation/create": withGlobalAuthed(withAuth(withCSRF(routes.organisationCreate))),
"/organisation/by-id": withGlobalAuthed(withAuth(routes.organisationById)),
"/organisation/export": withGlobalAuthed(withAuth(routes.organisationExport)),
"/organisation/update": withGlobalAuthed(withAuth(withCSRF(routes.organisationUpdate))),
"/organisation/delete": withGlobalAuthed(withAuth(withCSRF(routes.organisationDelete))),
"/organisation/upload-icon": withGlobalAuthed(withAuth(withCSRF(routes.organisationUploadIcon))),

View File

@@ -22,6 +22,7 @@ import organisationById from "./organisation/by-id";
import organisationsByUser from "./organisation/by-user";
import organisationCreate from "./organisation/create";
import organisationDelete from "./organisation/delete";
import organisationExport from "./organisation/export";
import organisationMemberTimeTracking from "./organisation/member-time-tracking";
import organisationMembers from "./organisation/members";
import organisationRemoveMember from "./organisation/remove-member";
@@ -84,6 +85,7 @@ export const routes = {
organisationCreate,
organisationById,
organisationExport,
organisationUpdate,
organisationDelete,
organisationAddMember,

View File

@@ -0,0 +1,43 @@
import { OrgByIdQuerySchema } from "@sprint/shared";
import type { AuthedRequest } from "../../auth/middleware";
import { exportOrganisation } from "../../db/queries/export";
import { getOrganisationById, getOrganisationMemberRole } from "../../db/queries/organisations";
import { errorResponse, parseQueryParams } from "../../validation";
export default async function organisationExport(req: AuthedRequest) {
const url = new URL(req.url);
const parsed = parseQueryParams(url, OrgByIdQuerySchema);
if ("error" in parsed) return parsed.error;
const { id } = parsed.data;
const { userId } = req;
// check if organisation exists
const organisation = await getOrganisationById(id);
if (!organisation) {
return errorResponse(`organisation with id ${id} not found`, "ORG_NOT_FOUND", 404);
}
// check if user is admin or owner
const memberRole = await getOrganisationMemberRole(id, userId);
if (!memberRole || (memberRole.role !== "owner" && memberRole.role !== "admin")) {
return errorResponse("only organisation admins and owners can export data", "FORBIDDEN", 403);
}
const exportData = await exportOrganisation(id);
if (!exportData) {
return errorResponse("failed to export organisation data", "EXPORT_FAILED", 500);
}
// add metadata to export
const exportWithMetadata = {
...exportData,
_metadata: {
exportedAt: new Date().toISOString(),
exportedBy: userId,
version: "1.0",
},
};
return Response.json(exportWithMetadata);
}