mirror of
https://github.com/hex248/sprint.git
synced 2026-02-08 02:33:01 +00:00
Free/Pro plan limitations
This commit is contained in:
@@ -7,3 +7,11 @@ export * from "./sprints";
|
||||
export * from "./subscriptions";
|
||||
export * from "./timed-sessions";
|
||||
export * from "./users";
|
||||
|
||||
// free tier limits
|
||||
export const FREE_TIER_LIMITS = {
|
||||
organisationsPerUser: 1,
|
||||
projectsPerOrganisation: 1,
|
||||
issuesPerOrganisation: 100,
|
||||
membersPerOrganisation: 5,
|
||||
} as const;
|
||||
|
||||
@@ -259,6 +259,25 @@ export async function getIssueAssigneeCount(issueId: number): Promise<number> {
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
export async function getOrganisationIssueCount(organisationId: number): Promise<number> {
|
||||
const { Project } = await import("@sprint/shared");
|
||||
|
||||
const projects = await db
|
||||
.select({ id: Project.id })
|
||||
.from(Project)
|
||||
.where(eq(Project.organisationId, organisationId));
|
||||
const projectIds = projects.map((p) => p.id);
|
||||
|
||||
if (projectIds.length === 0) return 0;
|
||||
|
||||
const [result] = await db
|
||||
.select({ count: sql<number>`COUNT(*)` })
|
||||
.from(Issue)
|
||||
.where(inArray(Issue.projectId, projectIds));
|
||||
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
export async function isIssueAssignee(issueId: number, userId: number): Promise<boolean> {
|
||||
const [assignee] = await db
|
||||
.select({ id: IssueAssignee.id })
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Organisation, OrganisationMember, User } from "@sprint/shared";
|
||||
import { and, eq } from "drizzle-orm";
|
||||
import { and, eq, sql } from "drizzle-orm";
|
||||
import { db } from "../client";
|
||||
|
||||
export async function createOrganisation(name: string, slug: string, description?: string) {
|
||||
@@ -144,3 +144,11 @@ export async function updateOrganisationMemberRole(organisationId: number, userI
|
||||
.returning();
|
||||
return member;
|
||||
}
|
||||
|
||||
export async function getUserOrganisationCount(userId: number): Promise<number> {
|
||||
const [result] = await db
|
||||
.select({ count: sql<number>`COUNT(*)` })
|
||||
.from(OrganisationMember)
|
||||
.where(eq(OrganisationMember.userId, userId));
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Issue, Organisation, Project, Sprint, User } from "@sprint/shared";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import { db } from "../client";
|
||||
|
||||
export async function createProject(key: string, name: string, creatorId: number, organisationId: number) {
|
||||
@@ -82,3 +82,11 @@ export async function getProjectsByOrganisationId(organisationId: number) {
|
||||
.leftJoin(Organisation, eq(Project.organisationId, Organisation.id));
|
||||
return projects;
|
||||
}
|
||||
|
||||
export async function getOrganisationProjectCount(organisationId: number): Promise<number> {
|
||||
const [result] = await db
|
||||
.select({ count: sql<number>`COUNT(*)` })
|
||||
.from(Project)
|
||||
.where(eq(Project.organisationId, organisationId));
|
||||
return result?.count ?? 0;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { IssueCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createIssue, getOrganisationMemberRole, getProjectByID } from "../../db/queries";
|
||||
import {
|
||||
createIssue,
|
||||
FREE_TIER_LIMITS,
|
||||
getOrganisationIssueCount,
|
||||
getOrganisationMemberRole,
|
||||
getProjectByID,
|
||||
getUserById,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function issueCreate(req: AuthedRequest) {
|
||||
@@ -26,6 +33,19 @@ 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 issue = await createIssue(
|
||||
project.id,
|
||||
title,
|
||||
|
||||
@@ -2,8 +2,10 @@ import { OrgAddMemberRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import {
|
||||
createOrganisationMember,
|
||||
FREE_TIER_LIMITS,
|
||||
getOrganisationById,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationMembers,
|
||||
getUserById,
|
||||
} from "../../db/queries";
|
||||
import { updateSeatCount } from "../../lib/seats";
|
||||
@@ -39,6 +41,19 @@ export default async function organisationAddMember(req: AuthedRequest) {
|
||||
return errorResponse("only owners and admins can add members", "PERMISSION_DENIED", 403);
|
||||
}
|
||||
|
||||
// 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 member = await createOrganisationMember(organisationId, userId, role);
|
||||
|
||||
// update seat count if the requester is the owner
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
import { OrgCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createOrganisationWithOwner, getOrganisationBySlug } from "../../db/queries";
|
||||
import {
|
||||
createOrganisationWithOwner,
|
||||
FREE_TIER_LIMITS,
|
||||
getOrganisationBySlug,
|
||||
getUserById,
|
||||
getUserOrganisationCount,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function organisationCreate(req: AuthedRequest) {
|
||||
@@ -14,6 +20,19 @@ export default async function organisationCreate(req: AuthedRequest) {
|
||||
return errorResponse(`organisation with slug "${slug}" already exists`, "SLUG_TAKEN", 409);
|
||||
}
|
||||
|
||||
// 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 organisation = await createOrganisationWithOwner(name, slug, req.userId, description);
|
||||
|
||||
return Response.json(organisation);
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
import { ProjectCreateRequestSchema } from "@sprint/shared";
|
||||
import type { AuthedRequest } from "../../auth/middleware";
|
||||
import { createProject, getOrganisationMemberRole, getProjectByKey, getUserById } from "../../db/queries";
|
||||
import {
|
||||
createProject,
|
||||
FREE_TIER_LIMITS,
|
||||
getOrganisationMemberRole,
|
||||
getOrganisationProjectCount,
|
||||
getProjectByKey,
|
||||
getUserById,
|
||||
} from "../../db/queries";
|
||||
import { errorResponse, parseJsonBody } from "../../validation";
|
||||
|
||||
export default async function projectCreate(req: AuthedRequest) {
|
||||
@@ -22,7 +29,19 @@ export default async function projectCreate(req: AuthedRequest) {
|
||||
return errorResponse("only owners and admins can create projects", "PERMISSION_DENIED", 403);
|
||||
}
|
||||
|
||||
// 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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!creator) {
|
||||
return errorResponse(`creator not found`, "CREATOR_NOT_FOUND", 404);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user