feat: share dashboard & reports, sankey report, new widgets
* fix: prompt card shadows on light mode * fix: handle past_due and unpaid from polar * wip * wip * wip 1 * fix: improve types for chart/reports * wip share
This commit is contained in:
committed by
GitHub
parent
39251c8598
commit
ed1c57dbb8
@@ -1,11 +1,23 @@
|
||||
import ShortUniqueId from 'short-unique-id';
|
||||
|
||||
import { db } from '@openpanel/db';
|
||||
import { zShareOverview } from '@openpanel/validation';
|
||||
import {
|
||||
db,
|
||||
getReportById,
|
||||
getReportsByDashboardId,
|
||||
getShareDashboardById,
|
||||
getShareReportById,
|
||||
transformReport,
|
||||
} from '@openpanel/db';
|
||||
import {
|
||||
zShareDashboard,
|
||||
zShareOverview,
|
||||
zShareReport,
|
||||
} from '@openpanel/validation';
|
||||
|
||||
import { hashPassword } from '@openpanel/auth';
|
||||
import { z } from 'zod';
|
||||
import { TRPCNotFoundError } from '../errors';
|
||||
import { getProjectAccess } from '../access';
|
||||
import { TRPCAccessError, TRPCNotFoundError } from '../errors';
|
||||
import { createTRPCRouter, protectedProcedure, publicProcedure } from '../trpc';
|
||||
|
||||
const uid = new ShortUniqueId({ length: 6 });
|
||||
@@ -85,4 +97,203 @@ export const shareRouter = createTRPCRouter({
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
// Dashboard sharing
|
||||
dashboard: publicProcedure
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
dashboardId: z.string(),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
shareId: z.string(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const share = await db.shareDashboard.findUnique({
|
||||
include: {
|
||||
organization: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
project: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
dashboard: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
where:
|
||||
'dashboardId' in input
|
||||
? {
|
||||
dashboardId: input.dashboardId,
|
||||
}
|
||||
: {
|
||||
id: input.shareId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!share) {
|
||||
if ('shareId' in input) {
|
||||
throw TRPCNotFoundError('Dashboard share not found');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...share,
|
||||
hasAccess: !!ctx.cookies[`shared-dashboard-${share?.id}`],
|
||||
};
|
||||
}),
|
||||
|
||||
createDashboard: protectedProcedure
|
||||
.input(zShareDashboard)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const access = await getProjectAccess({
|
||||
projectId: input.projectId,
|
||||
userId: ctx.session.userId,
|
||||
});
|
||||
|
||||
if (!access) {
|
||||
throw TRPCAccessError('You do not have access to this project');
|
||||
}
|
||||
|
||||
const passwordHash = input.password
|
||||
? await hashPassword(input.password)
|
||||
: null;
|
||||
|
||||
return db.shareDashboard.upsert({
|
||||
where: {
|
||||
dashboardId: input.dashboardId,
|
||||
},
|
||||
create: {
|
||||
id: uid.rnd(),
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
dashboardId: input.dashboardId,
|
||||
public: input.public,
|
||||
password: passwordHash,
|
||||
},
|
||||
update: {
|
||||
public: input.public,
|
||||
password: passwordHash,
|
||||
},
|
||||
});
|
||||
}),
|
||||
|
||||
dashboardReports: publicProcedure
|
||||
.input(
|
||||
z.object({
|
||||
shareId: z.string(),
|
||||
}),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const share = await getShareDashboardById(input.shareId);
|
||||
|
||||
if (!share || !share.public) {
|
||||
throw TRPCNotFoundError('Dashboard share not found');
|
||||
}
|
||||
|
||||
// Check password access
|
||||
const hasAccess = !!ctx.cookies[`shared-dashboard-${share.id}`];
|
||||
if (share.password && !hasAccess) {
|
||||
throw TRPCAccessError('Password required');
|
||||
}
|
||||
|
||||
return getReportsByDashboardId(share.dashboardId);
|
||||
}),
|
||||
|
||||
// Report sharing
|
||||
report: publicProcedure
|
||||
.input(
|
||||
z
|
||||
.object({
|
||||
reportId: z.string(),
|
||||
})
|
||||
.or(
|
||||
z.object({
|
||||
shareId: z.string(),
|
||||
}),
|
||||
),
|
||||
)
|
||||
.query(async ({ input, ctx }) => {
|
||||
const share = await db.shareReport.findUnique({
|
||||
include: {
|
||||
organization: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
project: {
|
||||
select: {
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
report: true,
|
||||
},
|
||||
where:
|
||||
'reportId' in input
|
||||
? {
|
||||
reportId: input.reportId,
|
||||
}
|
||||
: {
|
||||
id: input.shareId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!share) {
|
||||
if ('shareId' in input) {
|
||||
throw TRPCNotFoundError('Report share not found');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...share,
|
||||
hasAccess: !!ctx.cookies[`shared-report-${share?.id}`],
|
||||
report: transformReport(share.report),
|
||||
};
|
||||
}),
|
||||
|
||||
createReport: protectedProcedure
|
||||
.input(zShareReport)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const access = await getProjectAccess({
|
||||
projectId: input.projectId,
|
||||
userId: ctx.session.userId,
|
||||
});
|
||||
|
||||
if (!access) {
|
||||
throw TRPCAccessError('You do not have access to this project');
|
||||
}
|
||||
|
||||
const passwordHash = input.password
|
||||
? await hashPassword(input.password)
|
||||
: null;
|
||||
|
||||
return db.shareReport.upsert({
|
||||
where: {
|
||||
reportId: input.reportId,
|
||||
},
|
||||
create: {
|
||||
id: uid.rnd(),
|
||||
organizationId: input.organizationId,
|
||||
projectId: input.projectId,
|
||||
reportId: input.reportId,
|
||||
public: input.public,
|
||||
password: passwordHash,
|
||||
},
|
||||
update: {
|
||||
public: input.public,
|
||||
password: passwordHash,
|
||||
},
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user