mirror of
https://github.com/hex248/sprint.git
synced 2026-02-07 18:23:03 +00:00
Merge branch 'development'
This commit is contained in:
@@ -18,5 +18,4 @@ STRIPE_SECRET_KEY=your_stripe_secret_key
|
||||
RESEND_API_KEY=re_xxxxxxxxxxxxxxxx
|
||||
EMAIL_FROM=Sprint <support@sprintpm.org>
|
||||
|
||||
# password for demo accounts in seed data
|
||||
SEED_PASSWORD=change_me_in_production
|
||||
SEED_PASSWORD=replace-in-production
|
||||
|
||||
@@ -2,11 +2,11 @@ import { IssueCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createIssue,
|
||||
FREE_TIER_LIMITS,
|
||||
getOrganisationIssueCount,
|
||||
// FREE_TIER_LIMITS,
|
||||
// getOrganisationIssueCount,
|
||||
getOrganisationMemberRole,
|
||||
getProjectByID,
|
||||
getUserById,
|
||||
// getUserById,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
@@ -34,17 +34,17 @@ export default async function issueCreate(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// check free tier limit
|
||||
const user = await getUserById(req.userId);
|
||||
if (user && user.plan !== "pro") {
|
||||
const issueCount = await getOrganisationIssueCount(project.organisationId);
|
||||
if (issueCount >= FREE_TIER_LIMITS.issuesPerOrganisation) {
|
||||
return errorResponse(
|
||||
`free tier is limited to ${FREE_TIER_LIMITS.issuesPerOrganisation} issues per organisation. upgrade to pro for unlimited issues.`,
|
||||
"FREE_TIER_ISSUE_LIMIT",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// const user = await getUserById(req.userId);
|
||||
// if (user && user.plan !== "pro") {
|
||||
// const issueCount = await getOrganisationIssueCount(project.organisationId);
|
||||
// if (issueCount >= FREE_TIER_LIMITS.issuesPerOrganisation) {
|
||||
// return errorResponse(
|
||||
// `free tier is limited to ${FREE_TIER_LIMITS.issuesPerOrganisation} issues per organisation. upgrade to pro for unlimited issues.`,
|
||||
// "FREE_TIER_ISSUE_LIMIT",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const issue = await createIssue(
|
||||
project.id,
|
||||
|
||||
@@ -2,10 +2,10 @@ import { OrgAddMemberRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createOrganisationMember,
|
||||
FREE_TIER_LIMITS,
|
||||
// FREE_TIER_LIMITS,
|
||||
getOrganisationById,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationMembers,
|
||||
// getOrganisationMembers,
|
||||
getUserById,
|
||||
} from "../../db/queries";
|
||||
import { updateSeatCount } from "../../lib/seats";
|
||||
@@ -42,17 +42,17 @@ export default async function organisationAddMember(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// check free tier member limit
|
||||
const requester = await getUserById(req.userId);
|
||||
if (requester && requester.plan !== "pro") {
|
||||
const members = await getOrganisationMembers(organisationId);
|
||||
if (members.length >= FREE_TIER_LIMITS.membersPerOrganisation) {
|
||||
return errorResponse(
|
||||
`free tier is limited to ${FREE_TIER_LIMITS.membersPerOrganisation} members per organisation. upgrade to pro for unlimited members.`,
|
||||
"FREE_TIER_MEMBER_LIMIT",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// const requester = await getUserById(req.userId);
|
||||
// if (requester && requester.plan !== "pro") {
|
||||
// const members = await getOrganisationMembers(organisationId);
|
||||
// if (members.length >= FREE_TIER_LIMITS.membersPerOrganisation) {
|
||||
// return errorResponse(
|
||||
// `free tier is limited to ${FREE_TIER_LIMITS.membersPerOrganisation} members per organisation. upgrade to pro for unlimited members.`,
|
||||
// "FREE_TIER_MEMBER_LIMIT",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const member = await createOrganisationMember(organisationId, userId, role);
|
||||
|
||||
|
||||
@@ -2,10 +2,10 @@ import { OrgCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createOrganisationWithOwner,
|
||||
FREE_TIER_LIMITS,
|
||||
// FREE_TIER_LIMITS,
|
||||
getOrganisationBySlug,
|
||||
getUserById,
|
||||
getUserOrganisationCount,
|
||||
// getUserById,
|
||||
// getUserOrganisationCount,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
@@ -21,17 +21,17 @@ export default async function organisationCreate(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// check free tier limit
|
||||
const user = await getUserById(req.userId);
|
||||
if (user && user.plan !== "pro") {
|
||||
const orgCount = await getUserOrganisationCount(req.userId);
|
||||
if (orgCount >= FREE_TIER_LIMITS.organisationsPerUser) {
|
||||
return errorResponse(
|
||||
`free tier is limited to ${FREE_TIER_LIMITS.organisationsPerUser} organisation. upgrade to pro for unlimited organisations.`,
|
||||
"FREE_TIER_ORG_LIMIT",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// const user = await getUserById(req.userId);
|
||||
// if (user && user.plan !== "pro") {
|
||||
// const orgCount = await getUserOrganisationCount(req.userId);
|
||||
// if (orgCount >= FREE_TIER_LIMITS.organisationsPerUser) {
|
||||
// return errorResponse(
|
||||
// `free tier is limited to ${FREE_TIER_LIMITS.organisationsPerUser} organisation. upgrade to pro for unlimited organisations.`,
|
||||
// "FREE_TIER_ORG_LIMIT",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const organisation = await createOrganisationWithOwner(name, slug, req.userId, description);
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
getOrganisationById,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationMemberTimedSessions,
|
||||
getOrganisationOwner,
|
||||
getUserById,
|
||||
// getOrganisationOwner,
|
||||
// getUserById,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseQueryParams } from "../../validation";
|
||||
|
||||
@@ -40,9 +40,9 @@ export default async function organisationMemberTimeTracking(req: AuthedRequest)
|
||||
}
|
||||
|
||||
// check if organisation owner has pro subscription
|
||||
const owner = await getOrganisationOwner(organisationId);
|
||||
const ownerUser = owner ? await getUserById(owner.userId) : null;
|
||||
const isPro = ownerUser?.plan === "pro";
|
||||
// const owner = await getOrganisationOwner(organisationId);
|
||||
// const ownerUser = owner ? await getUserById(owner.userId) : null;
|
||||
// const isPro = ownerUser?.plan === "pro";
|
||||
|
||||
const sessions = await getOrganisationMemberTimedSessions(organisationId, fromDate);
|
||||
|
||||
@@ -57,12 +57,12 @@ export default async function organisationMemberTimeTracking(req: AuthedRequest)
|
||||
issueId: session.issueId,
|
||||
issueNumber: session.issueNumber,
|
||||
projectKey: session.projectKey,
|
||||
timestamps: isPro ? session.timestamps : [],
|
||||
endedAt: isPro ? session.endedAt : null,
|
||||
createdAt: isPro ? session.createdAt : null,
|
||||
workTimeMs: isPro ? actualWorkTimeMs : 0,
|
||||
breakTimeMs: isPro ? actualBreakTimeMs : 0,
|
||||
isRunning: isPro ? session.endedAt === null && isTimerRunning(timestamps) : false,
|
||||
timestamps: session.timestamps,
|
||||
endedAt: session.endedAt,
|
||||
createdAt: session.createdAt,
|
||||
workTimeMs: actualWorkTimeMs,
|
||||
breakTimeMs: actualBreakTimeMs,
|
||||
isRunning: session.endedAt === null && isTimerRunning(timestamps),
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ import { ProjectCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createProject,
|
||||
FREE_TIER_LIMITS,
|
||||
// FREE_TIER_LIMITS,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationProjectCount,
|
||||
// getOrganisationProjectCount,
|
||||
getProjectByKey,
|
||||
getUserById,
|
||||
} from "../../db/queries";
|
||||
@@ -30,18 +30,19 @@ export default async function projectCreate(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// check free tier limit
|
||||
const creator = await getUserById(req.userId);
|
||||
if (creator && creator.plan !== "pro") {
|
||||
const projectCount = await getOrganisationProjectCount(organisationId);
|
||||
if (projectCount >= FREE_TIER_LIMITS.projectsPerOrganisation) {
|
||||
return errorResponse(
|
||||
`free tier is limited to ${FREE_TIER_LIMITS.projectsPerOrganisation} project per organisation. upgrade to pro for unlimited projects.`,
|
||||
"FREE_TIER_PROJECT_LIMIT",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// const creator = await getUserById(req.userId);
|
||||
// if (creator && creator.plan !== "pro") {
|
||||
// const projectCount = await getOrganisationProjectCount(organisationId);
|
||||
// if (projectCount >= FREE_TIER_LIMITS.projectsPerOrganisation) {
|
||||
// return errorResponse(
|
||||
// `free tier is limited to ${FREE_TIER_LIMITS.projectsPerOrganisation} project per organisation. upgrade to pro for unlimited projects.`,
|
||||
// "FREE_TIER_PROJECT_LIMIT",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const creator = await getUserById(req.userId);
|
||||
if (!creator) {
|
||||
return errorResponse(`creator not found`, "CREATOR_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ import { SprintCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createSprint,
|
||||
FREE_TIER_LIMITS,
|
||||
// FREE_TIER_LIMITS,
|
||||
getOrganisationMemberRole,
|
||||
getProjectByID,
|
||||
getProjectSprintCount,
|
||||
getSubscriptionByUserId,
|
||||
// getProjectSprintCount,
|
||||
// getSubscriptionByUserId,
|
||||
hasOverlappingSprints,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
@@ -32,18 +32,18 @@ export default async function sprintCreate(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// check free tier sprint limit
|
||||
const subscription = await getSubscriptionByUserId(req.userId);
|
||||
const isPro = subscription?.status === "active";
|
||||
if (!isPro) {
|
||||
const sprintCount = await getProjectSprintCount(projectId);
|
||||
if (sprintCount >= FREE_TIER_LIMITS.sprintsPerProject) {
|
||||
return errorResponse(
|
||||
`Free tier limited to ${FREE_TIER_LIMITS.sprintsPerProject} sprints per project. Upgrade to Pro for unlimited sprints.`,
|
||||
"SPRINT_LIMIT_REACHED",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// const subscription = await getSubscriptionByUserId(req.userId);
|
||||
// const isPro = subscription?.status === "active";
|
||||
// if (!isPro) {
|
||||
// const sprintCount = await getProjectSprintCount(projectId);
|
||||
// if (sprintCount >= FREE_TIER_LIMITS.sprintsPerProject) {
|
||||
// return errorResponse(
|
||||
// `Free tier limited to ${FREE_TIER_LIMITS.sprintsPerProject} sprints per project. Upgrade to Pro for unlimited sprints.`,
|
||||
// "SPRINT_LIMIT_REACHED",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const start = new Date(startDate);
|
||||
const end = new Date(endDate);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { UserUpdateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { hashPassword } from "../../auth/utils";
|
||||
import { getSubscriptionByUserId, getUserById } from "../../db/queries";
|
||||
import { getUserById } from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function update(req: AuthedRequest) {
|
||||
@@ -24,17 +24,17 @@ export default async function update(req: AuthedRequest) {
|
||||
}
|
||||
|
||||
// block free users from changing icon preference
|
||||
if (iconPreference !== undefined && iconPreference !== user.iconPreference) {
|
||||
const subscription = await getSubscriptionByUserId(req.userId);
|
||||
const isPro = subscription?.status === "active";
|
||||
if (!isPro) {
|
||||
return errorResponse(
|
||||
"icon style customization is only available on Pro. Upgrade to customize your icon style.",
|
||||
"ICON_STYLE_PRO_ONLY",
|
||||
403,
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (iconPreference !== undefined && iconPreference !== user.iconPreference) {
|
||||
// const subscription = await getSubscriptionByUserId(req.userId);
|
||||
// const isPro = subscription?.status === "active";
|
||||
// if (!isPro) {
|
||||
// return errorResponse(
|
||||
// "icon style customization is only available on Pro. Upgrade to customize your icon style.",
|
||||
// "ICON_STYLE_PRO_ONLY",
|
||||
// 403,
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
let passwordHash: string | undefined;
|
||||
if (password !== undefined) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import sharp from "sharp";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { getSubscriptionByUserId } from "../../db/queries";
|
||||
// import { getSubscriptionByUserId } from "../../db/queries";
|
||||
import { s3Client, s3Endpoint, s3PublicUrl } from "../../s3";
|
||||
|
||||
const MAX_FILE_SIZE = 5 * 1024 * 1024;
|
||||
@@ -42,21 +42,21 @@ export default async function uploadAvatar(req: AuthedRequest) {
|
||||
const inputBuffer = Buffer.from(await file.arrayBuffer());
|
||||
|
||||
// check if user is pro
|
||||
const subscription = await getSubscriptionByUserId(req.userId);
|
||||
const isPro = subscription?.status === "active";
|
||||
// const subscription = await getSubscriptionByUserId(req.userId);
|
||||
// const isPro = subscription?.status === "active";
|
||||
|
||||
// block animated avatars for free users
|
||||
if (!isPro && file.type === "image/gif") {
|
||||
const animated = await isAnimatedGIF(inputBuffer);
|
||||
if (animated) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
error: "Animated avatars are only available on Pro. Upgrade to upload animated avatars.",
|
||||
}),
|
||||
{ status: 403, headers: { "Content-Type": "application/json" } },
|
||||
);
|
||||
}
|
||||
}
|
||||
// if (!isPro && file.type === "image/gif") {
|
||||
// const animated = await isAnimatedGIF(inputBuffer);
|
||||
// if (animated) {
|
||||
// return new Response(
|
||||
// JSON.stringify({
|
||||
// error: "Animated avatars are only available on Pro. Upgrade to upload animated avatars.",
|
||||
// }),
|
||||
// { status: 403, headers: { "Content-Type": "application/json" } },
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
|
||||
const isGIF = file.type === "image/gif";
|
||||
const outputExtension = isGIF ? "gif" : "png";
|
||||
|
||||
Reference in New Issue
Block a user