Sign In
now.
@@ -70,7 +62,7 @@ const errorCodeMessages: {
message: () => (
Admin user does not exist.
-
+
Sign In
now.
@@ -106,53 +98,3 @@ export const authErrorHandler = (errorCode: EAdminAuthErrorCodes, email?: string
return undefined;
};
-
-export const getBaseAuthenticationModes: (props: TGetBaseAuthenticationModeProps) => TInstanceAuthenticationModes[] = ({
- disabled,
- updateConfig,
- resolvedTheme,
-}) => [
- {
- key: "unique-codes",
- name: "Unique codes",
- description:
- "Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
- icon:
,
- config:
,
- },
- {
- key: "passwords-login",
- name: "Passwords",
- description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
- icon:
,
- config:
,
- },
- {
- key: "google",
- name: "Google",
- description: "Allow members to log in or sign up for Plane with their Google accounts.",
- icon:
,
- config:
,
- },
- {
- key: "github",
- name: "GitHub",
- description: "Allow members to log in or sign up for Plane with their GitHub accounts.",
- icon: (
-
- ),
- config:
,
- },
- {
- key: "gitlab",
- name: "GitLab",
- description: "Allow members to log in or sign up to plane with their GitLab accounts.",
- icon:
,
- config:
,
- },
-];
diff --git a/apps/admin/app/(all)/(home)/layout.tsx b/apps/admin/app/(all)/(home)/layout.tsx
index 26c0e2b26e..a334536840 100644
--- a/apps/admin/app/(all)/(home)/layout.tsx
+++ b/apps/admin/app/(all)/(home)/layout.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/navigation";
@@ -16,7 +22,7 @@ function RootLayout() {
}, [replace, isUserLoggedIn]);
return (
-
+
);
diff --git a/apps/admin/app/(all)/(home)/page.tsx b/apps/admin/app/(all)/(home)/page.tsx
index 12f701c462..7947adcdcc 100644
--- a/apps/admin/app/(all)/(home)/page.tsx
+++ b/apps/admin/app/(all)/(home)/page.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
// components
import { LogoSpinner } from "@/components/common/logo-spinner";
@@ -16,7 +22,7 @@ function HomePage() {
// if instance is not fetched, show loading
if (!instance && !error) {
return (
-
+
);
diff --git a/apps/admin/app/(all)/(home)/sign-in-form.tsx b/apps/admin/app/(all)/(home)/sign-in-form.tsx
index cd40a4c77e..4e0afb8ea1 100644
--- a/apps/admin/app/(all)/(home)/sign-in-form.tsx
+++ b/apps/admin/app/(all)/(home)/sign-in-form.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
import { Eye, EyeOff } from "lucide-react";
@@ -10,7 +16,7 @@ import { Input, Spinner } from "@plane/ui";
// components
import { Banner } from "@/components/common/banner";
// local components
-import { FormHeader } from "../../../core/components/instance/form-header";
+import { FormHeader } from "@/components/instance/form-header";
import { AuthBanner } from "./auth-banner";
import { AuthHeader } from "./auth-header";
import { authErrorHandler } from "./auth-helpers";
@@ -105,8 +111,8 @@ export function InstanceSignInForm() {
return (
<>
-
-
+
+
-
+
Email *
handleFormChange("email", e.target.value)}
- autoComplete="on"
+ autoComplete="off"
autoFocus
/>
-
+
Password *
@@ -159,12 +165,12 @@ export function InstanceSignInForm() {
placeholder="Enter your password"
value={formData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
- autoComplete="on"
+ autoComplete="off"
/>
{showPassword ? (
setShowPassword(false)}
>
@@ -172,7 +178,7 @@ export function InstanceSignInForm() {
) : (
setShowPassword(true)}
>
diff --git a/apps/admin/app/compat/next/helper.ts b/apps/admin/app/compat/next/helper.ts
index c046998702..c4edf3d545 100644
--- a/apps/admin/app/compat/next/helper.ts
+++ b/apps/admin/app/compat/next/helper.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
/**
* Ensures that a URL has a trailing slash while preserving query parameters and fragments
* @param url - The URL to process
diff --git a/apps/admin/app/compat/next/image.tsx b/apps/admin/app/compat/next/image.tsx
index 062638de41..12a2bb21ed 100644
--- a/apps/admin/app/compat/next/image.tsx
+++ b/apps/admin/app/compat/next/image.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React from "react";
// Minimal shim so code using next/image compiles under React Router + Vite
diff --git a/apps/admin/app/compat/next/link.tsx b/apps/admin/app/compat/next/link.tsx
index b0bca4faf5..85177560fc 100644
--- a/apps/admin/app/compat/next/link.tsx
+++ b/apps/admin/app/compat/next/link.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React from "react";
import { Link as RRLink } from "react-router";
import { ensureTrailingSlash } from "./helper";
diff --git a/apps/admin/app/compat/next/navigation.ts b/apps/admin/app/compat/next/navigation.ts
index e0e6e90259..dc59a9a85c 100644
--- a/apps/admin/app/compat/next/navigation.ts
+++ b/apps/admin/app/compat/next/navigation.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useMemo } from "react";
import { useLocation, useNavigate, useSearchParams as useSearchParamsRR } from "react-router";
import { ensureTrailingSlash } from "./helper";
diff --git a/apps/admin/app/components/404.tsx b/apps/admin/app/components/404.tsx
index a4954ebaaf..473dbb6e17 100644
--- a/apps/admin/app/components/404.tsx
+++ b/apps/admin/app/components/404.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React from "react";
import { Link } from "react-router";
// ui
diff --git a/apps/admin/app/entry.client.tsx b/apps/admin/app/entry.client.tsx
index 9cf1c32deb..9c665ede07 100644
--- a/apps/admin/app/entry.client.tsx
+++ b/apps/admin/app/entry.client.tsx
@@ -1,28 +1,13 @@
-import * as Sentry from "@sentry/react-router";
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";
-Sentry.init({
- dsn: process.env.VITE_SENTRY_DSN,
- environment: process.env.VITE_SENTRY_ENVIRONMENT,
- sendDefaultPii: process.env.VITE_SENTRY_SEND_DEFAULT_PII ? process.env.VITE_SENTRY_SEND_DEFAULT_PII === "1" : false,
- release: process.env.VITE_APP_VERSION,
- tracesSampleRate: process.env.VITE_SENTRY_TRACES_SAMPLE_RATE
- ? parseFloat(process.env.VITE_SENTRY_TRACES_SAMPLE_RATE)
- : 0.1,
- profilesSampleRate: process.env.VITE_SENTRY_PROFILES_SAMPLE_RATE
- ? parseFloat(process.env.VITE_SENTRY_PROFILES_SAMPLE_RATE)
- : 0.1,
- replaysSessionSampleRate: process.env.VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE
- ? parseFloat(process.env.VITE_SENTRY_REPLAYS_SESSION_SAMPLE_RATE)
- : 0.1,
- replaysOnErrorSampleRate: process.env.VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE
- ? parseFloat(process.env.VITE_SENTRY_REPLAYS_ON_ERROR_SAMPLE_RATE)
- : 1.0,
- integrations: [],
-});
-
startTransition(() => {
hydrateRoot(
document,
diff --git a/apps/admin/app/root.tsx b/apps/admin/app/root.tsx
index a2a5c216be..1bfcfa37d8 100644
--- a/apps/admin/app/root.tsx
+++ b/apps/admin/app/root.tsx
@@ -1,7 +1,12 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import type { ReactNode } from "react";
import { Links, Meta, Outlet, Scripts } from "react-router";
import type { LinksFunction } from "react-router";
-import * as Sentry from "@sentry/react-router";
import appleTouchIcon from "@/app/assets/favicon/apple-touch-icon.png?url";
import favicon16 from "@/app/assets/favicon/favicon-16x16.png?url";
import favicon32 from "@/app/assets/favicon/favicon-32x32.png?url";
@@ -69,7 +74,7 @@ export const meta: Route.MetaFunction = () => [
export default function Root() {
return (
-
+
);
@@ -84,10 +89,6 @@ export function HydrateFallback() {
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
- if (error) {
- Sentry.captureException(error);
- }
-
return (
Something went wrong.
diff --git a/apps/admin/app/routes.ts b/apps/admin/app/routes.ts
index 0f7232439f..184bed205a 100644
--- a/apps/admin/app/routes.ts
+++ b/apps/admin/app/routes.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { index, layout, route } from "@react-router/dev/routes";
import type { RouteConfig } from "@react-router/dev/routes";
diff --git a/apps/admin/ce/store/root.store.ts b/apps/admin/ce/store/root.store.ts
deleted file mode 100644
index 1be816f70a..0000000000
--- a/apps/admin/ce/store/root.store.ts
+++ /dev/null
@@ -1,19 +0,0 @@
-import { enableStaticRendering } from "mobx-react";
-// stores
-import { CoreRootStore } from "@/store/root.store";
-
-enableStaticRendering(typeof window === "undefined");
-
-export class RootStore extends CoreRootStore {
- constructor() {
- super();
- }
-
- hydrate(initialData: any) {
- super.hydrate(initialData);
- }
-
- resetOnSignOut() {
- super.resetOnSignOut();
- }
-}
diff --git a/apps/admin/core/components/authentication/authentication-method-card.tsx b/apps/admin/components/authentication/authentication-method-card.tsx
similarity index 74%
rename from apps/admin/core/components/authentication/authentication-method-card.tsx
rename to apps/admin/components/authentication/authentication-method-card.tsx
index b420d13fb0..86934bbe75 100644
--- a/apps/admin/core/components/authentication/authentication-method-card.tsx
+++ b/apps/admin/components/authentication/authentication-method-card.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
// helpers
import { cn } from "@plane/utils";
@@ -16,8 +22,8 @@ export function AuthenticationMethodCard(props: Props) {
return (
{type === "error" ? (
-
+
) : (
diff --git a/apps/admin/core/components/common/breadcrumb-link.tsx b/apps/admin/components/common/breadcrumb-link.tsx
similarity index 82%
rename from apps/admin/core/components/common/breadcrumb-link.tsx
rename to apps/admin/components/common/breadcrumb-link.tsx
index 0bed7e6064..46d4fd1da0 100644
--- a/apps/admin/core/components/common/breadcrumb-link.tsx
+++ b/apps/admin/components/common/breadcrumb-link.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import Link from "next/link";
import { Tooltip } from "@plane/propel/tooltip";
@@ -16,12 +22,12 @@ export function BreadcrumbLink(props: Props) {
{href ? (
{icon && {icon}
}
- {label}
+ {label}
) : (
{icon &&
{icon}
}
-
{label}
+
{label}
)}
diff --git a/apps/admin/core/components/common/code-block.tsx b/apps/admin/components/common/code-block.tsx
similarity index 51%
rename from apps/admin/core/components/common/code-block.tsx
rename to apps/admin/components/common/code-block.tsx
index 334d8d54ce..02c44be22e 100644
--- a/apps/admin/core/components/common/code-block.tsx
+++ b/apps/admin/components/common/code-block.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { cn } from "@plane/utils";
type TProps = {
@@ -10,9 +16,9 @@ export function CodeBlock({ children, className, darkerShade }: TProps) {
return (
-
+
-
+
You have unsaved changes
@@ -54,7 +60,7 @@ export function ConfirmDiscardModal(props: Props) {
-
+
Keep editing
diff --git a/apps/admin/core/components/common/controller-input.tsx b/apps/admin/components/common/controller-input.tsx
similarity index 89%
rename from apps/admin/core/components/common/controller-input.tsx
rename to apps/admin/components/common/controller-input.tsx
index 9dc38d851a..3d95b4484e 100644
--- a/apps/admin/core/components/common/controller-input.tsx
+++ b/apps/admin/components/common/controller-input.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React, { useState } from "react";
import type { Control } from "react-hook-form";
import { Controller } from "react-hook-form";
@@ -61,7 +67,7 @@ export function ControllerInput(props: Props) {
(showPassword ? (
setShowPassword(false)}
>
@@ -69,7 +75,7 @@ export function ControllerInput(props: Props) {
) : (
setShowPassword(true)}
>
diff --git a/apps/admin/core/components/common/controller-switch.tsx b/apps/admin/components/common/controller-switch.tsx
similarity index 89%
rename from apps/admin/core/components/common/controller-switch.tsx
rename to apps/admin/components/common/controller-switch.tsx
index f20d6cafe3..58681949d2 100644
--- a/apps/admin/core/components/common/controller-switch.tsx
+++ b/apps/admin/components/common/controller-switch.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import type { Control, FieldPath, FieldValues } from "react-hook-form";
import { Controller } from "react-hook-form";
// plane internal packages
diff --git a/apps/admin/core/components/common/copy-field.tsx b/apps/admin/components/common/copy-field.tsx
similarity index 88%
rename from apps/admin/core/components/common/copy-field.tsx
rename to apps/admin/components/common/copy-field.tsx
index 981a0476b4..d161740df8 100644
--- a/apps/admin/core/components/common/copy-field.tsx
+++ b/apps/admin/components/common/copy-field.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React from "react";
// ui
import { Button } from "@plane/propel/button";
diff --git a/apps/admin/core/components/common/empty-state.tsx b/apps/admin/components/common/empty-state.tsx
similarity index 84%
rename from apps/admin/core/components/common/empty-state.tsx
rename to apps/admin/components/common/empty-state.tsx
index 861ff85882..0f33f13cc7 100644
--- a/apps/admin/core/components/common/empty-state.tsx
+++ b/apps/admin/components/common/empty-state.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import React from "react";
import { Button } from "@plane/propel/button";
@@ -19,7 +25,7 @@ export function EmptyState({ title, description, image, primaryButton, secondary
{image &&
}
-
{title}
+
{title}
{description &&
{description}
}
{primaryButton && (
diff --git a/apps/admin/core/components/common/header/core.ts b/apps/admin/components/common/header/core.ts
similarity index 67%
rename from apps/admin/core/components/common/header/core.ts
rename to apps/admin/components/common/header/core.ts
index db77b83fa1..4cfc8ee718 100644
--- a/apps/admin/core/components/common/header/core.ts
+++ b/apps/admin/components/common/header/core.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
export const CORE_HEADER_SEGMENT_LABELS: Record
= {
general: "General",
ai: "Artificial Intelligence",
diff --git a/apps/admin/components/common/header/extended.ts b/apps/admin/components/common/header/extended.ts
new file mode 100644
index 0000000000..b4c3e66d37
--- /dev/null
+++ b/apps/admin/components/common/header/extended.ts
@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export const EXTENDED_HEADER_SEGMENT_LABELS: Record = {};
diff --git a/apps/admin/core/components/common/header/index.tsx b/apps/admin/components/common/header/index.tsx
similarity index 84%
rename from apps/admin/core/components/common/header/index.tsx
rename to apps/admin/components/common/header/index.tsx
index 65d6f5703b..48b9ca78e9 100644
--- a/apps/admin/core/components/common/header/index.tsx
+++ b/apps/admin/components/common/header/index.tsx
@@ -1,10 +1,16 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
import { usePathname } from "next/navigation";
import { Menu, Settings } from "lucide-react";
// icons
import { Breadcrumbs } from "@plane/ui";
// components
-import { BreadcrumbLink } from "@/components/common/breadcrumb-link";
+import { BreadcrumbLink } from "../breadcrumb-link";
// hooks
import { useTheme } from "@/hooks/store";
// local imports
@@ -15,10 +21,10 @@ export const HamburgerToggle = observer(function HamburgerToggle() {
const { isSidebarCollapsed, toggleSidebar } = useTheme();
return (
toggleSidebar(!isSidebarCollapsed)}
>
-
+
);
});
diff --git a/apps/admin/core/components/common/logo-spinner.tsx b/apps/admin/components/common/logo-spinner.tsx
similarity index 76%
rename from apps/admin/core/components/common/logo-spinner.tsx
rename to apps/admin/components/common/logo-spinner.tsx
index 4d06d28224..b74c38d707 100644
--- a/apps/admin/core/components/common/logo-spinner.tsx
+++ b/apps/admin/components/common/logo-spinner.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useTheme } from "next-themes";
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
import LogoSpinnerLight from "@/app/assets/images/logo-spinner-light.gif?url";
diff --git a/apps/admin/core/components/new-user-popup.tsx b/apps/admin/components/common/new-user-popup.tsx
similarity index 83%
rename from apps/admin/core/components/new-user-popup.tsx
rename to apps/admin/components/common/new-user-popup.tsx
index a20cb147fc..30dda8d2dc 100644
--- a/apps/admin/core/components/new-user-popup.tsx
+++ b/apps/admin/components/common/new-user-popup.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
import Link from "next/link";
import { useTheme as useNextTheme } from "next-themes";
@@ -18,7 +24,7 @@ export const NewUserPopup = observer(function NewUserPopup() {
if (!isNewUserPopup) return <>>;
return (
-
+
Create workspace
@@ -35,7 +41,7 @@ export const NewUserPopup = observer(function NewUserPopup() {
-
+
{
return (
{customHeader ? (
-
{customHeader}
+
{customHeader}
) : (
header && (
-
+
-
{header.title}
-
{header.description}
+
{header.title}
+
{header.description}
{header.actions &&
{header.actions}
}
)
)}
-
diff --git a/apps/admin/core/components/instance/failure.tsx b/apps/admin/components/instance/failure.tsx
similarity index 65%
rename from apps/admin/core/components/instance/failure.tsx
rename to apps/admin/components/instance/failure.tsx
index 30d9f09dfb..1f1610fbe1 100644
--- a/apps/admin/core/components/instance/failure.tsx
+++ b/apps/admin/components/instance/failure.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
import { useTheme } from "next-themes";
import { Button } from "@plane/propel/button";
@@ -18,12 +24,12 @@ export const InstanceFailureView = observer(function InstanceFailureView() {
return (
<>
-
-
-
+
+
+
-
Unable to fetch instance details.
-
+
Unable to fetch instance details.
+
We were unable to fetch the details of the instance. Fret not, it might just be a connectivity issue.
diff --git a/apps/admin/components/instance/form-header.tsx b/apps/admin/components/instance/form-header.tsx
new file mode 100644
index 0000000000..652b7d3047
--- /dev/null
+++ b/apps/admin/components/instance/form-header.tsx
@@ -0,0 +1,14 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export function FormHeader({ heading, subHeading }: { heading: string; subHeading: string }) {
+ return (
+
+ {heading}
+ {subHeading}
+
+ );
+}
diff --git a/apps/admin/core/components/instance/instance-not-ready.tsx b/apps/admin/components/instance/instance-not-ready.tsx
similarity index 52%
rename from apps/admin/core/components/instance/instance-not-ready.tsx
rename to apps/admin/components/instance/instance-not-ready.tsx
index 7092ae048c..4fa5e06705 100644
--- a/apps/admin/core/components/instance/instance-not-ready.tsx
+++ b/apps/admin/components/instance/instance-not-ready.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import Link from "next/link";
import { Button } from "@plane/propel/button";
// assets
@@ -5,12 +11,12 @@ import PlaneTakeOffImage from "@/app/assets/images/plane-takeoff.png?url";
export function InstanceNotReady() {
return (
-
-
-
-
Welcome aboard Plane!
+
+
+
+
Welcome aboard Plane!
-
Get started by setting up your instance and workspace
+
Get started by setting up your instance and workspace
diff --git a/apps/admin/core/components/instance/loading.tsx b/apps/admin/components/instance/loading.tsx
similarity index 76%
rename from apps/admin/core/components/instance/loading.tsx
rename to apps/admin/components/instance/loading.tsx
index 2b45deff28..293b44bdcc 100644
--- a/apps/admin/core/components/instance/loading.tsx
+++ b/apps/admin/components/instance/loading.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useTheme } from "next-themes";
// assets
import LogoSpinnerDark from "@/app/assets/images/logo-spinner-dark.gif?url";
diff --git a/apps/admin/core/components/instance/setup-form.tsx b/apps/admin/components/instance/setup-form.tsx
similarity index 82%
rename from apps/admin/core/components/instance/setup-form.tsx
rename to apps/admin/components/instance/setup-form.tsx
index f463dfc593..74e80db45b 100644
--- a/apps/admin/core/components/instance/setup-form.tsx
+++ b/apps/admin/components/instance/setup-form.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useEffect, useMemo, useState } from "react";
import { useSearchParams } from "next/navigation";
// icons
@@ -7,11 +13,11 @@ import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
import { Button } from "@plane/propel/button";
import { AuthService } from "@plane/services";
import { Checkbox, Input, PasswordStrengthIndicator, Spinner } from "@plane/ui";
-import { getPasswordStrength } from "@plane/utils";
+import { getPasswordStrength, validatePersonName, validateCompanyName } from "@plane/utils";
// components
import { AuthHeader } from "@/app/(all)/(home)/auth-header";
-import { Banner } from "@/components/common/banner";
-import { FormHeader } from "@/components/instance/form-header";
+import { Banner } from "../common/banner";
+import { FormHeader } from "./form-header";
// service initialization
const authService = new AuthService();
@@ -133,8 +139,8 @@ export function InstanceSetupForm() {
return (
<>
-
-
+
+
-
+
-
+
Company name *
handleFormChange("company_name", e.target.value)}
+ onChange={(e) => {
+ const validation = validateCompanyName(e.target.value, false);
+ if (validation === true || e.target.value === "") {
+ handleFormChange("company_name", e.target.value);
+ }
+ }}
+ maxLength={80}
/>
-
+
Set a password *
@@ -244,13 +268,13 @@ export function InstanceSetupForm() {
hasError={errorData.type && errorData.type === EErrorCodes.INVALID_PASSWORD ? true : false}
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
- autoComplete="on"
+ autoComplete="new-password"
/>
{showPassword.password ? (
handleShowPassword("password")}
>
@@ -259,7 +283,7 @@ export function InstanceSetupForm() {
handleShowPassword("password")}
>
@@ -273,7 +297,7 @@ export function InstanceSetupForm() {
-
+
Confirm password *
@@ -288,12 +312,13 @@ export function InstanceSetupForm() {
className="w-full border border-subtle !bg-surface-1 pr-12 placeholder:text-placeholder"
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
+ autoComplete="new-password"
/>
{showPassword.retypePassword ? (
handleShowPassword("retypePassword")}
>
@@ -302,7 +327,7 @@ export function InstanceSetupForm() {
handleShowPassword("retypePassword")}
>
@@ -319,21 +344,21 @@ export function InstanceSetupForm() {
handleFormChange("is_telemetry_enabled", !formData.is_telemetry_enabled)}
checked={formData.is_telemetry_enabled}
/>
-
+
Allow Plane to anonymously collect usage events.{" "}
See More
diff --git a/apps/admin/core/components/workspace/list-item.tsx b/apps/admin/components/workspace/list-item.tsx
similarity index 77%
rename from apps/admin/core/components/workspace/list-item.tsx
rename to apps/admin/components/workspace/list-item.tsx
index a4e0df2259..9594d7f219 100644
--- a/apps/admin/core/components/workspace/list-item.tsx
+++ b/apps/admin/components/workspace/list-item.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
// plane internal packages
@@ -24,19 +30,19 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
key={workspaceId}
href={`${WEB_BASE_URL}/${encodeURIComponent(workspace.slug)}`}
target="_blank"
- className="group flex items-center justify-between p-3 gap-2.5 truncate border border-subtle hover:border-subtle-1 bg-layer-1 hover:bg-layer-1-hover hover:shadow-raised-100 rounded-lg"
+ className="group flex items-center justify-between gap-2.5 truncate rounded-lg border border-subtle bg-layer-1 p-3 hover:border-subtle-1 hover:bg-layer-1-hover hover:shadow-raised-100"
rel="noreferrer"
>
{workspace?.logo_url && workspace.logo_url !== "" ? (
) : (
@@ -44,7 +50,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
)}
-
+
{workspace.name} /
[{workspace.slug}]
@@ -52,14 +58,14 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
{workspace.owner.email && (
-
Owned by:
+ Owned by:
{workspace.owner.email}
)}
{workspace.total_projects !== null && (
- Total projects:
+ Total projects:
{workspace.total_projects}
)}
@@ -67,7 +73,7 @@ export const WorkspaceListItem = observer(function WorkspaceListItem({ workspace
<>
•
- Total members:
+ Total members:
{workspace.total_members}
>
diff --git a/apps/admin/core/components/common/header/extended.ts b/apps/admin/core/components/common/header/extended.ts
deleted file mode 100644
index 152b082498..0000000000
--- a/apps/admin/core/components/common/header/extended.ts
+++ /dev/null
@@ -1 +0,0 @@
-export const EXTENDED_HEADER_SEGMENT_LABELS: Record
= {};
diff --git a/apps/admin/core/components/instance/form-header.tsx b/apps/admin/core/components/instance/form-header.tsx
deleted file mode 100644
index 75062c30f5..0000000000
--- a/apps/admin/core/components/instance/form-header.tsx
+++ /dev/null
@@ -1,8 +0,0 @@
-export function FormHeader({ heading, subHeading }: { heading: string; subHeading: string }) {
- return (
-
- {heading}
- {subHeading}
-
- );
-}
diff --git a/apps/admin/core/hooks/store/index.ts b/apps/admin/core/hooks/store/index.ts
deleted file mode 100644
index ed1781299f..0000000000
--- a/apps/admin/core/hooks/store/index.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export * from "./use-theme";
-export * from "./use-instance";
-export * from "./use-user";
-export * from "./use-workspace";
diff --git a/apps/admin/core/lib/b-progress/index.tsx b/apps/admin/core/lib/b-progress/index.tsx
deleted file mode 100644
index 7b531da2b2..0000000000
--- a/apps/admin/core/lib/b-progress/index.tsx
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./AppProgressBar";
diff --git a/apps/admin/core/providers/extended.tsx b/apps/admin/core/providers/extended.tsx
deleted file mode 100644
index 60f36cbe47..0000000000
--- a/apps/admin/core/providers/extended.tsx
+++ /dev/null
@@ -1,3 +0,0 @@
-export function ExtendedProviders({ children }: { children: React.ReactNode }) {
- return <>{children}>;
-}
diff --git a/apps/admin/core/utils/public-asset.ts b/apps/admin/core/utils/public-asset.ts
deleted file mode 100644
index cb0ff5c3b5..0000000000
--- a/apps/admin/core/utils/public-asset.ts
+++ /dev/null
@@ -1 +0,0 @@
-export {};
diff --git a/apps/admin/ee/store/root.store.ts b/apps/admin/ee/store/root.store.ts
deleted file mode 100644
index c514c4c25f..0000000000
--- a/apps/admin/ee/store/root.store.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "ce/store/root.store";
diff --git a/apps/admin/helpers/authentication.ts b/apps/admin/helpers/authentication.ts
new file mode 100644
index 0000000000..22b5e3b4b6
--- /dev/null
+++ b/apps/admin/helpers/authentication.ts
@@ -0,0 +1,36 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+import type {
+ IFormattedInstanceConfiguration,
+ TInstanceAuthenticationModes,
+ TInstanceConfigurationKeys,
+} from "@plane/types";
+
+/**
+ * Checks if a given authentication method can be disabled.
+ * @param configKey - The configuration key to check.
+ * @param authModes - The authentication modes to check.
+ * @param formattedConfig - The formatted configuration to check.
+ * @returns True if the authentication method can be disabled, false otherwise.
+ */
+export const canDisableAuthMethod = (
+ configKey: TInstanceConfigurationKeys,
+ authModes: TInstanceAuthenticationModes[],
+ formattedConfig: IFormattedInstanceConfiguration | undefined
+): boolean => {
+ // Count currently enabled methods
+ const enabledCount = authModes.reduce((count, method) => {
+ const enabledKey = method.enabledConfigKey;
+ if (!enabledKey || !formattedConfig) return count;
+ const isEnabled = Boolean(parseInt(formattedConfig[enabledKey] ?? "0"));
+ return isEnabled ? count + 1 : count;
+ }, 0);
+
+ // If trying to disable and only 1 method is enabled, prevent it
+ const isCurrentlyEnabled = Boolean(parseInt(formattedConfig?.[configKey] ?? "0"));
+ return !(isCurrentlyEnabled && enabledCount === 1);
+};
diff --git a/apps/admin/core/hooks/oauth/core.tsx b/apps/admin/hooks/oauth/core.tsx
similarity index 88%
rename from apps/admin/core/hooks/oauth/core.tsx
rename to apps/admin/hooks/oauth/core.tsx
index dc04b40ff7..9e6914e41c 100644
--- a/apps/admin/core/hooks/oauth/core.tsx
+++ b/apps/admin/hooks/oauth/core.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { KeyRound, Mails } from "lucide-react";
// types
import type {
@@ -34,6 +40,7 @@ export const getCoreAuthenticationModesMap: (
"Log in or sign up for Plane using codes sent via email. You need to have set up SMTP to use this method.",
icon: ,
config: ,
+ enabledConfigKey: "ENABLE_MAGIC_LINK_LOGIN",
},
"passwords-login": {
key: "passwords-login",
@@ -41,6 +48,7 @@ export const getCoreAuthenticationModesMap: (
description: "Allow members to create accounts with passwords and use it with their email addresses to sign in.",
icon: ,
config: ,
+ enabledConfigKey: "ENABLE_EMAIL_PASSWORD",
},
google: {
key: "google",
@@ -48,6 +56,7 @@ export const getCoreAuthenticationModesMap: (
description: "Allow members to log in or sign up for Plane with their Google accounts.",
icon: ,
config: ,
+ enabledConfigKey: "IS_GOOGLE_ENABLED",
},
github: {
key: "github",
@@ -62,6 +71,7 @@ export const getCoreAuthenticationModesMap: (
/>
),
config: ,
+ enabledConfigKey: "IS_GITHUB_ENABLED",
},
gitlab: {
key: "gitlab",
@@ -69,6 +79,7 @@ export const getCoreAuthenticationModesMap: (
description: "Allow members to log in or sign up to plane with their GitLab accounts.",
icon: ,
config: ,
+ enabledConfigKey: "IS_GITLAB_ENABLED",
},
gitea: {
key: "gitea",
@@ -76,5 +87,6 @@ export const getCoreAuthenticationModesMap: (
description: "Allow members to log in or sign up to plane with their Gitea accounts.",
icon: ,
config: ,
+ enabledConfigKey: "IS_GITEA_ENABLED",
},
});
diff --git a/apps/admin/core/hooks/oauth/index.ts b/apps/admin/hooks/oauth/index.ts
similarity index 82%
rename from apps/admin/core/hooks/oauth/index.ts
rename to apps/admin/hooks/oauth/index.ts
index 2982814e5b..74c11e33fc 100644
--- a/apps/admin/core/hooks/oauth/index.ts
+++ b/apps/admin/hooks/oauth/index.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import type { TInstanceAuthenticationModes } from "@plane/types";
import { getCoreAuthenticationModesMap } from "./core";
import type { TGetAuthenticationModeProps } from "./types";
diff --git a/apps/admin/core/hooks/oauth/types.ts b/apps/admin/hooks/oauth/types.ts
similarity index 62%
rename from apps/admin/core/hooks/oauth/types.ts
rename to apps/admin/hooks/oauth/types.ts
index cf265152ac..3e89ad9367 100644
--- a/apps/admin/core/hooks/oauth/types.ts
+++ b/apps/admin/hooks/oauth/types.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import type { TInstanceAuthenticationMethodKeys } from "@plane/types";
export type TGetAuthenticationModeProps = {
diff --git a/apps/admin/hooks/store/index.ts b/apps/admin/hooks/store/index.ts
new file mode 100644
index 0000000000..3b8df72f67
--- /dev/null
+++ b/apps/admin/hooks/store/index.ts
@@ -0,0 +1,10 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export * from "./use-theme";
+export * from "./use-instance";
+export * from "./use-user";
+export * from "./use-workspace";
diff --git a/apps/admin/core/hooks/store/use-instance.tsx b/apps/admin/hooks/store/use-instance.tsx
similarity index 71%
rename from apps/admin/core/hooks/store/use-instance.tsx
rename to apps/admin/hooks/store/use-instance.tsx
index 508e8fe1ec..4ae991139d 100644
--- a/apps/admin/core/hooks/store/use-instance.tsx
+++ b/apps/admin/hooks/store/use-instance.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
diff --git a/apps/admin/core/hooks/store/use-theme.tsx b/apps/admin/hooks/store/use-theme.tsx
similarity index 70%
rename from apps/admin/core/hooks/store/use-theme.tsx
rename to apps/admin/hooks/store/use-theme.tsx
index 289ea6ae4a..2348c2e43a 100644
--- a/apps/admin/core/hooks/store/use-theme.tsx
+++ b/apps/admin/hooks/store/use-theme.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
diff --git a/apps/admin/core/hooks/store/use-user.tsx b/apps/admin/hooks/store/use-user.tsx
similarity index 69%
rename from apps/admin/core/hooks/store/use-user.tsx
rename to apps/admin/hooks/store/use-user.tsx
index 80cd046b49..5eba488d90 100644
--- a/apps/admin/core/hooks/store/use-user.tsx
+++ b/apps/admin/hooks/store/use-user.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
diff --git a/apps/admin/core/hooks/store/use-workspace.tsx b/apps/admin/hooks/store/use-workspace.tsx
similarity index 71%
rename from apps/admin/core/hooks/store/use-workspace.tsx
rename to apps/admin/hooks/store/use-workspace.tsx
index 957a33ff3c..42a2bf7f7c 100644
--- a/apps/admin/core/hooks/store/use-workspace.tsx
+++ b/apps/admin/hooks/store/use-workspace.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useContext } from "react";
// store
import { StoreContext } from "@/providers/store.provider";
diff --git a/apps/admin/core/hooks/use-sidebar-menu/core.ts b/apps/admin/hooks/use-sidebar-menu/core.ts
similarity index 88%
rename from apps/admin/core/hooks/use-sidebar-menu/core.ts
rename to apps/admin/hooks/use-sidebar-menu/core.ts
index 7a821d8990..9d4e449b3a 100644
--- a/apps/admin/core/hooks/use-sidebar-menu/core.ts
+++ b/apps/admin/hooks/use-sidebar-menu/core.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { Image, BrainCog, Cog, Mail } from "lucide-react";
// plane imports
import { LockIcon, WorkspaceIcon } from "@plane/propel/icons";
diff --git a/apps/admin/core/hooks/use-sidebar-menu/index.ts b/apps/admin/hooks/use-sidebar-menu/index.ts
similarity index 71%
rename from apps/admin/core/hooks/use-sidebar-menu/index.ts
rename to apps/admin/hooks/use-sidebar-menu/index.ts
index 0f9e717dc6..cfc76f47ea 100644
--- a/apps/admin/core/hooks/use-sidebar-menu/index.ts
+++ b/apps/admin/hooks/use-sidebar-menu/index.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
// local imports
import { coreSidebarMenuLinks } from "./core";
import type { TSidebarMenuItem } from "./types";
diff --git a/apps/admin/core/hooks/use-sidebar-menu/types.ts b/apps/admin/hooks/use-sidebar-menu/types.ts
similarity index 56%
rename from apps/admin/core/hooks/use-sidebar-menu/types.ts
rename to apps/admin/hooks/use-sidebar-menu/types.ts
index d7a49a50d7..dfe531bb28 100644
--- a/apps/admin/core/hooks/use-sidebar-menu/types.ts
+++ b/apps/admin/hooks/use-sidebar-menu/types.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import type { LucideIcon } from "lucide-react";
export type TSidebarMenuItem = {
diff --git a/apps/space/core/lib/b-progress/AppProgressBar.tsx b/apps/admin/lib/b-progress/AppProgressBar.tsx
similarity index 95%
rename from apps/space/core/lib/b-progress/AppProgressBar.tsx
rename to apps/admin/lib/b-progress/AppProgressBar.tsx
index 7ad93fc11b..e4362581dc 100644
--- a/apps/space/core/lib/b-progress/AppProgressBar.tsx
+++ b/apps/admin/lib/b-progress/AppProgressBar.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useEffect, useRef } from "react";
import { BProgress } from "@bprogress/core";
import { useNavigation } from "react-router";
diff --git a/apps/admin/lib/b-progress/index.tsx b/apps/admin/lib/b-progress/index.tsx
new file mode 100644
index 0000000000..592017255e
--- /dev/null
+++ b/apps/admin/lib/b-progress/index.tsx
@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export * from "./AppProgressBar";
diff --git a/apps/admin/nginx/nginx.conf b/apps/admin/nginx/nginx.conf
index 243aebff54..0fd4a192ae 100644
--- a/apps/admin/nginx/nginx.conf
+++ b/apps/admin/nginx/nginx.conf
@@ -20,6 +20,12 @@ http {
server {
listen 3000;
+ # Security headers
+ add_header X-Frame-Options "DENY" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+ add_header X-XSS-Protection "1; mode=block" always;
+
location / {
root /usr/share/nginx/html;
index index.html index.htm;
diff --git a/apps/admin/package.json b/apps/admin/package.json
index 25f0076f63..2d314b0612 100644
--- a/apps/admin/package.json
+++ b/apps/admin/package.json
@@ -1,9 +1,9 @@
{
"name": "admin",
- "description": "Admin UI for Plane",
"version": "1.2.0",
- "license": "AGPL-3.0",
"private": true,
+ "description": "Admin UI for Plane",
+ "license": "AGPL-3.0",
"type": "module",
"scripts": {
"dev": "react-router dev --port 3001",
@@ -11,11 +11,11 @@
"preview": "react-router build && serve -s build/client -l 3001",
"start": "serve -s build/client -l 3001",
"clean": "rm -rf .turbo && rm -rf .next && rm -rf node_modules && rm -rf dist && rm -rf build",
- "check:lint": "eslint . --cache --cache-location node_modules/.cache/eslint/ --max-warnings=485",
+ "check:lint": "oxlint --max-warnings=759 .",
"check:types": "react-router typegen && tsc --noEmit",
- "check:format": "prettier . --cache --check",
- "fix:lint": "eslint . --cache --cache-location node_modules/.cache/eslint/ --fix --max-warnings=485",
- "fix:format": "prettier . --cache --write"
+ "check:format": "oxfmt --check .",
+ "fix:lint": "oxlint --fix .",
+ "fix:format": "oxfmt ."
},
"dependencies": {
"@bprogress/core": "catalog:",
@@ -31,7 +31,6 @@
"@plane/ui": "workspace:*",
"@plane/utils": "workspace:*",
"@react-router/node": "catalog:",
- "@sentry/react-router": "catalog:",
"@tanstack/react-virtual": "^3.13.12",
"@tanstack/virtual-core": "^3.13.12",
"axios": "catalog:",
@@ -45,7 +44,6 @@
"react-dom": "catalog:",
"react-hook-form": "7.51.5",
"react-router": "catalog:",
- "react-router-dom": "catalog:",
"serve": "14.2.5",
"swr": "catalog:",
"uuid": "catalog:"
diff --git a/apps/admin/core/providers/core.tsx b/apps/admin/providers/core.tsx
similarity index 86%
rename from apps/admin/core/providers/core.tsx
rename to apps/admin/providers/core.tsx
index d06d8f3f4a..3b22c70878 100644
--- a/apps/admin/core/providers/core.tsx
+++ b/apps/admin/providers/core.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { ThemeProvider } from "next-themes";
import { SWRConfig } from "swr";
import { AppProgressBar } from "@/lib/b-progress";
diff --git a/apps/admin/providers/extended.tsx b/apps/admin/providers/extended.tsx
new file mode 100644
index 0000000000..72023a79b5
--- /dev/null
+++ b/apps/admin/providers/extended.tsx
@@ -0,0 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export function ExtendedProviders({ children }: { children: React.ReactNode }) {
+ return <>{children}>;
+}
diff --git a/apps/admin/core/providers/index.tsx b/apps/admin/providers/index.tsx
similarity index 64%
rename from apps/admin/core/providers/index.tsx
rename to apps/admin/providers/index.tsx
index c0447b5bce..2ecd419b23 100644
--- a/apps/admin/core/providers/index.tsx
+++ b/apps/admin/providers/index.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { CoreProviders } from "./core";
import { ExtendedProviders } from "./extended";
diff --git a/apps/admin/core/providers/instance.provider.tsx b/apps/admin/providers/instance.provider.tsx
similarity index 77%
rename from apps/admin/core/providers/instance.provider.tsx
rename to apps/admin/providers/instance.provider.tsx
index 5dcd3b6f5f..50e6696217 100644
--- a/apps/admin/core/providers/instance.provider.tsx
+++ b/apps/admin/providers/instance.provider.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { observer } from "mobx-react";
import useSWR from "swr";
// hooks
diff --git a/apps/admin/core/providers/store.provider.tsx b/apps/admin/providers/store.provider.tsx
similarity index 84%
rename from apps/admin/core/providers/store.provider.tsx
rename to apps/admin/providers/store.provider.tsx
index 49e341b723..9ddb53b6b9 100644
--- a/apps/admin/core/providers/store.provider.tsx
+++ b/apps/admin/providers/store.provider.tsx
@@ -1,6 +1,12 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { createContext } from "react";
// plane admin store
-import { RootStore } from "@/plane-admin/store/root.store";
+import { RootStore } from "../store/root.store";
let rootStore = new RootStore();
diff --git a/apps/admin/core/providers/toast.tsx b/apps/admin/providers/toast.tsx
similarity index 64%
rename from apps/admin/core/providers/toast.tsx
rename to apps/admin/providers/toast.tsx
index 1e7e3a11e0..541678efa5 100644
--- a/apps/admin/core/providers/toast.tsx
+++ b/apps/admin/providers/toast.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useTheme } from "next-themes";
import { Toast } from "@plane/propel/toast";
import { resolveGeneralTheme } from "@plane/utils";
diff --git a/apps/admin/core/providers/user.provider.tsx b/apps/admin/providers/user.provider.tsx
similarity index 86%
rename from apps/admin/core/providers/user.provider.tsx
rename to apps/admin/providers/user.provider.tsx
index 04242abc9e..3a840c1865 100644
--- a/apps/admin/core/providers/user.provider.tsx
+++ b/apps/admin/providers/user.provider.tsx
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { useEffect } from "react";
import { observer } from "mobx-react";
import useSWR from "swr";
diff --git a/apps/admin/core/store/instance.store.ts b/apps/admin/store/instance.store.ts
similarity index 96%
rename from apps/admin/core/store/instance.store.ts
rename to apps/admin/store/instance.store.ts
index ec89229205..ed92beae11 100644
--- a/apps/admin/core/store/instance.store.ts
+++ b/apps/admin/store/instance.store.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { set } from "lodash-es";
import { observable, action, computed, makeObservable, runInAction } from "mobx";
// plane internal packages
@@ -13,7 +19,7 @@ import type {
IInstanceConfig,
} from "@plane/types";
// root store
-import type { CoreRootStore } from "@/store/root.store";
+import type { RootStore } from "@/store/root.store";
export interface IInstanceStore {
// issues
@@ -47,7 +53,7 @@ export class InstanceStore implements IInstanceStore {
// service
instanceService;
- constructor(private store: CoreRootStore) {
+ constructor(private store: RootStore) {
makeObservable(this, {
// observable
isLoading: observable.ref,
diff --git a/apps/admin/core/store/root.store.ts b/apps/admin/store/root.store.ts
similarity index 87%
rename from apps/admin/core/store/root.store.ts
rename to apps/admin/store/root.store.ts
index 68d11885b7..42f0a63e5b 100644
--- a/apps/admin/core/store/root.store.ts
+++ b/apps/admin/store/root.store.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { enableStaticRendering } from "mobx-react";
// stores
import type { IInstanceStore } from "./instance.store";
@@ -11,7 +17,7 @@ import { WorkspaceStore } from "./workspace.store";
enableStaticRendering(typeof window === "undefined");
-export abstract class CoreRootStore {
+export class RootStore {
theme: IThemeStore;
instance: IInstanceStore;
user: IUserStore;
diff --git a/apps/admin/core/store/theme.store.ts b/apps/admin/store/theme.store.ts
similarity index 88%
rename from apps/admin/core/store/theme.store.ts
rename to apps/admin/store/theme.store.ts
index 4512facd2b..bb663804fe 100644
--- a/apps/admin/core/store/theme.store.ts
+++ b/apps/admin/store/theme.store.ts
@@ -1,6 +1,12 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { action, observable, makeObservable } from "mobx";
// root store
-import type { CoreRootStore } from "@/store/root.store";
+import type { RootStore } from "./root.store";
type TTheme = "dark" | "light";
export interface IThemeStore {
@@ -21,7 +27,7 @@ export class ThemeStore implements IThemeStore {
isSidebarCollapsed: boolean | undefined = undefined;
theme: string | undefined = undefined;
- constructor(private store: CoreRootStore) {
+ constructor(private store: RootStore) {
makeObservable(this, {
// observables
isNewUserPopup: observable.ref,
diff --git a/apps/admin/core/store/user.store.ts b/apps/admin/store/user.store.ts
similarity index 91%
rename from apps/admin/core/store/user.store.ts
rename to apps/admin/store/user.store.ts
index 1187355a04..c218eec0fa 100644
--- a/apps/admin/core/store/user.store.ts
+++ b/apps/admin/store/user.store.ts
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { action, observable, runInAction, makeObservable } from "mobx";
// plane internal packages
import type { TUserStatus } from "@plane/constants";
@@ -5,7 +11,7 @@ import { EUserStatus } from "@plane/constants";
import { AuthService, UserService } from "@plane/services";
import type { IUser } from "@plane/types";
// root store
-import type { CoreRootStore } from "@/store/root.store";
+import type { RootStore } from "@/store/root.store";
export interface IUserStore {
// observables
@@ -30,7 +36,7 @@ export class UserStore implements IUserStore {
userService;
authService;
- constructor(private store: CoreRootStore) {
+ constructor(private store: RootStore) {
makeObservable(this, {
// observables
isLoading: observable.ref,
diff --git a/apps/admin/core/store/workspace.store.ts b/apps/admin/store/workspace.store.ts
similarity index 94%
rename from apps/admin/core/store/workspace.store.ts
rename to apps/admin/store/workspace.store.ts
index f9203ed40c..7dbfaa1af9 100644
--- a/apps/admin/core/store/workspace.store.ts
+++ b/apps/admin/store/workspace.store.ts
@@ -1,10 +1,16 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
import { set } from "lodash-es";
import { action, observable, runInAction, makeObservable, computed } from "mobx";
// plane imports
import { InstanceWorkspaceService } from "@plane/services";
import type { IWorkspace, TLoader, TPaginationInfo } from "@plane/types";
// root store
-import type { CoreRootStore } from "@/store/root.store";
+import type { RootStore } from "@/store/root.store";
export interface IWorkspaceStore {
// observables
@@ -31,7 +37,7 @@ export class WorkspaceStore implements IWorkspaceStore {
// services
instanceWorkspaceService;
- constructor(private store: CoreRootStore) {
+ constructor(private store: RootStore) {
makeObservable(this, {
// observables
loader: observable,
diff --git a/apps/admin/tsconfig.json b/apps/admin/tsconfig.json
index 90dedb762e..2d04cbe81f 100644
--- a/apps/admin/tsconfig.json
+++ b/apps/admin/tsconfig.json
@@ -9,12 +9,7 @@
"types": ["vite/client"],
"paths": {
"package.json": ["./package.json"],
- "ce/*": ["./ce/*"],
- "@/app/*": ["./app/*"],
- "@/*": ["./core/*"],
- "@/plane-admin/*": ["./ce/*"],
- "@/ce/*": ["./ce/*"],
- "@/styles/*": ["./styles/*"]
+ "@/*": ["./*"]
}
},
"include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"],
diff --git a/apps/admin/utils/public-asset.ts b/apps/admin/utils/public-asset.ts
new file mode 100644
index 0000000000..382ca5fe35
--- /dev/null
+++ b/apps/admin/utils/public-asset.ts
@@ -0,0 +1,7 @@
+/**
+ * Copyright (c) 2023-present Plane Software, Inc. and contributors
+ * SPDX-License-Identifier: AGPL-3.0-only
+ * See the LICENSE file for details.
+ */
+
+export {};
diff --git a/apps/api/manage.py b/apps/api/manage.py
index 9728694628..a79268b37c 100644
--- a/apps/api/manage.py
+++ b/apps/api/manage.py
@@ -1,4 +1,8 @@
#!/usr/bin/env python
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import os
import sys
diff --git a/apps/api/plane/__init__.py b/apps/api/plane/__init__.py
index 53f4ccb1d8..f561e3e630 100644
--- a/apps/api/plane/__init__.py
+++ b/apps/api/plane/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .celery import app as celery_app
__all__ = ("celery_app",)
diff --git a/apps/api/plane/analytics/__init__.py b/apps/api/plane/analytics/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/analytics/__init__.py
+++ b/apps/api/plane/analytics/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/analytics/apps.py b/apps/api/plane/analytics/apps.py
index 52a59f3138..b11b7bfc94 100644
--- a/apps/api/plane/analytics/apps.py
+++ b/apps/api/plane/analytics/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/api/__init__.py b/apps/api/plane/api/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/api/__init__.py
+++ b/apps/api/plane/api/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/api/apps.py b/apps/api/plane/api/apps.py
index f1f5311188..90688c1306 100644
--- a/apps/api/plane/api/apps.py
+++ b/apps/api/plane/api/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/api/middleware/__init__.py b/apps/api/plane/api/middleware/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/api/middleware/__init__.py
+++ b/apps/api/plane/api/middleware/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/api/middleware/api_authentication.py b/apps/api/plane/api/middleware/api_authentication.py
index ddabb4132d..abd8139857 100644
--- a/apps/api/plane/api/middleware/api_authentication.py
+++ b/apps/api/plane/api/middleware/api_authentication.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
from django.db.models import Q
diff --git a/apps/api/plane/api/rate_limit.py b/apps/api/plane/api/rate_limit.py
index 0d266e98b5..33df895cbf 100644
--- a/apps/api/plane/api/rate_limit.py
+++ b/apps/api/plane/api/rate_limit.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# python imports
import os
diff --git a/apps/api/plane/api/serializers/__init__.py b/apps/api/plane/api/serializers/__init__.py
index 550d8e87f4..44e527a2dc 100644
--- a/apps/api/plane/api/serializers/__init__.py
+++ b/apps/api/plane/api/serializers/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .user import UserLiteSerializer
from .workspace import WorkspaceLiteSerializer
from .project import (
diff --git a/apps/api/plane/api/serializers/asset.py b/apps/api/plane/api/serializers/asset.py
index 6b74b37572..363b5eb842 100644
--- a/apps/api/plane/api/serializers/asset.py
+++ b/apps/api/plane/api/serializers/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/api/serializers/base.py b/apps/api/plane/api/serializers/base.py
index bc790f2cd7..2e39a01fd5 100644
--- a/apps/api/plane/api/serializers/base.py
+++ b/apps/api/plane/api/serializers/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/api/serializers/cycle.py b/apps/api/plane/api/serializers/cycle.py
index f2724231a2..9b6aed516d 100644
--- a/apps/api/plane/api/serializers/cycle.py
+++ b/apps/api/plane/api/serializers/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
import pytz
from rest_framework import serializers
diff --git a/apps/api/plane/api/serializers/estimate.py b/apps/api/plane/api/serializers/estimate.py
index b670006d53..e2a7c5e1d3 100644
--- a/apps/api/plane/api/serializers/estimate.py
+++ b/apps/api/plane/api/serializers/estimate.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from plane.db.models import EstimatePoint
from .base import BaseSerializer
diff --git a/apps/api/plane/api/serializers/intake.py b/apps/api/plane/api/serializers/intake.py
index 40cbba38b6..12bbd4572a 100644
--- a/apps/api/plane/api/serializers/intake.py
+++ b/apps/api/plane/api/serializers/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from .issue import IssueExpandSerializer
@@ -13,11 +17,14 @@ class IssueForIntakeSerializer(BaseSerializer):
content validation and priority assignment for triage workflows.
"""
+ description = serializers.JSONField(source="description_json", required=False, allow_null=True)
+
class Meta:
model = Issue
fields = [
"name",
- "description",
+ "description", # Deprecated
+ "description_json",
"description_html",
"priority",
]
diff --git a/apps/api/plane/api/serializers/invite.py b/apps/api/plane/api/serializers/invite.py
index 5b52dc03c4..18c1c02066 100644
--- a/apps/api/plane/api/serializers/invite.py
+++ b/apps/api/plane/api/serializers/invite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
diff --git a/apps/api/plane/api/serializers/issue.py b/apps/api/plane/api/serializers/issue.py
index d86dfa6b6e..ba9ddae301 100644
--- a/apps/api/plane/api/serializers/issue.py
+++ b/apps/api/plane/api/serializers/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
from lxml import html
@@ -65,7 +69,7 @@ class IssueSerializer(BaseSerializer):
class Meta:
model = Issue
read_only_fields = ["id", "workspace", "project", "updated_by", "updated_at"]
- exclude = ["description", "description_stripped"]
+ exclude = ["description_json", "description_stripped"]
def validate(self, data):
if (
@@ -633,6 +637,7 @@ class IssueExpandSerializer(BaseSerializer):
labels = serializers.SerializerMethodField()
assignees = serializers.SerializerMethodField()
state = StateLiteSerializer(read_only=True)
+ description = serializers.JSONField(source="description_json", read_only=True)
def get_labels(self, obj):
expand = self.context.get("expand", [])
diff --git a/apps/api/plane/api/serializers/member.py b/apps/api/plane/api/serializers/member.py
index 3aa9644b4c..266a4cfe11 100644
--- a/apps/api/plane/api/serializers/member.py
+++ b/apps/api/plane/api/serializers/member.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/api/serializers/module.py b/apps/api/plane/api/serializers/module.py
index d1e3b0d81a..8ab73a33ee 100644
--- a/apps/api/plane/api/serializers/module.py
+++ b/apps/api/plane/api/serializers/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/api/serializers/project.py b/apps/api/plane/api/serializers/project.py
index c9a0a31c89..644b5ba107 100644
--- a/apps/api/plane/api/serializers/project.py
+++ b/apps/api/plane/api/serializers/project.py
@@ -1,7 +1,15 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
import random
from rest_framework import serializers
+
+# Python imports
+import re
+
# Module imports
from plane.db.models import Project, ProjectIdentifier, WorkspaceMember, State, Estimate
@@ -97,6 +105,15 @@ class ProjectCreateSerializer(BaseSerializer):
]
def validate(self, data):
+ project_name = data.get("name", None)
+ project_identifier = data.get("identifier", None)
+
+ if project_name is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_name):
+ raise serializers.ValidationError("Project name cannot contain special characters.")
+
+ if project_identifier is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_identifier):
+ raise serializers.ValidationError("Project identifier cannot contain special characters.")
+
if data.get("project_lead", None) is not None:
# Check if the project lead is a member of the workspace
if not WorkspaceMember.objects.filter(
@@ -156,6 +173,15 @@ class ProjectUpdateSerializer(ProjectCreateSerializer):
read_only_fields = ProjectCreateSerializer.Meta.read_only_fields
def update(self, instance, validated_data):
+ project_name = validated_data.get("name", None)
+ project_identifier = validated_data.get("identifier", None)
+
+ if project_name is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_name):
+ raise serializers.ValidationError("Project name cannot contain special characters.")
+
+ if project_identifier is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_identifier):
+ raise serializers.ValidationError("Project identifier cannot contain special characters.")
+
"""Update a project"""
if (
validated_data.get("default_state", None) is not None
@@ -206,6 +232,15 @@ class ProjectSerializer(BaseSerializer):
]
def validate(self, data):
+ project_name = data.get("name", None)
+ project_identifier = data.get("identifier", None)
+
+ if project_name is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_name):
+ raise serializers.ValidationError("Project name cannot contain special characters.")
+
+ if project_identifier is not None and re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, project_identifier):
+ raise serializers.ValidationError("Project identifier cannot contain special characters.")
+
# Check project lead should be a member of the workspace
if (
data.get("project_lead", None) is not None
diff --git a/apps/api/plane/api/serializers/state.py b/apps/api/plane/api/serializers/state.py
index c279529b82..c07fe78bb9 100644
--- a/apps/api/plane/api/serializers/state.py
+++ b/apps/api/plane/api/serializers/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import State, StateGroup
diff --git a/apps/api/plane/api/serializers/sticky.py b/apps/api/plane/api/serializers/sticky.py
index 067fc1b899..e2ffee8a97 100644
--- a/apps/api/plane/api/serializers/sticky.py
+++ b/apps/api/plane/api/serializers/sticky.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
from .base import BaseSerializer
diff --git a/apps/api/plane/api/serializers/user.py b/apps/api/plane/api/serializers/user.py
index 805eb9fe1e..24bb6b902f 100644
--- a/apps/api/plane/api/serializers/user.py
+++ b/apps/api/plane/api/serializers/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
# Module imports
diff --git a/apps/api/plane/api/serializers/workspace.py b/apps/api/plane/api/serializers/workspace.py
index e98683c2fd..6b85fcabcb 100644
--- a/apps/api/plane/api/serializers/workspace.py
+++ b/apps/api/plane/api/serializers/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from plane.db.models import Workspace
from .base import BaseSerializer
diff --git a/apps/api/plane/api/urls/__init__.py b/apps/api/plane/api/urls/__init__.py
index 593501939c..4a202431bc 100644
--- a/apps/api/plane/api/urls/__init__.py
+++ b/apps/api/plane/api/urls/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .asset import urlpatterns as asset_patterns
from .cycle import urlpatterns as cycle_patterns
from .intake import urlpatterns as intake_patterns
diff --git a/apps/api/plane/api/urls/asset.py b/apps/api/plane/api/urls/asset.py
index 5bdd4d914c..abd160242c 100644
--- a/apps/api/plane/api/urls/asset.py
+++ b/apps/api/plane/api/urls/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/urls/cycle.py b/apps/api/plane/api/urls/cycle.py
index a2cab1fe69..6d582784e8 100644
--- a/apps/api/plane/api/urls/cycle.py
+++ b/apps/api/plane/api/urls/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views.cycle import (
diff --git a/apps/api/plane/api/urls/intake.py b/apps/api/plane/api/urls/intake.py
index 5538467aaf..6a54804590 100644
--- a/apps/api/plane/api/urls/intake.py
+++ b/apps/api/plane/api/urls/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/urls/invite.py b/apps/api/plane/api/urls/invite.py
index e4fddd5c50..7c1ce5e6a0 100644
--- a/apps/api/plane/api/urls/invite.py
+++ b/apps/api/plane/api/urls/invite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.urls import path, include
diff --git a/apps/api/plane/api/urls/label.py b/apps/api/plane/api/urls/label.py
index f7ee57b171..358806fb78 100644
--- a/apps/api/plane/api/urls/label.py
+++ b/apps/api/plane/api/urls/label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import LabelListCreateAPIEndpoint, LabelDetailAPIEndpoint
diff --git a/apps/api/plane/api/urls/member.py b/apps/api/plane/api/urls/member.py
index 83c9dfbe50..4f2b032307 100644
--- a/apps/api/plane/api/urls/member.py
+++ b/apps/api/plane/api/urls/member.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/urls/module.py b/apps/api/plane/api/urls/module.py
index 578f5c860c..a0924100d3 100644
--- a/apps/api/plane/api/urls/module.py
+++ b/apps/api/plane/api/urls/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/urls/project.py b/apps/api/plane/api/urls/project.py
index 9cf9291aa6..eb22249e0a 100644
--- a/apps/api/plane/api/urls/project.py
+++ b/apps/api/plane/api/urls/project.py
@@ -1,9 +1,14 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
ProjectListCreateAPIEndpoint,
ProjectDetailAPIEndpoint,
ProjectArchiveUnarchiveAPIEndpoint,
+ ProjectSummaryAPIEndpoint,
)
urlpatterns = [
@@ -22,4 +27,9 @@ urlpatterns = [
ProjectArchiveUnarchiveAPIEndpoint.as_view(http_method_names=["post", "delete"]),
name="project-archive-unarchive",
),
+ path(
+ "workspaces//projects//summary/",
+ ProjectSummaryAPIEndpoint.as_view(http_method_names=["get"]),
+ name="project-summary",
+ ),
]
diff --git a/apps/api/plane/api/urls/schema.py b/apps/api/plane/api/urls/schema.py
index 781dbe9deb..9511ca02b8 100644
--- a/apps/api/plane/api/urls/schema.py
+++ b/apps/api/plane/api/urls/schema.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from drf_spectacular.views import (
SpectacularAPIView,
SpectacularRedocView,
diff --git a/apps/api/plane/api/urls/state.py b/apps/api/plane/api/urls/state.py
index e35012a200..8b84abfbe4 100644
--- a/apps/api/plane/api/urls/state.py
+++ b/apps/api/plane/api/urls/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/urls/sticky.py b/apps/api/plane/api/urls/sticky.py
index 0066e77ea4..0df9c49c39 100644
--- a/apps/api/plane/api/urls/sticky.py
+++ b/apps/api/plane/api/urls/sticky.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path, include
from rest_framework.routers import DefaultRouter
diff --git a/apps/api/plane/api/urls/user.py b/apps/api/plane/api/urls/user.py
index 461b083339..33769b7c01 100644
--- a/apps/api/plane/api/urls/user.py
+++ b/apps/api/plane/api/urls/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import UserEndpoint
diff --git a/apps/api/plane/api/urls/work_item.py b/apps/api/plane/api/urls/work_item.py
index 7207df9579..48b1948dbe 100644
--- a/apps/api/plane/api/urls/work_item.py
+++ b/apps/api/plane/api/urls/work_item.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.api.views import (
diff --git a/apps/api/plane/api/views/__init__.py b/apps/api/plane/api/views/__init__.py
index 75b1b17c40..644d87edc8 100644
--- a/apps/api/plane/api/views/__init__.py
+++ b/apps/api/plane/api/views/__init__.py
@@ -1,7 +1,12 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .project import (
ProjectListCreateAPIEndpoint,
ProjectDetailAPIEndpoint,
ProjectArchiveUnarchiveAPIEndpoint,
+ ProjectSummaryAPIEndpoint,
)
from .state import (
diff --git a/apps/api/plane/api/views/asset.py b/apps/api/plane/api/views/asset.py
index a91ebc8839..88c34c37ca 100644
--- a/apps/api/plane/api/views/asset.py
+++ b/apps/api/plane/api/views/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python Imports
import uuid
diff --git a/apps/api/plane/api/views/base.py b/apps/api/plane/api/views/base.py
index 2e65844301..fc65e7abdc 100644
--- a/apps/api/plane/api/views/base.py
+++ b/apps/api/plane/api/views/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import zoneinfo
import logging
diff --git a/apps/api/plane/api/views/cycle.py b/apps/api/plane/api/views/cycle.py
index 170c644f33..30b04ed46d 100644
--- a/apps/api/plane/api/views/cycle.py
+++ b/apps/api/plane/api/views/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/api/views/intake.py b/apps/api/plane/api/views/intake.py
index 216b27afc0..2df2d30699 100644
--- a/apps/api/plane/api/views/intake.py
+++ b/apps/api/plane/api/views/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
@@ -180,11 +184,14 @@ class IntakeIssueListCreateAPIEndpoint(BaseAPIView):
)
# create an issue
+ issue_data = request.data.get("issue", {})
+ # Accept both "description" and "description_json" keys for the description_json field
+ description_json = issue_data.get("description") or issue_data.get("description_json") or {}
issue = Issue.objects.create(
- name=request.data.get("issue", {}).get("name"),
- description=request.data.get("issue", {}).get("description", {}),
- description_html=request.data.get("issue", {}).get("description_html", "
"),
- priority=request.data.get("issue", {}).get("priority", "none"),
+ name=issue_data.get("name"),
+ description_json=description_json,
+ description_html=issue_data.get("description_html", "
"),
+ priority=issue_data.get("priority", "none"),
project_id=project_id,
state_id=triage_state.id,
)
@@ -365,10 +372,11 @@ class IntakeIssueDetailAPIEndpoint(BaseAPIView):
# Only allow guests to edit name and description
if project_member.role <= 5:
+ description_json = issue_data.get("description") or issue_data.get("description_json") or {}
issue_data = {
"name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html),
- "description": issue_data.get("description", issue.description),
+ "description_json": description_json,
}
issue_serializer = IssueSerializer(issue, data=issue_data, partial=True)
diff --git a/apps/api/plane/api/views/invite.py b/apps/api/plane/api/views/invite.py
index f1263b0090..f1dd6af258 100644
--- a/apps/api/plane/api/views/invite.py
+++ b/apps/api/plane/api/views/invite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/api/views/issue.py b/apps/api/plane/api/views/issue.py
index fe32fe3fdd..b936cdcda0 100644
--- a/apps/api/plane/api/views/issue.py
+++ b/apps/api/plane/api/views/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import uuid
diff --git a/apps/api/plane/api/views/member.py b/apps/api/plane/api/views/member.py
index a569a59769..adb28be003 100644
--- a/apps/api/plane/api/views/member.py
+++ b/apps/api/plane/api/views/member.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/api/views/module.py b/apps/api/plane/api/views/module.py
index a4e0f3fe82..61e198b481 100644
--- a/apps/api/plane/api/views/module.py
+++ b/apps/api/plane/api/views/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
@@ -410,7 +414,7 @@ class ModuleDetailAPIEndpoint(BaseAPIView):
{"error": "Archived module cannot be edited"},
status=status.HTTP_400_BAD_REQUEST,
)
- serializer = ModuleSerializer(module, data=request.data, context={"project_id": project_id}, partial=True)
+ serializer = ModuleUpdateSerializer(module, data=request.data, context={"project_id": project_id}, partial=True)
if serializer.is_valid():
if (
request.data.get("external_id")
diff --git a/apps/api/plane/api/views/project.py b/apps/api/plane/api/views/project.py
index fbf6790516..5ab0fd1c1f 100644
--- a/apps/api/plane/api/views/project.py
+++ b/apps/api/plane/api/views/project.py
@@ -1,9 +1,14 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
# Django imports
from django.db import IntegrityError
-from django.db.models import Exists, F, Func, OuterRef, Prefetch, Q, Subquery
+from django.db.models import Exists, F, Func, OuterRef, Prefetch, Q, Subquery, Count
+from django.db.models.functions import Coalesce
from django.utils import timezone
from django.core.serializers.json import DjangoJSONEncoder
@@ -18,7 +23,6 @@ from drf_spectacular.utils import OpenApiResponse, OpenApiRequest
from plane.db.models import (
Cycle,
Intake,
- IssueUserProperty,
Module,
Project,
DeployBoard,
@@ -27,6 +31,11 @@ from plane.db.models import (
DEFAULT_STATES,
Workspace,
UserFavorite,
+ Label,
+ Issue,
+ StateGroup,
+ IntakeIssue,
+ ProjectPage,
)
from plane.bgtasks.webhook_task import model_activity, webhook_activity
from .base import BaseAPIView
@@ -36,7 +45,7 @@ from plane.api.serializers import (
ProjectCreateSerializer,
ProjectUpdateSerializer,
)
-from plane.app.permissions import ProjectBasePermission
+from plane.app.permissions import ProjectBasePermission, WorkSpaceAdminPermission
from plane.utils.openapi import (
project_docs,
PROJECT_ID_PARAMETER,
@@ -179,9 +188,9 @@ class ProjectListCreateAPIEndpoint(BaseAPIView):
return self.paginate(
request=request,
queryset=(projects),
- on_results=lambda projects: ProjectSerializer(
- projects, many=True, fields=self.fields, expand=self.expand
- ).data,
+ on_results=lambda projects: (
+ ProjectSerializer(projects, many=True, fields=self.fields, expand=self.expand).data
+ ),
)
@project_docs(
@@ -218,8 +227,6 @@ class ProjectListCreateAPIEndpoint(BaseAPIView):
# Add the user as Administrator to the project
_ = ProjectMember.objects.create(project_id=serializer.instance.id, member=request.user, role=20)
- # Also create the issue property for the user
- _ = IssueUserProperty.objects.create(project_id=serializer.instance.id, user=request.user)
if serializer.instance.project_lead is not None and str(serializer.instance.project_lead) != str(
request.user.id
@@ -229,11 +236,6 @@ class ProjectListCreateAPIEndpoint(BaseAPIView):
member_id=serializer.instance.project_lead,
role=20,
)
- # Also create the issue property for the user
- IssueUserProperty.objects.create(
- project_id=serializer.instance.id,
- user_id=serializer.instance.project_lead,
- )
State.objects.bulk_create(
[
@@ -552,3 +554,119 @@ class ProjectArchiveUnarchiveAPIEndpoint(BaseAPIView):
project.archived_at = None
project.save()
return Response(status=status.HTTP_204_NO_CONTENT)
+
+
+ALLOWED_PROJECT_SUMMARY_FIELDS = [
+ "members",
+ "states",
+ "labels",
+ "cycles",
+ "modules",
+ "issues",
+ "intakes",
+ "pages",
+]
+
+
+class ProjectSummaryAPIEndpoint(BaseAPIView):
+ permission_classes = [WorkSpaceAdminPermission]
+ use_read_replica = True
+
+ def get(self, request, slug, project_id):
+ """Get project summary
+
+ Get the summary of a project
+ """
+ project = Project.objects.filter(pk=project_id, workspace__slug=slug).first()
+ if not project:
+ return Response({"error": "Project not found"}, status=status.HTTP_404_NOT_FOUND)
+ fields = request.GET.get("fields", "").split(",")
+ requested_fields = set(filter(None, (f.strip() for f in fields))) & set(ALLOWED_PROJECT_SUMMARY_FIELDS)
+ if not requested_fields:
+ requested_fields = set(ALLOWED_PROJECT_SUMMARY_FIELDS)
+
+ # Single DB round-trip with only requested count subqueries
+ counts = self._get_all_summary_counts(project_id, requested_fields)
+ counts_dict = {field: counts[field] for field in requested_fields}
+ summary = {
+ "id": project.id,
+ "name": project.name,
+ "identifier": project.identifier,
+ "counts": counts_dict,
+ }
+ return Response(summary, status=status.HTTP_200_OK)
+
+ # Getting all summary counts in one ORM query; only runs subqueries for requested fields.
+ def _get_all_summary_counts(self, project_id, requested_fields):
+ """Return requested summary counts in one ORM query; only runs subqueries for requested fields."""
+
+ # Using a different annotation name for 'pages' to avoid conflict with Project.pages (M2M from Page)
+ def _annotation_name(field):
+ return "pages_count" if field == "pages" else field
+
+ subquery_builders = {
+ "members": lambda: (
+ ProjectMember.objects.filter(project_id=OuterRef("pk"), is_active=True)
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "states": lambda: (
+ State.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "labels": lambda: (
+ Label.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "cycles": lambda: (
+ Cycle.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "modules": lambda: (
+ Module.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "issues": lambda: (
+ Issue.objects.filter(project_id=OuterRef("pk"))
+ .exclude(state__group=StateGroup.TRIAGE.value)
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "intakes": lambda: (
+ IntakeIssue.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ "pages": lambda: (
+ ProjectPage.objects.filter(project_id=OuterRef("pk"))
+ .values("project_id")
+ .annotate(count=Count("*"))
+ .values("count")
+ ),
+ }
+
+ # Build annotations dictionary for the requested fields
+ annotations = {
+ _annotation_name(field): Coalesce(Subquery(subquery_builders[field]()), 0) for field in requested_fields
+ }
+
+ # Prepare values list for the annotation names
+ fields_list = sorted(requested_fields)
+ values_list = [_annotation_name(f) for f in fields_list]
+ # Execute the query and get the result
+ query_result = Project.objects.filter(pk=project_id).annotate(**annotations).values(*values_list).first()
+ if not query_result:
+ return {field: 0 for field in requested_fields}
+ # Return the result as a dictionary
+ return {field: query_result[_annotation_name(field)] for field in requested_fields}
diff --git a/apps/api/plane/api/views/state.py b/apps/api/plane/api/views/state.py
index 8d2633e675..eac0ee258f 100644
--- a/apps/api/plane/api/views/state.py
+++ b/apps/api/plane/api/views/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import IntegrityError
diff --git a/apps/api/plane/api/views/sticky.py b/apps/api/plane/api/views/sticky.py
index a5173edc73..f6b4298f66 100644
--- a/apps/api/plane/api/views/sticky.py
+++ b/apps/api/plane/api/views/sticky.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/api/views/user.py b/apps/api/plane/api/views/user.py
index b874cec18b..02d29d1188 100644
--- a/apps/api/plane/api/views/user.py
+++ b/apps/api/plane/api/views/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/__init__.py b/apps/api/plane/app/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/app/__init__.py
+++ b/apps/api/plane/app/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/app/apps.py b/apps/api/plane/app/apps.py
index e3277fc4d8..1dcf0d849f 100644
--- a/apps/api/plane/app/apps.py
+++ b/apps/api/plane/app/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/app/middleware/__init__.py b/apps/api/plane/app/middleware/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/app/middleware/__init__.py
+++ b/apps/api/plane/app/middleware/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/app/middleware/api_authentication.py b/apps/api/plane/app/middleware/api_authentication.py
index ddabb4132d..abd8139857 100644
--- a/apps/api/plane/app/middleware/api_authentication.py
+++ b/apps/api/plane/app/middleware/api_authentication.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
from django.db.models import Q
diff --git a/apps/api/plane/app/permissions/__init__.py b/apps/api/plane/app/permissions/__init__.py
index 849f7ba3ee..22d27694e9 100644
--- a/apps/api/plane/app/permissions/__init__.py
+++ b/apps/api/plane/app/permissions/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .workspace import (
WorkSpaceBasePermission,
WorkspaceOwnerPermission,
diff --git a/apps/api/plane/app/permissions/base.py b/apps/api/plane/app/permissions/base.py
index a2b1a18ff8..7b243cbb78 100644
--- a/apps/api/plane/app/permissions/base.py
+++ b/apps/api/plane/app/permissions/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.db.models import WorkspaceMember, ProjectMember
from functools import wraps
from rest_framework.response import Response
diff --git a/apps/api/plane/app/permissions/page.py b/apps/api/plane/app/permissions/page.py
index bea878f4c4..844ff4dafb 100644
--- a/apps/api/plane/app/permissions/page.py
+++ b/apps/api/plane/app/permissions/page.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.db.models import ProjectMember, Page
from plane.app.permissions import ROLE
diff --git a/apps/api/plane/app/permissions/project.py b/apps/api/plane/app/permissions/project.py
index a8c0f92a27..55550b27ac 100644
--- a/apps/api/plane/app/permissions/project.py
+++ b/apps/api/plane/app/permissions/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.permissions import SAFE_METHODS, BasePermission
diff --git a/apps/api/plane/app/permissions/workspace.py b/apps/api/plane/app/permissions/workspace.py
index 8dc791c0cc..ada16ec3b5 100644
--- a/apps/api/plane/app/permissions/workspace.py
+++ b/apps/api/plane/app/permissions/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.permissions import BasePermission, SAFE_METHODS
diff --git a/apps/api/plane/app/serializers/__init__.py b/apps/api/plane/app/serializers/__init__.py
index 759f27ed6e..e8a4007ea6 100644
--- a/apps/api/plane/app/serializers/__init__.py
+++ b/apps/api/plane/app/serializers/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from .user import (
UserSerializer,
@@ -52,7 +56,7 @@ from .issue import (
IssueCreateSerializer,
IssueActivitySerializer,
IssueCommentSerializer,
- IssueUserPropertySerializer,
+ ProjectUserPropertySerializer,
IssueAssigneeSerializer,
LabelSerializer,
IssueSerializer,
diff --git a/apps/api/plane/app/serializers/analytic.py b/apps/api/plane/app/serializers/analytic.py
index 13b24d14dd..ca86e569ff 100644
--- a/apps/api/plane/app/serializers/analytic.py
+++ b/apps/api/plane/app/serializers/analytic.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from plane.db.models import AnalyticView
from plane.utils.issue_filters import issue_filters
diff --git a/apps/api/plane/app/serializers/api.py b/apps/api/plane/app/serializers/api.py
index 009f7a611f..05c6198f59 100644
--- a/apps/api/plane/app/serializers/api.py
+++ b/apps/api/plane/app/serializers/api.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from plane.db.models import APIToken, APIActivityLog
from rest_framework import serializers
@@ -15,6 +19,9 @@ class APITokenSerializer(BaseSerializer):
"updated_at",
"workspace",
"user",
+ "is_active",
+ "last_used",
+ "user_type",
]
diff --git a/apps/api/plane/app/serializers/asset.py b/apps/api/plane/app/serializers/asset.py
index 560cd35381..1de5961015 100644
--- a/apps/api/plane/app/serializers/asset.py
+++ b/apps/api/plane/app/serializers/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from plane.db.models import FileAsset
diff --git a/apps/api/plane/app/serializers/base.py b/apps/api/plane/app/serializers/base.py
index 0d8c855c91..6457eec50a 100644
--- a/apps/api/plane/app/serializers/base.py
+++ b/apps/api/plane/app/serializers/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/cycle.py b/apps/api/plane/app/serializers/cycle.py
index 89a5efc067..afdc58116b 100644
--- a/apps/api/plane/app/serializers/cycle.py
+++ b/apps/api/plane/app/serializers/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/draft.py b/apps/api/plane/app/serializers/draft.py
index b017a03baf..da6eae6e26 100644
--- a/apps/api/plane/app/serializers/draft.py
+++ b/apps/api/plane/app/serializers/draft.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
diff --git a/apps/api/plane/app/serializers/estimate.py b/apps/api/plane/app/serializers/estimate.py
index b2d65ef8c3..d3343fbe8b 100644
--- a/apps/api/plane/app/serializers/estimate.py
+++ b/apps/api/plane/app/serializers/estimate.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
diff --git a/apps/api/plane/app/serializers/exporter.py b/apps/api/plane/app/serializers/exporter.py
index 5c78cfa694..f8efcfce14 100644
--- a/apps/api/plane/app/serializers/exporter.py
+++ b/apps/api/plane/app/serializers/exporter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import ExporterHistory
diff --git a/apps/api/plane/app/serializers/favorite.py b/apps/api/plane/app/serializers/favorite.py
index 246461f8f3..023c7d5d54 100644
--- a/apps/api/plane/app/serializers/favorite.py
+++ b/apps/api/plane/app/serializers/favorite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
from plane.db.models import UserFavorite, Cycle, Module, Issue, IssueView, Page, Project
diff --git a/apps/api/plane/app/serializers/importer.py b/apps/api/plane/app/serializers/importer.py
index 8997f63920..2dc4e8e721 100644
--- a/apps/api/plane/app/serializers/importer.py
+++ b/apps/api/plane/app/serializers/importer.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
diff --git a/apps/api/plane/app/serializers/intake.py b/apps/api/plane/app/serializers/intake.py
index bc75a0ce58..4037dfe1ca 100644
--- a/apps/api/plane/app/serializers/intake.py
+++ b/apps/api/plane/app/serializers/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party frameworks
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/issue.py b/apps/api/plane/app/serializers/issue.py
index 5e3b93ab67..c5af9b9ffe 100644
--- a/apps/api/plane/app/serializers/issue.py
+++ b/apps/api/plane/app/serializers/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
from django.core.validators import URLValidator
@@ -18,7 +22,7 @@ from plane.db.models import (
Issue,
IssueActivity,
IssueComment,
- IssueUserProperty,
+ ProjectUserProperty,
IssueAssignee,
IssueSubscriber,
IssueLabel,
@@ -53,7 +57,7 @@ class IssueFlatSerializer(BaseSerializer):
fields = [
"id",
"name",
- "description",
+ "description_json",
"description_html",
"priority",
"start_date",
@@ -346,9 +350,9 @@ class IssueActivitySerializer(BaseSerializer):
fields = "__all__"
-class IssueUserPropertySerializer(BaseSerializer):
+class ProjectUserPropertySerializer(BaseSerializer):
class Meta:
- model = IssueUserProperty
+ model = ProjectUserProperty
fields = "__all__"
read_only_fields = ["user", "workspace", "project"]
diff --git a/apps/api/plane/app/serializers/module.py b/apps/api/plane/app/serializers/module.py
index b5e2953cc6..7d01284e30 100644
--- a/apps/api/plane/app/serializers/module.py
+++ b/apps/api/plane/app/serializers/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/notification.py b/apps/api/plane/app/serializers/notification.py
index 58007ec26c..b4eb4eac54 100644
--- a/apps/api/plane/app/serializers/notification.py
+++ b/apps/api/plane/app/serializers/notification.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from .user import UserLiteSerializer
diff --git a/apps/api/plane/app/serializers/page.py b/apps/api/plane/app/serializers/page.py
index 3aecbafda3..a9251129c3 100644
--- a/apps/api/plane/app/serializers/page.py
+++ b/apps/api/plane/app/serializers/page.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
import base64
@@ -58,7 +62,7 @@ class PageSerializer(BaseSerializer):
labels = validated_data.pop("labels", None)
project_id = self.context["project_id"]
owned_by_id = self.context["owned_by_id"]
- description = self.context["description"]
+ description_json = self.context["description_json"]
description_binary = self.context["description_binary"]
description_html = self.context["description_html"]
@@ -68,7 +72,7 @@ class PageSerializer(BaseSerializer):
# Create the page
page = Page.objects.create(
**validated_data,
- description=description,
+ description_json=description_json,
description_binary=description_binary,
description_html=description_html,
owned_by_id=owned_by_id,
@@ -171,7 +175,7 @@ class PageBinaryUpdateSerializer(serializers.Serializer):
description_binary = serializers.CharField(required=False, allow_blank=True)
description_html = serializers.CharField(required=False, allow_blank=True)
- description = serializers.JSONField(required=False, allow_null=True)
+ description_json = serializers.JSONField(required=False, allow_null=True)
def validate_description_binary(self, value):
"""Validate the base64-encoded binary data"""
@@ -214,8 +218,8 @@ class PageBinaryUpdateSerializer(serializers.Serializer):
if "description_html" in validated_data:
instance.description_html = validated_data.get("description_html")
- if "description" in validated_data:
- instance.description = validated_data.get("description")
+ if "description_json" in validated_data:
+ instance.description_json = validated_data.get("description_json")
instance.save()
return instance
diff --git a/apps/api/plane/app/serializers/project.py b/apps/api/plane/app/serializers/project.py
index b8a4136c96..924c48fcfa 100644
--- a/apps/api/plane/app/serializers/project.py
+++ b/apps/api/plane/app/serializers/project.py
@@ -1,6 +1,13 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
+# Python imports
+import re
+
# Module imports
from .base import BaseSerializer, DynamicBaseSerializer
from django.db.models import Max
@@ -33,6 +40,9 @@ class ProjectSerializer(BaseSerializer):
project_id = self.instance.id if self.instance else None
workspace_id = self.context["workspace_id"]
+ if re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, name):
+ raise serializers.ValidationError(detail="PROJECT_NAME_CANNOT_CONTAIN_SPECIAL_CHARACTERS")
+
project = Project.objects.filter(name=name, workspace_id=workspace_id)
if project_id:
@@ -49,6 +59,9 @@ class ProjectSerializer(BaseSerializer):
project_id = self.instance.id if self.instance else None
workspace_id = self.context["workspace_id"]
+ if re.match(Project.FORBIDDEN_IDENTIFIER_CHARS_PATTERN, identifier):
+ raise serializers.ValidationError(detail="PROJECT_IDENTIFIER_CANNOT_CONTAIN_SPECIAL_CHARACTERS")
+
project = Project.objects.filter(identifier=identifier, workspace_id=workspace_id)
if project_id:
diff --git a/apps/api/plane/app/serializers/state.py b/apps/api/plane/app/serializers/state.py
index cb56cfbe99..0e333a80b7 100644
--- a/apps/api/plane/app/serializers/state.py
+++ b/apps/api/plane/app/serializers/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/user.py b/apps/api/plane/app/serializers/user.py
index 5910178573..aeef4ee28f 100644
--- a/apps/api/plane/app/serializers/user.py
+++ b/apps/api/plane/app/serializers/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/view.py b/apps/api/plane/app/serializers/view.py
index bf7ff9727c..72f72ff71b 100644
--- a/apps/api/plane/app/serializers/view.py
+++ b/apps/api/plane/app/serializers/view.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/app/serializers/webhook.py b/apps/api/plane/app/serializers/webhook.py
index ef193e24da..74ebde8920 100644
--- a/apps/api/plane/app/serializers/webhook.py
+++ b/apps/api/plane/app/serializers/webhook.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import socket
import ipaddress
@@ -34,7 +38,7 @@ class WebhookSerializer(DynamicBaseSerializer):
for addr in ip_addresses:
ip = ipaddress.ip_address(addr[4][0])
- if ip.is_loopback:
+ if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
# Additional validation for multiple request domains and their subdomains
@@ -69,7 +73,7 @@ class WebhookSerializer(DynamicBaseSerializer):
for addr in ip_addresses:
ip = ipaddress.ip_address(addr[4][0])
- if ip.is_loopback:
+ if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
raise serializers.ValidationError({"url": "URL resolves to a blocked IP address."})
# Additional validation for multiple request domains and their subdomains
diff --git a/apps/api/plane/app/serializers/workspace.py b/apps/api/plane/app/serializers/workspace.py
index ba59f2429c..608cdad851 100644
--- a/apps/api/plane/app/serializers/workspace.py
+++ b/apps/api/plane/app/serializers/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
@@ -107,7 +111,7 @@ class WorkSpaceMemberInviteSerializer(BaseSerializer):
invite_link = serializers.SerializerMethodField()
def get_invite_link(self, obj):
- return f"/workspace-invitations/?invitation_id={obj.id}&email={obj.email}&slug={obj.workspace.slug}"
+ return f"/workspace-invitations/?invitation_id={obj.id}&slug={obj.workspace.slug}&token={obj.token}"
class Meta:
model = WorkspaceMemberInvite
diff --git a/apps/api/plane/app/urls/__init__.py b/apps/api/plane/app/urls/__init__.py
index 3feab4cb54..3fa850b6ab 100644
--- a/apps/api/plane/app/urls/__init__.py
+++ b/apps/api/plane/app/urls/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .analytic import urlpatterns as analytic_urls
from .api import urlpatterns as api_urls
from .asset import urlpatterns as asset_urls
diff --git a/apps/api/plane/app/urls/analytic.py b/apps/api/plane/app/urls/analytic.py
index df6ad24984..2b31941866 100644
--- a/apps/api/plane/app/urls/analytic.py
+++ b/apps/api/plane/app/urls/analytic.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/api.py b/apps/api/plane/app/urls/api.py
index c74aeddbf2..eedf18ccc3 100644
--- a/apps/api/plane/app/urls/api.py
+++ b/apps/api/plane/app/urls/api.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import ApiTokenEndpoint, ServiceApiTokenEndpoint
diff --git a/apps/api/plane/app/urls/asset.py b/apps/api/plane/app/urls/asset.py
index 4b7e2b220c..fd8d200738 100644
--- a/apps/api/plane/app/urls/asset.py
+++ b/apps/api/plane/app/urls/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/cycle.py b/apps/api/plane/app/urls/cycle.py
index f188d08724..2560a3edb5 100644
--- a/apps/api/plane/app/urls/cycle.py
+++ b/apps/api/plane/app/urls/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/estimate.py b/apps/api/plane/app/urls/estimate.py
index c77a5b6b6a..4378164f16 100644
--- a/apps/api/plane/app/urls/estimate.py
+++ b/apps/api/plane/app/urls/estimate.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/exporter.py b/apps/api/plane/app/urls/exporter.py
index 56c6bfb51a..c7acf53431 100644
--- a/apps/api/plane/app/urls/exporter.py
+++ b/apps/api/plane/app/urls/exporter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import ExportIssuesEndpoint
diff --git a/apps/api/plane/app/urls/external.py b/apps/api/plane/app/urls/external.py
index 4972962d87..1255ac08a3 100644
--- a/apps/api/plane/app/urls/external.py
+++ b/apps/api/plane/app/urls/external.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/intake.py b/apps/api/plane/app/urls/intake.py
index dd1efc872a..970e763d6b 100644
--- a/apps/api/plane/app/urls/intake.py
+++ b/apps/api/plane/app/urls/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/issue.py b/apps/api/plane/app/urls/issue.py
index 1d809e248f..436d227705 100644
--- a/apps/api/plane/app/urls/issue.py
+++ b/apps/api/plane/app/urls/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import (
@@ -14,7 +18,7 @@ from plane.app.views import (
IssueReactionViewSet,
IssueRelationViewSet,
IssueSubscriberViewSet,
- IssueUserDisplayPropertyEndpoint,
+ ProjectUserDisplayPropertyEndpoint,
IssueViewSet,
LabelViewSet,
BulkArchiveIssuesEndpoint,
@@ -208,13 +212,13 @@ urlpatterns = [
name="project-issue-comment-reactions",
),
## End Comment Reactions
- ## IssueUserProperty
+ ## ProjectUserProperty
path(
"workspaces//projects//user-properties/",
- IssueUserDisplayPropertyEndpoint.as_view(),
+ ProjectUserDisplayPropertyEndpoint.as_view(),
name="project-issue-display-properties",
),
- ## IssueUserProperty End
+ ## ProjectUserProperty End
## Issue Archives
path(
"workspaces//projects//archived-issues/",
diff --git a/apps/api/plane/app/urls/module.py b/apps/api/plane/app/urls/module.py
index 75cbb14d6b..255f8211cc 100644
--- a/apps/api/plane/app/urls/module.py
+++ b/apps/api/plane/app/urls/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/notification.py b/apps/api/plane/app/urls/notification.py
index 0c992d49e9..cd2b3c5a4e 100644
--- a/apps/api/plane/app/urls/notification.py
+++ b/apps/api/plane/app/urls/notification.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/page.py b/apps/api/plane/app/urls/page.py
index 8cac22a2fd..dd4395c18e 100644
--- a/apps/api/plane/app/urls/page.py
+++ b/apps/api/plane/app/urls/page.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/project.py b/apps/api/plane/app/urls/project.py
index a6dd8d8a8a..ee850af1d6 100644
--- a/apps/api/plane/app/urls/project.py
+++ b/apps/api/plane/app/urls/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import (
diff --git a/apps/api/plane/app/urls/search.py b/apps/api/plane/app/urls/search.py
index 0bbbd9cf7f..9d94aa2737 100644
--- a/apps/api/plane/app/urls/search.py
+++ b/apps/api/plane/app/urls/search.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/state.py b/apps/api/plane/app/urls/state.py
index b6135ca959..902b583cb5 100644
--- a/apps/api/plane/app/urls/state.py
+++ b/apps/api/plane/app/urls/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/timezone.py b/apps/api/plane/app/urls/timezone.py
index ff14d029f2..9fc17f79aa 100644
--- a/apps/api/plane/app/urls/timezone.py
+++ b/apps/api/plane/app/urls/timezone.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import TimezoneEndpoint
diff --git a/apps/api/plane/app/urls/user.py b/apps/api/plane/app/urls/user.py
index 373d4a70d3..bc110a28dd 100644
--- a/apps/api/plane/app/urls/user.py
+++ b/apps/api/plane/app/urls/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import (
diff --git a/apps/api/plane/app/urls/views.py b/apps/api/plane/app/urls/views.py
index 063e39c3db..f3e4ee1de5 100644
--- a/apps/api/plane/app/urls/views.py
+++ b/apps/api/plane/app/urls/views.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/urls/webhook.py b/apps/api/plane/app/urls/webhook.py
index e21ec72614..22ac4bc6f8 100644
--- a/apps/api/plane/app/urls/webhook.py
+++ b/apps/api/plane/app/urls/webhook.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.app.views import (
diff --git a/apps/api/plane/app/urls/workspace.py b/apps/api/plane/app/urls/workspace.py
index 5f781efa7a..d79d5a7452 100644
--- a/apps/api/plane/app/urls/workspace.py
+++ b/apps/api/plane/app/urls/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/app/views/__init__.py b/apps/api/plane/app/views/__init__.py
index 7a0e5cb3a2..baa6661b9c 100644
--- a/apps/api/plane/app/views/__init__.py
+++ b/apps/api/plane/app/views/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .project.base import (
ProjectViewSet,
ProjectIdentifierEndpoint,
@@ -114,7 +118,7 @@ from .asset.v2 import (
from .issue.base import (
IssueListEndpoint,
IssueViewSet,
- IssueUserDisplayPropertyEndpoint,
+ ProjectUserDisplayPropertyEndpoint,
BulkDeleteIssuesEndpoint,
DeletedIssuesListViewSet,
IssuePaginatedViewSet,
diff --git a/apps/api/plane/app/views/analytic/advance.py b/apps/api/plane/app/views/analytic/advance.py
index 1a5b1b34ce..5ba9a439b4 100644
--- a/apps/api/plane/app/views/analytic/advance.py
+++ b/apps/api/plane/app/views/analytic/advance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework.response import Response
from rest_framework import status
from typing import Dict, List, Any
diff --git a/apps/api/plane/app/views/analytic/base.py b/apps/api/plane/app/views/analytic/base.py
index 6e9311a185..2f3f8b5737 100644
--- a/apps/api/plane/app/views/analytic/base.py
+++ b/apps/api/plane/app/views/analytic/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Count, F, Sum, Q
from django.db.models.functions import ExtractMonth
diff --git a/apps/api/plane/app/views/analytic/project_analytics.py b/apps/api/plane/app/views/analytic/project_analytics.py
index 2529900b05..c8e896716b 100644
--- a/apps/api/plane/app/views/analytic/project_analytics.py
+++ b/apps/api/plane/app/views/analytic/project_analytics.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework.response import Response
from rest_framework import status
from typing import Dict, Any
diff --git a/apps/api/plane/app/views/api.py b/apps/api/plane/app/views/api.py
index 4198599023..f2abc1a2de 100644
--- a/apps/api/plane/app/views/api.py
+++ b/apps/api/plane/app/views/api.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python import
from uuid import uuid4
from typing import Optional
diff --git a/apps/api/plane/app/views/asset/base.py b/apps/api/plane/app/views/asset/base.py
index 522d4af751..5b55a76a61 100644
--- a/apps/api/plane/app/views/asset/base.py
+++ b/apps/api/plane/app/views/asset/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/asset/v2.py b/apps/api/plane/app/views/asset/v2.py
index 4313fe3fff..62c5f84a20 100644
--- a/apps/api/plane/app/views/asset/v2.py
+++ b/apps/api/plane/app/views/asset/v2.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
@@ -575,7 +579,7 @@ class ProjectAssetEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def patch(self, request, slug, project_id, pk):
# get the asset id
- asset = FileAsset.objects.get(id=pk)
+ asset = FileAsset.objects.get(id=pk, workspace__slug=slug, project_id=project_id)
# get the storage metadata
asset.is_uploaded = True
# get the storage metadata
diff --git a/apps/api/plane/app/views/base.py b/apps/api/plane/app/views/base.py
index 0323302c5a..db5469de58 100644
--- a/apps/api/plane/app/views/base.py
+++ b/apps/api/plane/app/views/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import traceback
diff --git a/apps/api/plane/app/views/cycle/archive.py b/apps/api/plane/app/views/cycle/archive.py
index a2f89d53f6..3738b33671 100644
--- a/apps/api/plane/app/views/cycle/archive.py
+++ b/apps/api/plane/app/views/cycle/archive.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
diff --git a/apps/api/plane/app/views/cycle/base.py b/apps/api/plane/app/views/cycle/base.py
index d61f1587b3..30a5732ce0 100644
--- a/apps/api/plane/app/views/cycle/base.py
+++ b/apps/api/plane/app/views/cycle/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import pytz
diff --git a/apps/api/plane/app/views/cycle/issue.py b/apps/api/plane/app/views/cycle/issue.py
index ad3923b17b..6099678457 100644
--- a/apps/api/plane/app/views/cycle/issue.py
+++ b/apps/api/plane/app/views/cycle/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import copy
import json
diff --git a/apps/api/plane/app/views/error_404.py b/apps/api/plane/app/views/error_404.py
index 97c3c59f7c..b7ec4dcf3a 100644
--- a/apps/api/plane/app/views/error_404.py
+++ b/apps/api/plane/app/views/error_404.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# views.py
from django.http import JsonResponse
diff --git a/apps/api/plane/app/views/estimate/base.py b/apps/api/plane/app/views/estimate/base.py
index f54115a4f2..4bdc86633d 100644
--- a/apps/api/plane/app/views/estimate/base.py
+++ b/apps/api/plane/app/views/estimate/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import random
import string
import json
diff --git a/apps/api/plane/app/views/exporter/base.py b/apps/api/plane/app/views/exporter/base.py
index 5f446ff949..64364ecf47 100644
--- a/apps/api/plane/app/views/exporter/base.py
+++ b/apps/api/plane/app/views/exporter/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/external/base.py b/apps/api/plane/app/views/external/base.py
index 2c554bbc86..013bad2dbf 100644
--- a/apps/api/plane/app/views/external/base.py
+++ b/apps/api/plane/app/views/external/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python import
import os
from typing import List, Dict, Tuple
diff --git a/apps/api/plane/app/views/intake/base.py b/apps/api/plane/app/views/intake/base.py
index 6af3a47974..100adf216d 100644
--- a/apps/api/plane/app/views/intake/base.py
+++ b/apps/api/plane/app/views/intake/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
@@ -394,7 +398,7 @@ class IntakeIssueViewSet(BaseViewSet):
issue_data = {
"name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html),
- "description": issue_data.get("description", issue.description),
+ "description_json": issue_data.get("description_json", issue.description_json),
}
issue_current_instance = json.dumps(IssueDetailSerializer(issue).data, cls=DjangoJSONEncoder)
diff --git a/apps/api/plane/app/views/issue/activity.py b/apps/api/plane/app/views/issue/activity.py
index fdfcd129ae..8f62956401 100644
--- a/apps/api/plane/app/views/issue/activity.py
+++ b/apps/api/plane/app/views/issue/activity.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from itertools import chain
diff --git a/apps/api/plane/app/views/issue/archive.py b/apps/api/plane/app/views/issue/archive.py
index b8f8589696..1ac808cf92 100644
--- a/apps/api/plane/app/views/issue/archive.py
+++ b/apps/api/plane/app/views/issue/archive.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import copy
import json
diff --git a/apps/api/plane/app/views/issue/attachment.py b/apps/api/plane/app/views/issue/attachment.py
index 7b7ecf378b..fa03ae5f1c 100644
--- a/apps/api/plane/app/views/issue/attachment.py
+++ b/apps/api/plane/app/views/issue/attachment.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import uuid
@@ -56,7 +60,11 @@ class IssueAttachmentEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN], creator=True, model=FileAsset)
def delete(self, request, slug, project_id, issue_id, pk):
- issue_attachment = FileAsset.objects.get(pk=pk)
+ issue_attachment = FileAsset.objects.filter(
+ pk=pk, workspace__slug=slug, project_id=project_id, issue_id=issue_id
+ ).first()
+ if not issue_attachment:
+ return Response(status=status.HTTP_404_NOT_FOUND)
issue_attachment.asset.delete(save=False)
issue_attachment.delete()
issue_activity.delay(
diff --git a/apps/api/plane/app/views/issue/base.py b/apps/api/plane/app/views/issue/base.py
index 7a5e7dddf6..98a59b6481 100644
--- a/apps/api/plane/app/views/issue/base.py
+++ b/apps/api/plane/app/views/issue/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import copy
import json
@@ -34,7 +38,7 @@ from plane.app.serializers import (
IssueDetailSerializer,
IssueListDetailSerializer,
IssueSerializer,
- IssueUserPropertySerializer,
+ ProjectUserPropertySerializer,
)
from plane.bgtasks.issue_activities_task import issue_activity
from plane.bgtasks.issue_description_version_task import issue_description_version_task
@@ -51,7 +55,7 @@ from plane.db.models import (
IssueReaction,
IssueRelation,
IssueSubscriber,
- IssueUserProperty,
+ ProjectUserProperty,
ModuleIssue,
Project,
ProjectMember,
@@ -723,23 +727,33 @@ class IssueViewSet(BaseViewSet):
return Response(status=status.HTTP_204_NO_CONTENT)
-class IssueUserDisplayPropertyEndpoint(BaseAPIView):
+class ProjectUserDisplayPropertyEndpoint(BaseAPIView):
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def patch(self, request, slug, project_id):
- issue_property = IssueUserProperty.objects.get(user=request.user, project_id=project_id)
+ try:
+ issue_property = ProjectUserProperty.objects.get(
+ user=request.user,
+ project_id=project_id
+ )
+ except ProjectUserProperty.DoesNotExist:
+ issue_property = ProjectUserProperty.objects.create(
+ user=request.user,
+ project_id=project_id
+ )
- issue_property.rich_filters = request.data.get("rich_filters", issue_property.rich_filters)
- issue_property.filters = request.data.get("filters", issue_property.filters)
- issue_property.display_filters = request.data.get("display_filters", issue_property.display_filters)
- issue_property.display_properties = request.data.get("display_properties", issue_property.display_properties)
- issue_property.save()
- serializer = IssueUserPropertySerializer(issue_property)
- return Response(serializer.data, status=status.HTTP_201_CREATED)
+ serializer = ProjectUserPropertySerializer(
+ issue_property,
+ data=request.data,
+ partial=True
+ )
+ serializer.is_valid(raise_exception=True)
+ serializer.save()
+ return Response(serializer.data, status=status.HTTP_200_OK)
@allow_permission([ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST])
def get(self, request, slug, project_id):
- issue_property, _ = IssueUserProperty.objects.get_or_create(user=request.user, project_id=project_id)
- serializer = IssueUserPropertySerializer(issue_property)
+ issue_property, _ = ProjectUserProperty.objects.get_or_create(user=request.user, project_id=project_id)
+ serializer = ProjectUserPropertySerializer(issue_property)
return Response(serializer.data, status=status.HTTP_200_OK)
diff --git a/apps/api/plane/app/views/issue/comment.py b/apps/api/plane/app/views/issue/comment.py
index 72a986fea5..34fe0f9e4b 100644
--- a/apps/api/plane/app/views/issue/comment.py
+++ b/apps/api/plane/app/views/issue/comment.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/issue/label.py b/apps/api/plane/app/views/issue/label.py
index 6e46b5abb0..05033593e6 100644
--- a/apps/api/plane/app/views/issue/label.py
+++ b/apps/api/plane/app/views/issue/label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import random
diff --git a/apps/api/plane/app/views/issue/link.py b/apps/api/plane/app/views/issue/link.py
index ee209f9fae..5490212302 100644
--- a/apps/api/plane/app/views/issue/link.py
+++ b/apps/api/plane/app/views/issue/link.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/issue/reaction.py b/apps/api/plane/app/views/issue/reaction.py
index fe8a63b135..c09e1e9244 100644
--- a/apps/api/plane/app/views/issue/reaction.py
+++ b/apps/api/plane/app/views/issue/reaction.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/issue/relation.py b/apps/api/plane/app/views/issue/relation.py
index 0dfd686d29..e91ddffec8 100644
--- a/apps/api/plane/app/views/issue/relation.py
+++ b/apps/api/plane/app/views/issue/relation.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/issue/sub_issue.py b/apps/api/plane/app/views/issue/sub_issue.py
index 2fa244dcfb..b52e07564f 100644
--- a/apps/api/plane/app/views/issue/sub_issue.py
+++ b/apps/api/plane/app/views/issue/sub_issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/issue/subscriber.py b/apps/api/plane/app/views/issue/subscriber.py
index 58f3ba4c7e..c9a1a29b65 100644
--- a/apps/api/plane/app/views/issue/subscriber.py
+++ b/apps/api/plane/app/views/issue/subscriber.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/app/views/issue/version.py b/apps/api/plane/app/views/issue/version.py
index 358271ac87..540c7d6d5c 100644
--- a/apps/api/plane/app/views/issue/version.py
+++ b/apps/api/plane/app/views/issue/version.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/module/archive.py b/apps/api/plane/app/views/module/archive.py
index 129ff0dac4..1f234d7915 100644
--- a/apps/api/plane/app/views/module/archive.py
+++ b/apps/api/plane/app/views/module/archive.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
from django.db.models import (
diff --git a/apps/api/plane/app/views/module/base.py b/apps/api/plane/app/views/module/base.py
index ae429e7504..97e683f750 100644
--- a/apps/api/plane/app/views/module/base.py
+++ b/apps/api/plane/app/views/module/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/module/issue.py b/apps/api/plane/app/views/module/issue.py
index 672bf4e1ae..4707d683a7 100644
--- a/apps/api/plane/app/views/module/issue.py
+++ b/apps/api/plane/app/views/module/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import copy
import json
diff --git a/apps/api/plane/app/views/notification/base.py b/apps/api/plane/app/views/notification/base.py
index a11c12d167..0b7dc27a8a 100644
--- a/apps/api/plane/app/views/notification/base.py
+++ b/apps/api/plane/app/views/notification/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Exists, OuterRef, Q, Case, When, BooleanField
from django.utils import timezone
diff --git a/apps/api/plane/app/views/page/base.py b/apps/api/plane/app/views/page/base.py
index d3ad49b5fd..ec391afc1a 100644
--- a/apps/api/plane/app/views/page/base.py
+++ b/apps/api/plane/app/views/page/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
from datetime import datetime
@@ -46,7 +50,7 @@ from plane.utils.error_codes import ERROR_CODES
# Local imports
from ..base import BaseAPIView, BaseViewSet
from plane.bgtasks.page_transaction_task import page_transaction
-from plane.bgtasks.page_version_task import page_version
+from plane.bgtasks.page_version_task import track_page_version
from plane.bgtasks.recent_visited_task import recent_visited_task
from plane.bgtasks.copy_s3_object import copy_s3_objects_of_description_and_assets
from plane.app.permissions import ProjectPagePermission
@@ -128,7 +132,7 @@ class PageViewSet(BaseViewSet):
context={
"project_id": project_id,
"owned_by_id": request.user.id,
- "description": request.data.get("description", {}),
+ "description_json": request.data.get("description_json", {}),
"description_binary": request.data.get("description_binary", None),
"description_html": request.data.get("description_html", "
"),
},
@@ -541,26 +545,28 @@ class PagesDescriptionViewSet(BaseViewSet):
status=status.HTTP_400_BAD_REQUEST,
)
+ # Store the old description_html before saving (needed for both tasks)
+ old_description_html = page.description_html
+
# Serialize the existing instance
- existing_instance = json.dumps({"description_html": page.description_html}, cls=DjangoJSONEncoder)
+ existing_instance = json.dumps({"description_html": old_description_html}, cls=DjangoJSONEncoder)
# Use serializer for validation and update
serializer = PageBinaryUpdateSerializer(page, data=request.data, partial=True)
if serializer.is_valid():
+ serializer.save()
+
# Capture the page transaction
if request.data.get("description_html"):
page_transaction.delay(
new_description_html=request.data.get("description_html", "
"),
- old_description_html=page.description_html,
+ old_description_html=old_description_html,
page_id=page_id,
)
- # Update the page using serializer
- updated_page = serializer.save()
-
# Run background tasks
- page_version.delay(
- page_id=updated_page.id,
+ track_page_version.delay(
+ page_id=page_id,
existing_instance=existing_instance,
user_id=request.user.id,
)
diff --git a/apps/api/plane/app/views/page/version.py b/apps/api/plane/app/views/page/version.py
index 1b285c9661..e102bf1d0b 100644
--- a/apps/api/plane/app/views/page/version.py
+++ b/apps/api/plane/app/views/page/version.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/project/base.py b/apps/api/plane/app/views/project/base.py
index 34e13d472d..0a7378c076 100644
--- a/apps/api/plane/app/views/project/base.py
+++ b/apps/api/plane/app/views/project/base.py
@@ -1,10 +1,14 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
# Django imports
from django.core.serializers.json import DjangoJSONEncoder
-from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery
+from django.db.models import Exists, F, OuterRef, Prefetch, Q, Subquery, Count
from django.utils import timezone
# Third Party imports
@@ -25,16 +29,17 @@ from plane.db.models import (
UserFavorite,
DeployBoard,
Intake,
- IssueUserProperty,
Project,
ProjectIdentifier,
ProjectMember,
ProjectNetwork,
+ ProjectUserProperty,
State,
DEFAULT_STATES,
Workspace,
WorkspaceMember,
)
+from plane.db.models.intake import IntakeIssueStatus
from plane.utils.host import base_host
@@ -45,11 +50,10 @@ class ProjectViewSet(BaseViewSet):
use_read_replica = True
def get_queryset(self):
- sort_order = ProjectMember.objects.filter(
- member=self.request.user,
+ sort_order = ProjectUserProperty.objects.filter(
+ user=self.request.user,
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
- is_active=True,
).values("sort_order")
return self.filter_queryset(
super()
@@ -135,11 +139,10 @@ class ProjectViewSet(BaseViewSet):
@allow_permission(allowed_roles=[ROLE.ADMIN, ROLE.MEMBER, ROLE.GUEST], level="WORKSPACE")
def list(self, request, slug):
- sort_order = ProjectMember.objects.filter(
- member=self.request.user,
+ sort_order = ProjectUserProperty.objects.filter(
+ user=self.request.user,
project_id=OuterRef("pk"),
workspace__slug=self.kwargs.get("slug"),
- is_active=True,
).values("sort_order")
projects = (
@@ -152,6 +155,15 @@ class ProjectViewSet(BaseViewSet):
is_active=True,
).values("role")
)
+ .annotate(
+ intake_count=Count(
+ "project_intakeissue",
+ filter=Q(
+ project_intakeissue__status=IntakeIssueStatus.PENDING.value,
+ project_intakeissue__deleted_at__isnull=True,
+ ),
+ )
+ )
.annotate(inbox_view=F("intake_view"))
.annotate(sort_order=Subquery(sort_order))
.distinct()
@@ -162,6 +174,7 @@ class ProjectViewSet(BaseViewSet):
"sort_order",
"logo_props",
"member_role",
+ "intake_count",
"archived_at",
"workspace",
"cycle_view",
@@ -250,8 +263,6 @@ class ProjectViewSet(BaseViewSet):
member=request.user,
role=ROLE.ADMIN.value,
)
- # Also create the issue property for the user
- _ = IssueUserProperty.objects.create(project_id=serializer.data["id"], user=request.user)
if serializer.data["project_lead"] is not None and str(serializer.data["project_lead"]) != str(
request.user.id
@@ -261,11 +272,6 @@ class ProjectViewSet(BaseViewSet):
member_id=serializer.data["project_lead"],
role=ROLE.ADMIN.value,
)
- # Also create the issue property for the user
- IssueUserProperty.objects.create(
- project_id=serializer.data["id"],
- user_id=serializer.data["project_lead"],
- )
State.objects.bulk_create(
[
diff --git a/apps/api/plane/app/views/project/invite.py b/apps/api/plane/app/views/project/invite.py
index cc5b3f4b57..19d8c36bcf 100644
--- a/apps/api/plane/app/views/project/invite.py
+++ b/apps/api/plane/app/views/project/invite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import jwt
from datetime import datetime
@@ -24,7 +28,7 @@ from plane.db.models import (
User,
WorkspaceMember,
Project,
- IssueUserProperty,
+ ProjectUserProperty,
)
from plane.db.models.project import ProjectNetwork
from plane.utils.host import base_host
@@ -160,9 +164,9 @@ class UserProjectInvitationsViewset(BaseViewSet):
ignore_conflicts=True,
)
- IssueUserProperty.objects.bulk_create(
+ ProjectUserProperty.objects.bulk_create(
[
- IssueUserProperty(
+ ProjectUserProperty(
project_id=project_id,
user=request.user,
workspace=workspace,
@@ -220,7 +224,7 @@ class ProjectJoinEndpoint(BaseAPIView):
if project_member is None:
# Create a Project Member
_ = ProjectMember.objects.create(
- workspace_id=project_invite.workspace_id,
+ project_id=project_id,
member=user,
role=project_invite.role,
)
diff --git a/apps/api/plane/app/views/project/member.py b/apps/api/plane/app/views/project/member.py
index 3ab7061e15..1ad7639fb8 100644
--- a/apps/api/plane/app/views/project/member.py
+++ b/apps/api/plane/app/views/project/member.py
@@ -1,6 +1,11 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
+from django.db.models import Min
# Module imports
from .base import BaseViewSet, BaseAPIView
@@ -13,7 +18,7 @@ from plane.app.serializers import (
from plane.app.permissions import WorkspaceUserPermission
-from plane.db.models import Project, ProjectMember, IssueUserProperty, WorkspaceMember
+from plane.db.models import Project, ProjectMember, ProjectUserProperty, WorkspaceMember
from plane.bgtasks.project_add_user_email_task import project_add_user_email
from plane.utils.host import base_host
from plane.app.permissions.base import allow_permission, ROLE
@@ -89,24 +94,23 @@ class ProjectMemberViewSet(BaseViewSet):
# Update the roles of the existing members
ProjectMember.objects.bulk_update(bulk_project_members, ["is_active", "role"], batch_size=100)
- # Get the list of project members of the requested workspace with the given slug
- project_members = (
- ProjectMember.objects.filter(
+ # Get the minimum sort_order for each member in the workspace
+ member_sort_orders = (
+ ProjectUserProperty.objects.filter(
workspace__slug=slug,
- member_id__in=[member.get("member_id") for member in members],
+ user_id__in=[member.get("member_id") for member in members],
)
- .values("member_id", "sort_order")
- .order_by("sort_order")
+ .values("user_id")
+ .annotate(min_sort_order=Min("sort_order"))
)
+ # Convert to dictionary for easy lookup: {user_id: min_sort_order}
+ sort_order_map = {str(item["user_id"]): item["min_sort_order"] for item in member_sort_orders}
# Loop through requested members
for member in members:
- # Get the sort orders of the member
- sort_order = [
- project_member.get("sort_order")
- for project_member in project_members
- if str(project_member.get("member_id")) == str(member.get("member_id"))
- ]
+ member_id = str(member.get("member_id"))
+ # Get the minimum sort_order for this member, or use default
+ min_sort_order = sort_order_map.get(member_id)
# Create a new project member
bulk_project_members.append(
ProjectMember(
@@ -114,22 +118,22 @@ class ProjectMemberViewSet(BaseViewSet):
role=member.get("role", 5),
project_id=project_id,
workspace_id=project.workspace_id,
- sort_order=(sort_order[0] - 10000 if len(sort_order) else 65535),
)
)
# Create a new issue property
bulk_issue_props.append(
- IssueUserProperty(
+ ProjectUserProperty(
user_id=member.get("member_id"),
project_id=project_id,
workspace_id=project.workspace_id,
+ sort_order=(min_sort_order - 10000 if min_sort_order is not None else 65535),
)
)
# Bulk create the project members and issue properties
project_members = ProjectMember.objects.bulk_create(bulk_project_members, batch_size=10, ignore_conflicts=True)
- _ = IssueUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True)
+ _ = ProjectUserProperty.objects.bulk_create(bulk_issue_props, batch_size=10, ignore_conflicts=True)
project_members = ProjectMember.objects.filter(
project_id=project_id,
diff --git a/apps/api/plane/app/views/search/base.py b/apps/api/plane/app/views/search/base.py
index f1e6926532..3bfbecaaff 100644
--- a/apps/api/plane/app/views/search/base.py
+++ b/apps/api/plane/app/views/search/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import re
diff --git a/apps/api/plane/app/views/search/issue.py b/apps/api/plane/app/views/search/issue.py
index ac9d783a9f..737fe64100 100644
--- a/apps/api/plane/app/views/search/issue.py
+++ b/apps/api/plane/app/views/search/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Q, QuerySet
diff --git a/apps/api/plane/app/views/state/base.py b/apps/api/plane/app/views/state/base.py
index ce1597a685..55c232fdf6 100644
--- a/apps/api/plane/app/views/state/base.py
+++ b/apps/api/plane/app/views/state/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from itertools import groupby
from collections import defaultdict
diff --git a/apps/api/plane/app/views/timezone/base.py b/apps/api/plane/app/views/timezone/base.py
index fc01211792..9644ceee37 100644
--- a/apps/api/plane/app/views/timezone/base.py
+++ b/apps/api/plane/app/views/timezone/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import pytz
from datetime import datetime
diff --git a/apps/api/plane/app/views/user/base.py b/apps/api/plane/app/views/user/base.py
index 72d42010ce..914dffb3b0 100644
--- a/apps/api/plane/app/views/user/base.py
+++ b/apps/api/plane/app/views/user/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
import json
diff --git a/apps/api/plane/app/views/view/base.py b/apps/api/plane/app/views/view/base.py
index 98fe04c62f..5ca7aac420 100644
--- a/apps/api/plane/app/views/view/base.py
+++ b/apps/api/plane/app/views/view/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import copy
# Django imports
diff --git a/apps/api/plane/app/views/webhook/base.py b/apps/api/plane/app/views/webhook/base.py
index e857c3e08f..c874f0a422 100644
--- a/apps/api/plane/app/views/webhook/base.py
+++ b/apps/api/plane/app/views/webhook/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import IntegrityError
diff --git a/apps/api/plane/app/views/workspace/base.py b/apps/api/plane/app/views/workspace/base.py
index 12caee5061..be43eace2f 100644
--- a/apps/api/plane/app/views/workspace/base.py
+++ b/apps/api/plane/app/views/workspace/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import csv
import io
@@ -45,6 +49,7 @@ from plane.bgtasks.workspace_seed_task import workspace_seed
from plane.bgtasks.event_tracking_task import track_event
from plane.utils.url import contains_url
from plane.utils.analytics_events import WORKSPACE_CREATED, WORKSPACE_DELETED
+from plane.utils.csv_utils import sanitize_csv_row
class WorkSpaceViewSet(BaseViewSet):
@@ -77,12 +82,14 @@ class WorkSpaceViewSet(BaseViewSet):
def create(self, request):
try:
- (DISABLE_WORKSPACE_CREATION,) = get_configuration_value([
- {
- "key": "DISABLE_WORKSPACE_CREATION",
- "default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
- }
- ])
+ (DISABLE_WORKSPACE_CREATION,) = get_configuration_value(
+ [
+ {
+ "key": "DISABLE_WORKSPACE_CREATION",
+ "default": os.environ.get("DISABLE_WORKSPACE_CREATION", "0"),
+ }
+ ]
+ )
if DISABLE_WORKSPACE_CREATION == "1":
return Response(
@@ -365,7 +372,7 @@ class ExportWorkspaceUserActivityEndpoint(BaseAPIView):
"""Generate CSV buffer from rows."""
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
- [writer.writerow(row) for row in rows]
+ [writer.writerow(sanitize_csv_row(row)) for row in rows]
csv_buffer.seek(0)
return csv_buffer
diff --git a/apps/api/plane/app/views/workspace/cycle.py b/apps/api/plane/app/views/workspace/cycle.py
index 73deca0594..deb86c5c4d 100644
--- a/apps/api/plane/app/views/workspace/cycle.py
+++ b/apps/api/plane/app/views/workspace/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Q, Count
diff --git a/apps/api/plane/app/views/workspace/draft.py b/apps/api/plane/app/views/workspace/draft.py
index c89fe4a730..aa228ded37 100644
--- a/apps/api/plane/app/views/workspace/draft.py
+++ b/apps/api/plane/app/views/workspace/draft.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/app/views/workspace/estimate.py b/apps/api/plane/app/views/workspace/estimate.py
index 8cba3d1700..7f5bb66f67 100644
--- a/apps/api/plane/app/views/workspace/estimate.py
+++ b/apps/api/plane/app/views/workspace/estimate.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party modules
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/favorite.py b/apps/api/plane/app/views/workspace/favorite.py
index 8a8bfed6c9..8217f0fb02 100644
--- a/apps/api/plane/app/views/workspace/favorite.py
+++ b/apps/api/plane/app/views/workspace/favorite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party modules
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/home.py b/apps/api/plane/app/views/workspace/home.py
index 731164eb1c..ec35aaf4ec 100644
--- a/apps/api/plane/app/views/workspace/home.py
+++ b/apps/api/plane/app/views/workspace/home.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from ..base import BaseAPIView
from plane.db.models.workspace import WorkspaceHomePreference
diff --git a/apps/api/plane/app/views/workspace/invite.py b/apps/api/plane/app/views/workspace/invite.py
index 0983309f12..cf2ab795a7 100644
--- a/apps/api/plane/app/views/workspace/invite.py
+++ b/apps/api/plane/app/views/workspace/invite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from datetime import datetime
@@ -159,10 +163,10 @@ class WorkspaceJoinEndpoint(BaseAPIView):
def post(self, request, slug, pk):
workspace_invite = WorkspaceMemberInvite.objects.get(pk=pk, workspace__slug=slug)
- email = request.data.get("email", "")
+ token = request.data.get("token", "")
- # Check the email
- if email == "" or workspace_invite.email != email:
+ # Validate the token to verify the user received the invitation email
+ if not token or workspace_invite.token != token:
return Response(
{"error": "You do not have permission to join the workspace"},
status=status.HTTP_403_FORBIDDEN,
@@ -176,7 +180,7 @@ class WorkspaceJoinEndpoint(BaseAPIView):
if workspace_invite.accepted:
# Check if the user created account after invitation
- user = User.objects.filter(email=email).first()
+ user = User.objects.filter(email=workspace_invite.email).first()
# If the user is present then create the workspace member
if user is not None:
diff --git a/apps/api/plane/app/views/workspace/label.py b/apps/api/plane/app/views/workspace/label.py
index 11ca6b9139..926a504a34 100644
--- a/apps/api/plane/app/views/workspace/label.py
+++ b/apps/api/plane/app/views/workspace/label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party modules
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/member.py b/apps/api/plane/app/views/workspace/member.py
index 3394cb253f..67c7637a8c 100644
--- a/apps/api/plane/app/views/workspace/member.py
+++ b/apps/api/plane/app/views/workspace/member.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Count, Q, OuterRef, Subquery, IntegerField
from django.utils import timezone
diff --git a/apps/api/plane/app/views/workspace/module.py b/apps/api/plane/app/views/workspace/module.py
index e61fc70e73..b048481402 100644
--- a/apps/api/plane/app/views/workspace/module.py
+++ b/apps/api/plane/app/views/workspace/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Prefetch, Q, Count
@@ -42,7 +46,7 @@ class WorkspaceModulesEndpoint(BaseAPIView):
)
.annotate(
completed_issues=Count(
- "issue_module__issue__state__group",
+ "issue_module",
filter=Q(
issue_module__issue__state__group="completed",
issue_module__issue__archived_at__isnull=True,
@@ -54,7 +58,7 @@ class WorkspaceModulesEndpoint(BaseAPIView):
)
.annotate(
cancelled_issues=Count(
- "issue_module__issue__state__group",
+ "issue_module",
filter=Q(
issue_module__issue__state__group="cancelled",
issue_module__issue__archived_at__isnull=True,
@@ -66,7 +70,7 @@ class WorkspaceModulesEndpoint(BaseAPIView):
)
.annotate(
started_issues=Count(
- "issue_module__issue__state__group",
+ "issue_module",
filter=Q(
issue_module__issue__state__group="started",
issue_module__issue__archived_at__isnull=True,
@@ -78,7 +82,7 @@ class WorkspaceModulesEndpoint(BaseAPIView):
)
.annotate(
unstarted_issues=Count(
- "issue_module__issue__state__group",
+ "issue_module",
filter=Q(
issue_module__issue__state__group="unstarted",
issue_module__issue__archived_at__isnull=True,
@@ -90,7 +94,7 @@ class WorkspaceModulesEndpoint(BaseAPIView):
)
.annotate(
backlog_issues=Count(
- "issue_module__issue__state__group",
+ "issue_module",
filter=Q(
issue_module__issue__state__group="backlog",
issue_module__issue__archived_at__isnull=True,
diff --git a/apps/api/plane/app/views/workspace/quick_link.py b/apps/api/plane/app/views/workspace/quick_link.py
index 82c104573d..ba971c54f9 100644
--- a/apps/api/plane/app/views/workspace/quick_link.py
+++ b/apps/api/plane/app/views/workspace/quick_link.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/recent_visit.py b/apps/api/plane/app/views/workspace/recent_visit.py
index 0d9c1ef9bb..a9394766a2 100644
--- a/apps/api/plane/app/views/workspace/recent_visit.py
+++ b/apps/api/plane/app/views/workspace/recent_visit.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/state.py b/apps/api/plane/app/views/workspace/state.py
index 3bfc8d22de..a8c5b368d9 100644
--- a/apps/api/plane/app/views/workspace/state.py
+++ b/apps/api/plane/app/views/workspace/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party modules
from rest_framework import status
from rest_framework.response import Response
diff --git a/apps/api/plane/app/views/workspace/sticky.py b/apps/api/plane/app/views/workspace/sticky.py
index 8ab6c5c982..9cf1532257 100644
--- a/apps/api/plane/app/views/workspace/sticky.py
+++ b/apps/api/plane/app/views/workspace/sticky.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/app/views/workspace/user.py b/apps/api/plane/app/views/workspace/user.py
index b45c6e410b..b60ae5e15e 100644
--- a/apps/api/plane/app/views/workspace/user.py
+++ b/apps/api/plane/app/views/workspace/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import copy
from datetime import date
diff --git a/apps/api/plane/app/views/workspace/user_preference.py b/apps/api/plane/app/views/workspace/user_preference.py
index 253f715b36..83e380b9ec 100644
--- a/apps/api/plane/app/views/workspace/user_preference.py
+++ b/apps/api/plane/app/views/workspace/user_preference.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from ..base import BaseAPIView
from plane.db.models.workspace import WorkspaceUserPreference
diff --git a/apps/api/plane/asgi.py b/apps/api/plane/asgi.py
index 2dd703ffef..9d3bd6b07a 100644
--- a/apps/api/plane/asgi.py
+++ b/apps/api/plane/asgi.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import os
from channels.routing import ProtocolTypeRouter
diff --git a/apps/api/plane/authentication/__init__.py b/apps/api/plane/authentication/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/__init__.py
+++ b/apps/api/plane/authentication/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/adapter/__init__.py b/apps/api/plane/authentication/adapter/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/adapter/__init__.py
+++ b/apps/api/plane/authentication/adapter/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/adapter/base.py b/apps/api/plane/authentication/adapter/base.py
index baae95453b..b80dfa5002 100644
--- a/apps/api/plane/authentication/adapter/base.py
+++ b/apps/api/plane/authentication/adapter/base.py
@@ -1,27 +1,35 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
+import logging
import os
import uuid
-import requests
from io import BytesIO
+import requests
+from django.conf import settings
+from django.core.exceptions import ValidationError
+from django.core.validators import validate_email
+
# Django imports
from django.utils import timezone
-from django.core.validators import validate_email
-from django.core.exceptions import ValidationError
-from django.conf import settings
# Third party imports
from zxcvbn import zxcvbn
-# Module imports
-from plane.db.models import Profile, User, WorkspaceMemberInvite, FileAsset
-from plane.license.utils.instance_value import get_configuration_value
-from .error import AuthenticationException, AUTHENTICATION_ERROR_CODES
from plane.bgtasks.user_activation_email_task import user_activation_email
+
+# Module imports
+from plane.db.models import FileAsset, Profile, User, WorkspaceMemberInvite
+from plane.license.utils.instance_value import get_configuration_value
+from plane.settings.storage import S3Storage
+from plane.utils.exception_logger import log_exception
from plane.utils.host import base_host
from plane.utils.ip_address import get_client_ip
-from plane.utils.exception_logger import log_exception
-from plane.settings.storage import S3Storage
+
+from .error import AUTHENTICATION_ERROR_CODES, AuthenticationException
class Adapter:
@@ -33,6 +41,7 @@ class Adapter:
self.callback = callback
self.token_data = None
self.user_data = None
+ self.logger = logging.getLogger("plane.authentication")
def get_user_token(self, data, headers=None):
raise NotImplementedError
@@ -55,6 +64,7 @@ class Adapter:
def sanitize_email(self, email):
# Check if email is present
if not email:
+ self.logger.error("Email is not present")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
error_message="INVALID_EMAIL",
@@ -68,6 +78,7 @@ class Adapter:
try:
validate_email(email)
except ValidationError:
+ self.logger.warning(f"Email is not valid: {email}")
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["INVALID_EMAIL"],
error_message="INVALID_EMAIL",
@@ -80,9 +91,10 @@ class Adapter:
"""Validate password strength"""
results = zxcvbn(self.code)
if results["score"] < 3:
+ self.logger.warning("Password is not strong enough")
raise AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"],
- error_message="INVALID_PASSWORD",
+ error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"],
+ error_message="PASSWORD_TOO_WEAK",
payload={"email": email},
)
return
@@ -97,6 +109,7 @@ class Adapter:
# Check if sign up is disabled and invite is present or not
if ENABLE_SIGNUP == "0" and not WorkspaceMemberInvite.objects.filter(email=email).exists():
+ self.logger.warning("Sign up is disabled and invite is not present")
# Raise exception
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["SIGNUP_DISABLED"],
diff --git a/apps/api/plane/authentication/adapter/credential.py b/apps/api/plane/authentication/adapter/credential.py
index 0327289ca2..eee6ea97f6 100644
--- a/apps/api/plane/authentication/adapter/credential.py
+++ b/apps/api/plane/authentication/adapter/credential.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.authentication.adapter.base import Adapter
diff --git a/apps/api/plane/authentication/adapter/error.py b/apps/api/plane/authentication/adapter/error.py
index 25a7cf5671..f91565df2e 100644
--- a/apps/api/plane/authentication/adapter/error.py
+++ b/apps/api/plane/authentication/adapter/error.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
AUTHENTICATION_ERROR_CODES = {
# Global
"INSTANCE_NOT_CONFIGURED": 5000,
@@ -9,6 +13,7 @@ AUTHENTICATION_ERROR_CODES = {
"USER_ACCOUNT_DEACTIVATED": 5019,
# Password strength
"INVALID_PASSWORD": 5020,
+ "PASSWORD_TOO_WEAK": 5021,
"SMTP_NOT_CONFIGURED": 5025,
# Sign Up
"USER_ALREADY_EXIST": 5030,
diff --git a/apps/api/plane/authentication/adapter/exception.py b/apps/api/plane/authentication/adapter/exception.py
index e906c5a50b..c8d28762a9 100644
--- a/apps/api/plane/authentication/adapter/exception.py
+++ b/apps/api/plane/authentication/adapter/exception.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.views import exception_handler
from rest_framework.exceptions import NotAuthenticated
diff --git a/apps/api/plane/authentication/adapter/oauth.py b/apps/api/plane/authentication/adapter/oauth.py
index d8e423d0e7..0bef76b248 100644
--- a/apps/api/plane/authentication/adapter/oauth.py
+++ b/apps/api/plane/authentication/adapter/oauth.py
@@ -1,19 +1,24 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import requests
+from django.db import DatabaseError, IntegrityError
# Django imports
from django.utils import timezone
-from django.db import DatabaseError, IntegrityError
+
+from plane.authentication.adapter.error import (
+ AUTHENTICATION_ERROR_CODES,
+ AuthenticationException,
+)
# Module imports
from plane.db.models import Account
+from plane.utils.exception_logger import log_exception
from .base import Adapter
-from plane.authentication.adapter.error import (
- AuthenticationException,
- AUTHENTICATION_ERROR_CODES,
-)
-from plane.utils.exception_logger import log_exception
class OauthAdapter(Adapter):
@@ -74,6 +79,7 @@ class OauthAdapter(Adapter):
response.raise_for_status()
return response.json()
except requests.RequestException:
+ self.logger.warning("Error getting user token")
code = self.authentication_error_code()
raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code))
@@ -84,6 +90,12 @@ class OauthAdapter(Adapter):
response.raise_for_status()
return response.json()
except requests.RequestException:
+ self.logger.warning(
+ "Error getting user response",
+ extra={
+ "headers": headers,
+ },
+ )
code = self.authentication_error_code()
raise AuthenticationException(error_code=AUTHENTICATION_ERROR_CODES[code], error_message=str(code))
diff --git a/apps/api/plane/authentication/apps.py b/apps/api/plane/authentication/apps.py
index cf5cdca1c9..5a612eaa98 100644
--- a/apps/api/plane/authentication/apps.py
+++ b/apps/api/plane/authentication/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/authentication/middleware/__init__.py b/apps/api/plane/authentication/middleware/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/middleware/__init__.py
+++ b/apps/api/plane/authentication/middleware/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/middleware/session.py b/apps/api/plane/authentication/middleware/session.py
index c367a15d36..817f898fd4 100644
--- a/apps/api/plane/authentication/middleware/session.py
+++ b/apps/api/plane/authentication/middleware/session.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import time
from importlib import import_module
diff --git a/apps/api/plane/authentication/provider/__init__.py b/apps/api/plane/authentication/provider/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/provider/__init__.py
+++ b/apps/api/plane/authentication/provider/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/provider/credentials/__init__.py b/apps/api/plane/authentication/provider/credentials/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/provider/credentials/__init__.py
+++ b/apps/api/plane/authentication/provider/credentials/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/provider/credentials/email.py b/apps/api/plane/authentication/provider/credentials/email.py
index c3d19a80e7..e2c424588a 100644
--- a/apps/api/plane/authentication/provider/credentials/email.py
+++ b/apps/api/plane/authentication/provider/credentials/email.py
@@ -1,13 +1,17 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
# Module imports
from plane.authentication.adapter.credential import CredentialAdapter
-from plane.db.models import User
from plane.authentication.adapter.error import (
AUTHENTICATION_ERROR_CODES,
AuthenticationException,
)
+from plane.db.models import User
from plane.license.utils.instance_value import get_configuration_value
@@ -20,14 +24,12 @@ class EmailProvider(CredentialAdapter):
self.code = code
self.is_signup = is_signup
- (ENABLE_EMAIL_PASSWORD,) = get_configuration_value(
- [
- {
- "key": "ENABLE_EMAIL_PASSWORD",
- "default": os.environ.get("ENABLE_EMAIL_PASSWORD"),
- }
- ]
- )
+ (ENABLE_EMAIL_PASSWORD,) = get_configuration_value([
+ {
+ "key": "ENABLE_EMAIL_PASSWORD",
+ "default": os.environ.get("ENABLE_EMAIL_PASSWORD"),
+ }
+ ])
if ENABLE_EMAIL_PASSWORD == "0":
raise AuthenticationException(
@@ -39,29 +41,29 @@ class EmailProvider(CredentialAdapter):
if self.is_signup:
# Check if the user already exists
if User.objects.filter(email=self.key).exists():
+ self.logger.warning("User already exists")
raise AuthenticationException(
error_message="USER_ALREADY_EXIST",
error_code=AUTHENTICATION_ERROR_CODES["USER_ALREADY_EXIST"],
)
- super().set_user_data(
- {
- "email": self.key,
- "user": {
- "avatar": "",
- "first_name": "",
- "last_name": "",
- "provider_id": "",
- "is_password_autoset": False,
- },
- }
- )
+ super().set_user_data({
+ "email": self.key,
+ "user": {
+ "avatar": "",
+ "first_name": "",
+ "last_name": "",
+ "provider_id": "",
+ "is_password_autoset": False,
+ },
+ })
return
else:
user = User.objects.filter(email=self.key).first()
# User does not exists
if not user:
+ self.logger.warning("User does not exist")
raise AuthenticationException(
error_message="USER_DOES_NOT_EXIST",
error_code=AUTHENTICATION_ERROR_CODES["USER_DOES_NOT_EXIST"],
@@ -70,6 +72,7 @@ class EmailProvider(CredentialAdapter):
# Check user password
if not user.check_password(self.code):
+ self.logger.warning("Authentication failed - invalid credentials")
raise AuthenticationException(
error_message=(
"AUTHENTICATION_FAILED_SIGN_UP" if self.is_signup else "AUTHENTICATION_FAILED_SIGN_IN"
@@ -80,16 +83,14 @@ class EmailProvider(CredentialAdapter):
payload={"email": self.key},
)
- super().set_user_data(
- {
- "email": self.key,
- "user": {
- "avatar": "",
- "first_name": "",
- "last_name": "",
- "provider_id": "",
- "is_password_autoset": False,
- },
- }
- )
+ super().set_user_data({
+ "email": self.key,
+ "user": {
+ "avatar": "",
+ "first_name": "",
+ "last_name": "",
+ "provider_id": "",
+ "is_password_autoset": False,
+ },
+ })
return
diff --git a/apps/api/plane/authentication/provider/credentials/magic_code.py b/apps/api/plane/authentication/provider/credentials/magic_code.py
index e7c5cfff95..a6c9883d6b 100644
--- a/apps/api/plane/authentication/provider/credentials/magic_code.py
+++ b/apps/api/plane/authentication/provider/credentials/magic_code.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import os
diff --git a/apps/api/plane/authentication/provider/oauth/__init__.py b/apps/api/plane/authentication/provider/oauth/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/authentication/provider/oauth/__init__.py
+++ b/apps/api/plane/authentication/provider/oauth/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/authentication/provider/oauth/gitea.py b/apps/api/plane/authentication/provider/oauth/gitea.py
index 4d8de8e1af..8c0c3a5db5 100644
--- a/apps/api/plane/authentication/provider/oauth/gitea.py
+++ b/apps/api/plane/authentication/provider/oauth/gitea.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import os
from datetime import datetime, timedelta
from urllib.parse import urlencode, urlparse
diff --git a/apps/api/plane/authentication/provider/oauth/github.py b/apps/api/plane/authentication/provider/oauth/github.py
index 54c48018ef..363cd722e5 100644
--- a/apps/api/plane/authentication/provider/oauth/github.py
+++ b/apps/api/plane/authentication/provider/oauth/github.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from datetime import datetime
@@ -6,13 +10,14 @@ from urllib.parse import urlencode
import pytz
import requests
+from plane.authentication.adapter.error import (
+ AUTHENTICATION_ERROR_CODES,
+ AuthenticationException,
+)
+
# Module imports
from plane.authentication.adapter.oauth import OauthAdapter
from plane.license.utils.instance_value import get_configuration_value
-from plane.authentication.adapter.error import (
- AuthenticationException,
- AUTHENTICATION_ERROR_CODES,
-)
class GitHubOAuthProvider(OauthAdapter):
@@ -26,22 +31,20 @@ class GitHubOAuthProvider(OauthAdapter):
organization_scope = "read:org"
def __init__(self, request, code=None, state=None, callback=None):
- GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_ORGANIZATION_ID = get_configuration_value(
- [
- {
- "key": "GITHUB_CLIENT_ID",
- "default": os.environ.get("GITHUB_CLIENT_ID"),
- },
- {
- "key": "GITHUB_CLIENT_SECRET",
- "default": os.environ.get("GITHUB_CLIENT_SECRET"),
- },
- {
- "key": "GITHUB_ORGANIZATION_ID",
- "default": os.environ.get("GITHUB_ORGANIZATION_ID"),
- },
- ]
- )
+ GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, GITHUB_ORGANIZATION_ID = get_configuration_value([
+ {
+ "key": "GITHUB_CLIENT_ID",
+ "default": os.environ.get("GITHUB_CLIENT_ID"),
+ },
+ {
+ "key": "GITHUB_CLIENT_SECRET",
+ "default": os.environ.get("GITHUB_CLIENT_SECRET"),
+ },
+ {
+ "key": "GITHUB_ORGANIZATION_ID",
+ "default": os.environ.get("GITHUB_ORGANIZATION_ID"),
+ },
+ ])
if not (GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET):
raise AuthenticationException(
@@ -86,32 +89,46 @@ class GitHubOAuthProvider(OauthAdapter):
"redirect_uri": self.redirect_uri,
}
token_response = self.get_user_token(data=data, headers={"Accept": "application/json"})
- super().set_token_data(
- {
- "access_token": token_response.get("access_token"),
- "refresh_token": token_response.get("refresh_token", None),
- "access_token_expired_at": (
- datetime.fromtimestamp(token_response.get("expires_in"), tz=pytz.utc)
- if token_response.get("expires_in")
- else None
- ),
- "refresh_token_expired_at": (
- datetime.fromtimestamp(token_response.get("refresh_token_expired_at"), tz=pytz.utc)
- if token_response.get("refresh_token_expired_at")
- else None
- ),
- "id_token": token_response.get("id_token", ""),
- }
- )
+ super().set_token_data({
+ "access_token": token_response.get("access_token"),
+ "refresh_token": token_response.get("refresh_token", None),
+ "access_token_expired_at": (
+ datetime.fromtimestamp(token_response.get("expires_in"), tz=pytz.utc)
+ if token_response.get("expires_in")
+ else None
+ ),
+ "refresh_token_expired_at": (
+ datetime.fromtimestamp(token_response.get("refresh_token_expired_at"), tz=pytz.utc)
+ if token_response.get("refresh_token_expired_at")
+ else None
+ ),
+ "id_token": token_response.get("id_token", ""),
+ })
def __get_email(self, headers):
try:
# Github does not provide email in user response
emails_url = "https://api.github.com/user/emails"
emails_response = requests.get(emails_url, headers=headers).json()
+ # Ensure the response is a list before iterating
+ if not isinstance(emails_response, list):
+ self.logger.error("Unexpected response format from GitHub emails API")
+ raise AuthenticationException(
+ error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
+ error_message="GITHUB_OAUTH_PROVIDER_ERROR",
+ )
email = next((email["email"] for email in emails_response if email["primary"]), None)
+ if not email:
+ self.logger.error("No primary email found for user")
+ raise AuthenticationException(
+ error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
+ error_message="GITHUB_OAUTH_PROVIDER_ERROR",
+ )
return email
except requests.RequestException:
+ self.logger.warning(
+ "Error getting email from GitHub",
+ )
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_OAUTH_PROVIDER_ERROR"],
error_message="GITHUB_OAUTH_PROVIDER_ERROR",
@@ -134,22 +151,33 @@ class GitHubOAuthProvider(OauthAdapter):
if self.organization_id:
if not self.is_user_in_organization(user_info_response.get("login")):
+ self.logger.warning(
+ "User is not in organization",
+ extra={
+ "organization_id": self.organization_id,
+ "user_login": user_info_response.get("login"),
+ },
+ )
raise AuthenticationException(
error_code=AUTHENTICATION_ERROR_CODES["GITHUB_USER_NOT_IN_ORG"],
error_message="GITHUB_USER_NOT_IN_ORG",
)
email = self.__get_email(headers=headers)
- super().set_user_data(
- {
+ self.logger.debug(
+ "Email found",
+ extra={
"email": email,
- "user": {
- "provider_id": user_info_response.get("id"),
- "email": email,
- "avatar": user_info_response.get("avatar_url"),
- "first_name": user_info_response.get("name"),
- "last_name": user_info_response.get("family_name"),
- "is_password_autoset": True,
- },
- }
+ },
)
+ super().set_user_data({
+ "email": email,
+ "user": {
+ "provider_id": user_info_response.get("id"),
+ "email": email,
+ "avatar": user_info_response.get("avatar_url"),
+ "first_name": user_info_response.get("name"),
+ "last_name": user_info_response.get("family_name"),
+ "is_password_autoset": True,
+ },
+ })
diff --git a/apps/api/plane/authentication/provider/oauth/gitlab.py b/apps/api/plane/authentication/provider/oauth/gitlab.py
index de4a3515ef..088987c237 100644
--- a/apps/api/plane/authentication/provider/oauth/gitlab.py
+++ b/apps/api/plane/authentication/provider/oauth/gitlab.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from datetime import datetime
diff --git a/apps/api/plane/authentication/provider/oauth/google.py b/apps/api/plane/authentication/provider/oauth/google.py
index 41293782f8..b02eda87de 100644
--- a/apps/api/plane/authentication/provider/oauth/google.py
+++ b/apps/api/plane/authentication/provider/oauth/google.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from datetime import datetime
diff --git a/apps/api/plane/authentication/rate_limit.py b/apps/api/plane/authentication/rate_limit.py
index d245d50b37..f939ef25cd 100644
--- a/apps/api/plane/authentication/rate_limit.py
+++ b/apps/api/plane/authentication/rate_limit.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.throttling import AnonRateThrottle, UserRateThrottle
from rest_framework import status
diff --git a/apps/api/plane/authentication/session.py b/apps/api/plane/authentication/session.py
index 862a63c130..fe2aa0c35c 100644
--- a/apps/api/plane/authentication/session.py
+++ b/apps/api/plane/authentication/session.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework.authentication import SessionAuthentication
diff --git a/apps/api/plane/authentication/urls.py b/apps/api/plane/authentication/urls.py
index 64b8e654c9..4bec07db00 100644
--- a/apps/api/plane/authentication/urls.py
+++ b/apps/api/plane/authentication/urls.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from .views import (
diff --git a/apps/api/plane/authentication/utils/host.py b/apps/api/plane/authentication/utils/host.py
index 415791a879..d79d54e8a8 100644
--- a/apps/api/plane/authentication/utils/host.py
+++ b/apps/api/plane/authentication/utils/host.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.http import HttpRequest
diff --git a/apps/api/plane/authentication/utils/login.py b/apps/api/plane/authentication/utils/login.py
index fe6fdad931..d573335511 100644
--- a/apps/api/plane/authentication/utils/login.py
+++ b/apps/api/plane/authentication/utils/login.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.contrib.auth import login
from django.conf import settings
diff --git a/apps/api/plane/authentication/utils/redirection_path.py b/apps/api/plane/authentication/utils/redirection_path.py
index 82139b8213..59d4b7d50a 100644
--- a/apps/api/plane/authentication/utils/redirection_path.py
+++ b/apps/api/plane/authentication/utils/redirection_path.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.db.models import Profile, Workspace, WorkspaceMemberInvite
diff --git a/apps/api/plane/authentication/utils/user_auth_workflow.py b/apps/api/plane/authentication/utils/user_auth_workflow.py
index 13de4c2874..4641f332c5 100644
--- a/apps/api/plane/authentication/utils/user_auth_workflow.py
+++ b/apps/api/plane/authentication/utils/user_auth_workflow.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .workspace_project_join import process_workspace_project_invitations
diff --git a/apps/api/plane/authentication/utils/workspace_project_join.py b/apps/api/plane/authentication/utils/workspace_project_join.py
index 31dae55eb7..9222791a84 100644
--- a/apps/api/plane/authentication/utils/workspace_project_join.py
+++ b/apps/api/plane/authentication/utils/workspace_project_join.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
diff --git a/apps/api/plane/authentication/views/__init__.py b/apps/api/plane/authentication/views/__init__.py
index 2595d2e756..a9c816ae9e 100644
--- a/apps/api/plane/authentication/views/__init__.py
+++ b/apps/api/plane/authentication/views/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .common import ChangePasswordEndpoint, CSRFTokenEndpoint, SetUserPasswordEndpoint
from .app.check import EmailCheckEndpoint
diff --git a/apps/api/plane/authentication/views/app/check.py b/apps/api/plane/authentication/views/app/check.py
index 10457b45a0..97ab24def1 100644
--- a/apps/api/plane/authentication/views/app/check.py
+++ b/apps/api/plane/authentication/views/app/check.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/authentication/views/app/email.py b/apps/api/plane/authentication/views/app/email.py
index 864ff102bc..3d1954875c 100644
--- a/apps/api/plane/authentication/views/app/email.py
+++ b/apps/api/plane/authentication/views/app/email.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
diff --git a/apps/api/plane/authentication/views/app/gitea.py b/apps/api/plane/authentication/views/app/gitea.py
index e43a35c3c8..67d25e1ab3 100644
--- a/apps/api/plane/authentication/views/app/gitea.py
+++ b/apps/api/plane/authentication/views/app/gitea.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import uuid
from urllib.parse import urlencode, urljoin
diff --git a/apps/api/plane/authentication/views/app/github.py b/apps/api/plane/authentication/views/app/github.py
index 4720fc7daa..82d5f4a053 100644
--- a/apps/api/plane/authentication/views/app/github.py
+++ b/apps/api/plane/authentication/views/app/github.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/app/gitlab.py b/apps/api/plane/authentication/views/app/gitlab.py
index 665af00c19..5b0435250d 100644
--- a/apps/api/plane/authentication/views/app/gitlab.py
+++ b/apps/api/plane/authentication/views/app/gitlab.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/app/google.py b/apps/api/plane/authentication/views/app/google.py
index 0ee81c768c..3dad1385a8 100644
--- a/apps/api/plane/authentication/views/app/google.py
+++ b/apps/api/plane/authentication/views/app/google.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/app/magic.py b/apps/api/plane/authentication/views/app/magic.py
index 518a5cdea4..9104311a62 100644
--- a/apps/api/plane/authentication/views/app/magic.py
+++ b/apps/api/plane/authentication/views/app/magic.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.validators import validate_email
from django.http import HttpResponseRedirect
diff --git a/apps/api/plane/authentication/views/app/password_management.py b/apps/api/plane/authentication/views/app/password_management.py
index de0baa71b5..48b54dcccb 100644
--- a/apps/api/plane/authentication/views/app/password_management.py
+++ b/apps/api/plane/authentication/views/app/password_management.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from urllib.parse import urlencode, urljoin
@@ -141,8 +145,8 @@ class ResetPasswordEndpoint(View):
results = zxcvbn(password)
if results["score"] < 3:
exc = AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"],
- error_message="INVALID_PASSWORD",
+ error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"],
+ error_message="PASSWORD_TOO_WEAK",
)
url = urljoin(
base_host(request=request, is_app=True),
diff --git a/apps/api/plane/authentication/views/app/signout.py b/apps/api/plane/authentication/views/app/signout.py
index b8019dac18..9941da3c9f 100644
--- a/apps/api/plane/authentication/views/app/signout.py
+++ b/apps/api/plane/authentication/views/app/signout.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.views import View
from django.contrib.auth import logout
diff --git a/apps/api/plane/authentication/views/common.py b/apps/api/plane/authentication/views/common.py
index c5dd1714c5..086d6b0d3e 100644
--- a/apps/api/plane/authentication/views/common.py
+++ b/apps/api/plane/authentication/views/common.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.shortcuts import render
@@ -79,8 +83,8 @@ class ChangePasswordEndpoint(APIView):
results = zxcvbn(new_password)
if results["score"] < 3:
exc = AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["INVALID_NEW_PASSWORD"],
- error_message="INVALID_NEW_PASSWORD",
+ error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"],
+ error_message="PASSWORD_TOO_WEAK",
)
return Response(exc.get_error_dict(), status=status.HTTP_400_BAD_REQUEST)
diff --git a/apps/api/plane/authentication/views/space/check.py b/apps/api/plane/authentication/views/space/check.py
index 95a5e68dfa..371fadf366 100644
--- a/apps/api/plane/authentication/views/space/check.py
+++ b/apps/api/plane/authentication/views/space/check.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/authentication/views/space/email.py b/apps/api/plane/authentication/views/space/email.py
index 3d092591ad..827348cef2 100644
--- a/apps/api/plane/authentication/views/space/email.py
+++ b/apps/api/plane/authentication/views/space/email.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.exceptions import ValidationError
from django.core.validators import validate_email
diff --git a/apps/api/plane/authentication/views/space/gitea.py b/apps/api/plane/authentication/views/space/gitea.py
index 497a1ecc09..04c21678fe 100644
--- a/apps/api/plane/authentication/views/space/gitea.py
+++ b/apps/api/plane/authentication/views/space/gitea.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
from urllib.parse import urlencode
diff --git a/apps/api/plane/authentication/views/space/github.py b/apps/api/plane/authentication/views/space/github.py
index f12498d3b0..1df6a8c619 100644
--- a/apps/api/plane/authentication/views/space/github.py
+++ b/apps/api/plane/authentication/views/space/github.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/space/gitlab.py b/apps/api/plane/authentication/views/space/gitlab.py
index 498916b344..19c057a069 100644
--- a/apps/api/plane/authentication/views/space/gitlab.py
+++ b/apps/api/plane/authentication/views/space/gitlab.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/space/google.py b/apps/api/plane/authentication/views/space/google.py
index 0f02c1f93e..daa1b48a6f 100644
--- a/apps/api/plane/authentication/views/space/google.py
+++ b/apps/api/plane/authentication/views/space/google.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/authentication/views/space/magic.py b/apps/api/plane/authentication/views/space/magic.py
index df940b3275..37683d9acf 100644
--- a/apps/api/plane/authentication/views/space/magic.py
+++ b/apps/api/plane/authentication/views/space/magic.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.validators import validate_email
from django.http import HttpResponseRedirect
diff --git a/apps/api/plane/authentication/views/space/password_management.py b/apps/api/plane/authentication/views/space/password_management.py
index 12cc88f63e..ed6682d74a 100644
--- a/apps/api/plane/authentication/views/space/password_management.py
+++ b/apps/api/plane/authentication/views/space/password_management.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from urllib.parse import urlencode
@@ -135,8 +139,8 @@ class ResetPasswordSpaceEndpoint(View):
results = zxcvbn(password)
if results["score"] < 3:
exc = AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["INVALID_PASSWORD"],
- error_message="INVALID_PASSWORD",
+ error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"],
+ error_message="PASSWORD_TOO_WEAK",
)
url = f"{base_host(request=request, is_space=True)}/accounts/reset-password/?{urlencode(exc.get_error_dict())}" # noqa: E501
return HttpResponseRedirect(url)
diff --git a/apps/api/plane/authentication/views/space/signout.py b/apps/api/plane/authentication/views/space/signout.py
index aa890f978d..164c6409bc 100644
--- a/apps/api/plane/authentication/views/space/signout.py
+++ b/apps/api/plane/authentication/views/space/signout.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.views import View
from django.contrib.auth import logout
diff --git a/apps/api/plane/bgtasks/__init__.py b/apps/api/plane/bgtasks/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/bgtasks/__init__.py
+++ b/apps/api/plane/bgtasks/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/bgtasks/analytic_plot_export.py b/apps/api/plane/bgtasks/analytic_plot_export.py
index 845fb50dd2..4b0983138b 100644
--- a/apps/api/plane/bgtasks/analytic_plot_export.py
+++ b/apps/api/plane/bgtasks/analytic_plot_export.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import csv
import io
@@ -9,7 +13,6 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
from django.db.models import Q, Case, Value, When
from django.db import models
from django.db.models.functions import Concat
@@ -18,8 +21,10 @@ from django.db.models.functions import Concat
from plane.db.models import Issue
from plane.license.utils.instance_value import get_email_configuration
from plane.utils.analytics_plot import build_graph_plot
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.utils.issue_filters import issue_filters
+from plane.utils.csv_utils import sanitize_csv_row
row_mapping = {
"state__name": "State",
@@ -48,7 +53,7 @@ def send_export_email(email, slug, csv_buffer, rows):
"""Helper function to send export email."""
subject = "Your Export is ready"
html_content = render_to_string("emails/exports/analytics.html", {})
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
csv_buffer.seek(0)
@@ -176,7 +181,7 @@ def generate_csv_from_rows(rows):
"""Generate CSV buffer from rows."""
csv_buffer = io.StringIO()
writer = csv.writer(csv_buffer, delimiter=",", quoting=csv.QUOTE_ALL)
- [writer.writerow(row) for row in rows]
+ [writer.writerow(sanitize_csv_row(row)) for row in rows]
return csv_buffer
diff --git a/apps/api/plane/bgtasks/apps.py b/apps/api/plane/bgtasks/apps.py
index 7f6ca38f0c..e5fb0aa547 100644
--- a/apps/api/plane/bgtasks/apps.py
+++ b/apps/api/plane/bgtasks/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/bgtasks/cleanup_task.py b/apps/api/plane/bgtasks/cleanup_task.py
index 6b23f2571d..407a67ca69 100644
--- a/apps/api/plane/bgtasks/cleanup_task.py
+++ b/apps/api/plane/bgtasks/cleanup_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from datetime import timedelta
import logging
diff --git a/apps/api/plane/bgtasks/copy_s3_object.py b/apps/api/plane/bgtasks/copy_s3_object.py
index e7ef09e353..742966a6fb 100644
--- a/apps/api/plane/bgtasks/copy_s3_object.py
+++ b/apps/api/plane/bgtasks/copy_s3_object.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
import base64
@@ -141,7 +145,7 @@ def copy_s3_objects_of_description_and_assets(entity_name, entity_identifier, pr
external_data = sync_with_external_service(entity_name, updated_html)
if external_data:
- entity.description = external_data.get("description")
+ entity.description_json = external_data.get("description_json")
entity.description_binary = base64.b64decode(external_data.get("description_binary"))
entity.save()
diff --git a/apps/api/plane/bgtasks/deletion_task.py b/apps/api/plane/bgtasks/deletion_task.py
index 932a1fce06..11d9041606 100644
--- a/apps/api/plane/bgtasks/deletion_task.py
+++ b/apps/api/plane/bgtasks/deletion_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
from django.apps import apps
diff --git a/apps/api/plane/bgtasks/dummy_data_task.py b/apps/api/plane/bgtasks/dummy_data_task.py
index 390bc160b5..6740495d83 100644
--- a/apps/api/plane/bgtasks/dummy_data_task.py
+++ b/apps/api/plane/bgtasks/dummy_data_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
import random
diff --git a/apps/api/plane/bgtasks/email_notification_task.py b/apps/api/plane/bgtasks/email_notification_task.py
index 1402adc41f..5cf1d52af9 100644
--- a/apps/api/plane/bgtasks/email_notification_task.py
+++ b/apps/api/plane/bgtasks/email_notification_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import logging
import re
from datetime import datetime
@@ -11,12 +15,12 @@ from django.template.loader import render_to_string
# Django imports
from django.utils import timezone
-from django.utils.html import strip_tags
# Module imports
from plane.db.models import EmailNotificationLog, Issue, User
from plane.license.utils.instance_value import get_email_configuration
from plane.settings.redis import redis_instance
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -256,7 +260,7 @@ def send_email_notification(issue_id, notification_data, receiver_id, email_noti
"entity_type": "issue",
}
html_content = render_to_string("emails/notifications/issue-updates.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
try:
connection = get_connection(
diff --git a/apps/api/plane/bgtasks/event_tracking_task.py b/apps/api/plane/bgtasks/event_tracking_task.py
index 82857fdfe1..e8f453e9ff 100644
--- a/apps/api/plane/bgtasks/event_tracking_task.py
+++ b/apps/api/plane/bgtasks/event_tracking_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import logging
import os
import uuid
diff --git a/apps/api/plane/bgtasks/export_task.py b/apps/api/plane/bgtasks/export_task.py
index f77e379108..24486999d7 100644
--- a/apps/api/plane/bgtasks/export_task.py
+++ b/apps/api/plane/bgtasks/export_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import io
import zipfile
diff --git a/apps/api/plane/bgtasks/exporter_expired_task.py b/apps/api/plane/bgtasks/exporter_expired_task.py
index 30b638c84c..9ec2a0102a 100644
--- a/apps/api/plane/bgtasks/exporter_expired_task.py
+++ b/apps/api/plane/bgtasks/exporter_expired_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import boto3
from datetime import timedelta
diff --git a/apps/api/plane/bgtasks/file_asset_task.py b/apps/api/plane/bgtasks/file_asset_task.py
index d6eccf7357..e54a754c9a 100644
--- a/apps/api/plane/bgtasks/file_asset_task.py
+++ b/apps/api/plane/bgtasks/file_asset_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
from datetime import timedelta
diff --git a/apps/api/plane/bgtasks/forgot_password_task.py b/apps/api/plane/bgtasks/forgot_password_task.py
index ffaba9937f..9ca0548de2 100644
--- a/apps/api/plane/bgtasks/forgot_password_task.py
+++ b/apps/api/plane/bgtasks/forgot_password_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -8,10 +12,10 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -41,7 +45,7 @@ def forgot_password(first_name, email, uidb64, token, current_site):
html_content = render_to_string("emails/auth/forgot_password.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/issue_activities_task.py b/apps/api/plane/bgtasks/issue_activities_task.py
index a886305fd2..032feb02a6 100644
--- a/apps/api/plane/bgtasks/issue_activities_task.py
+++ b/apps/api/plane/bgtasks/issue_activities_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/bgtasks/issue_automation_task.py b/apps/api/plane/bgtasks/issue_automation_task.py
index 1cc303b575..83a2f72d18 100644
--- a/apps/api/plane/bgtasks/issue_automation_task.py
+++ b/apps/api/plane/bgtasks/issue_automation_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
from datetime import timedelta
diff --git a/apps/api/plane/bgtasks/issue_description_version_sync.py b/apps/api/plane/bgtasks/issue_description_version_sync.py
index d10ebfcbac..795d5e7efc 100644
--- a/apps/api/plane/bgtasks/issue_description_version_sync.py
+++ b/apps/api/plane/bgtasks/issue_description_version_sync.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from typing import Optional
import logging
@@ -59,7 +63,7 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300):
"description_binary",
"description_html",
"description_stripped",
- "description",
+ "description_json",
)[offset:end_offset]
)
@@ -92,7 +96,7 @@ def sync_issue_description_version(batch_size=5000, offset=0, countdown=300):
description_binary=issue.description_binary,
description_html=issue.description_html,
description_stripped=issue.description_stripped,
- description_json=issue.description,
+ description_json=issue.description_json,
)
)
diff --git a/apps/api/plane/bgtasks/issue_description_version_task.py b/apps/api/plane/bgtasks/issue_description_version_task.py
index 06d15705a7..49689e8150 100644
--- a/apps/api/plane/bgtasks/issue_description_version_task.py
+++ b/apps/api/plane/bgtasks/issue_description_version_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from celery import shared_task
from django.db import transaction
from django.utils import timezone
@@ -19,7 +23,7 @@ def should_update_existing_version(
def update_existing_version(version: IssueDescriptionVersion, issue) -> None:
- version.description_json = issue.description
+ version.description_json = issue.description_json
version.description_html = issue.description_html
version.description_binary = issue.description_binary
version.description_stripped = issue.description_stripped
diff --git a/apps/api/plane/bgtasks/issue_version_sync.py b/apps/api/plane/bgtasks/issue_version_sync.py
index 761c26bc2f..221a5a417a 100644
--- a/apps/api/plane/bgtasks/issue_version_sync.py
+++ b/apps/api/plane/bgtasks/issue_version_sync.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
from typing import Optional, List, Dict
diff --git a/apps/api/plane/bgtasks/logger_task.py b/apps/api/plane/bgtasks/logger_task.py
index 01723ef77a..4a74e54bc5 100644
--- a/apps/api/plane/bgtasks/logger_task.py
+++ b/apps/api/plane/bgtasks/logger_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
from typing import Optional, Dict, Any
diff --git a/apps/api/plane/bgtasks/magic_link_code_task.py b/apps/api/plane/bgtasks/magic_link_code_task.py
index d8267e6971..eef7adea03 100644
--- a/apps/api/plane/bgtasks/magic_link_code_task.py
+++ b/apps/api/plane/bgtasks/magic_link_code_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -8,10 +12,10 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -33,7 +37,7 @@ def magic_link(email, key, token):
context = {"code": token, "email": email}
html_content = render_to_string("emails/auth/magic_signin.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/notification_task.py b/apps/api/plane/bgtasks/notification_task.py
index 6e571c0b17..bfb72afa36 100644
--- a/apps/api/plane/bgtasks/notification_task.py
+++ b/apps/api/plane/bgtasks/notification_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import uuid
diff --git a/apps/api/plane/bgtasks/page_transaction_task.py b/apps/api/plane/bgtasks/page_transaction_task.py
index 9c0caccf06..8c2cfe7a0c 100644
--- a/apps/api/plane/bgtasks/page_transaction_task.py
+++ b/apps/api/plane/bgtasks/page_transaction_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
diff --git a/apps/api/plane/bgtasks/page_version_task.py b/apps/api/plane/bgtasks/page_version_task.py
index 4de2387bec..7b41e3c445 100644
--- a/apps/api/plane/bgtasks/page_version_task.py
+++ b/apps/api/plane/bgtasks/page_version_task.py
@@ -1,37 +1,73 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
+
# Third party imports
from celery import shared_task
+# Django imports
+from django.utils import timezone
+
# Module imports
from plane.db.models import Page, PageVersion
from plane.utils.exception_logger import log_exception
+PAGE_VERSION_TASK_TIMEOUT = 600
@shared_task
-def page_version(page_id, existing_instance, user_id):
+def track_page_version(page_id, existing_instance, user_id):
try:
# Get the page
page = Page.objects.get(id=page_id)
# Get the current instance
current_instance = json.loads(existing_instance) if existing_instance is not None else {}
+ sub_pages = {}
+
# Create a version if description_html is updated
if current_instance.get("description_html") != page.description_html:
- # Create a new page version
- PageVersion.objects.create(
- page_id=page_id,
- workspace_id=page.workspace_id,
- description_html=page.description_html,
- description_binary=page.description_binary,
- owned_by_id=user_id,
- last_saved_at=page.updated_at,
- description_json=page.description,
- description_stripped=page.description_stripped,
- )
+ # Fetch the latest page version
+ page_version = PageVersion.objects.filter(page_id=page_id).order_by("-last_saved_at").first()
+ # Get the latest page version if it exists and is owned by the user
+ if (
+ page_version
+ and str(page_version.owned_by_id) == str(user_id)
+ and (timezone.now() - page_version.last_saved_at).total_seconds() <= PAGE_VERSION_TASK_TIMEOUT
+ ):
+ page_version.description_html = page.description_html
+ page_version.description_binary = page.description_binary
+ page_version.description_json = page.description
+ page_version.description_stripped = page.description_stripped
+ page_version.sub_pages_data = sub_pages
+ page_version.save(
+ update_fields=[
+ "description_html",
+ "description_binary",
+ "description_json",
+ "description_stripped",
+ "sub_pages_data",
+ "updated_at"
+ ]
+ )
+ else:
+ # Create a new page version
+ PageVersion.objects.create(
+ page_id=page_id,
+ workspace_id=page.workspace_id,
+ description_json=page.description,
+ description_html=page.description_html,
+ description_binary=page.description_binary,
+ description_stripped=page.description_stripped,
+ owned_by_id=user_id,
+ last_saved_at=timezone.now(),
+ sub_pages_data=sub_pages,
+ )
# If page versions are greater than 20 delete the oldest one
if PageVersion.objects.filter(page_id=page_id).count() > 20:
# Delete the old page version
diff --git a/apps/api/plane/bgtasks/project_add_user_email_task.py b/apps/api/plane/bgtasks/project_add_user_email_task.py
index af6014695d..1efe6bc461 100644
--- a/apps/api/plane/bgtasks/project_add_user_email_task.py
+++ b/apps/api/plane/bgtasks/project_add_user_email_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -7,11 +11,11 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.db.models import ProjectMember
from plane.db.models import User
@@ -55,7 +59,7 @@ def project_add_user_email(current_site, project_member_id, invitor_id):
# Render the email template
html_content = render_to_string("emails/notifications/project_addition.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
# Initialize the connection
connection = get_connection(
host=EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/project_invitation_task.py b/apps/api/plane/bgtasks/project_invitation_task.py
index b8eed5e45a..86c10e90cb 100644
--- a/apps/api/plane/bgtasks/project_invitation_task.py
+++ b/apps/api/plane/bgtasks/project_invitation_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -8,11 +12,11 @@ from celery import shared_task
# Third party imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.db.models import Project, ProjectMemberInvite, User
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -33,11 +37,12 @@ def project_invitation(email, project_id, token, current_site, invitor):
"first_name": user.first_name,
"project_name": project.name,
"invitation_url": abs_url,
+ "current_site": current_site,
}
html_content = render_to_string("emails/invitations/project_invitation.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
project_member_invite.message = text_content
project_member_invite.save()
diff --git a/apps/api/plane/bgtasks/recent_visited_task.py b/apps/api/plane/bgtasks/recent_visited_task.py
index eda297ce45..3d4f9e6e9f 100644
--- a/apps/api/plane/bgtasks/recent_visited_task.py
+++ b/apps/api/plane/bgtasks/recent_visited_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from django.utils import timezone
from django.db import DatabaseError
diff --git a/apps/api/plane/bgtasks/storage_metadata_task.py b/apps/api/plane/bgtasks/storage_metadata_task.py
index ea745053f7..77f99e9165 100644
--- a/apps/api/plane/bgtasks/storage_metadata_task.py
+++ b/apps/api/plane/bgtasks/storage_metadata_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from celery import shared_task
diff --git a/apps/api/plane/bgtasks/user_activation_email_task.py b/apps/api/plane/bgtasks/user_activation_email_task.py
index 492564b3ce..f7a2d3999a 100644
--- a/apps/api/plane/bgtasks/user_activation_email_task.py
+++ b/apps/api/plane/bgtasks/user_activation_email_task.py
@@ -1,10 +1,13 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
@@ -12,6 +15,7 @@ from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -27,7 +31,7 @@ def user_activation_email(current_site, user_id):
# Send email to user
html_content = render_to_string("emails/user/user_activation.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
# Configure email connection from the database
(
EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/user_deactivation_email_task.py b/apps/api/plane/bgtasks/user_deactivation_email_task.py
index 2595d8055b..81419606a7 100644
--- a/apps/api/plane/bgtasks/user_deactivation_email_task.py
+++ b/apps/api/plane/bgtasks/user_deactivation_email_task.py
@@ -1,10 +1,13 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Third party imports
from celery import shared_task
@@ -12,6 +15,7 @@ from celery import shared_task
# Module imports
from plane.db.models import User
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -27,7 +31,7 @@ def user_deactivation_email(current_site, user_id):
# Send email to user
html_content = render_to_string("emails/user/user_deactivation.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
# Configure email connection from the database
(
EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/user_email_update_task.py b/apps/api/plane/bgtasks/user_email_update_task.py
index 667de368c7..48b9c02dba 100644
--- a/apps/api/plane/bgtasks/user_email_update_task.py
+++ b/apps/api/plane/bgtasks/user_email_update_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -7,10 +11,10 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -32,7 +36,7 @@ def send_email_update_magic_code(email, token):
context = {"code": token, "email": email}
html_content = render_to_string("emails/auth/magic_signin.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,
@@ -83,7 +87,7 @@ def send_email_update_confirmation(email):
context = {"email": email}
html_content = render_to_string("emails/user/email_updated.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
connection = get_connection(
host=EMAIL_HOST,
diff --git a/apps/api/plane/bgtasks/webhook_task.py b/apps/api/plane/bgtasks/webhook_task.py
index 3d04a65b71..6543c3845b 100644
--- a/apps/api/plane/bgtasks/webhook_task.py
+++ b/apps/api/plane/bgtasks/webhook_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import hashlib
import hmac
import json
@@ -16,7 +20,6 @@ from django.db.models import Prefetch
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.serializers.json import DjangoJSONEncoder
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
from django.core.exceptions import ObjectDoesNotExist
# Module imports
@@ -47,6 +50,7 @@ from plane.db.models import (
IssueAssignee,
)
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
from plane.settings.mongo import MongoConnection
@@ -218,7 +222,7 @@ def send_webhook_deactivation_email(webhook_id: str, receiver_id: str, current_s
"webhook_url": f"{current_site}/{str(webhook.workspace.slug)}/settings/webhooks/{str(webhook.id)}",
}
html_content = render_to_string("emails/notifications/webhook-deactivate.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
# Set the email connection
connection = get_connection(
diff --git a/apps/api/plane/bgtasks/work_item_link_task.py b/apps/api/plane/bgtasks/work_item_link_task.py
index e436c1e8f8..442396c7f0 100644
--- a/apps/api/plane/bgtasks/work_item_link_task.py
+++ b/apps/api/plane/bgtasks/work_item_link_task.py
@@ -1,5 +1,10 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
+import socket
# Third party imports
from celery import shared_task
@@ -22,7 +27,7 @@ DEFAULT_FAVICON = "PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoP
def validate_url_ip(url: str) -> None:
"""
Validate that a URL doesn't point to a private/internal IP address.
- Only checks if the hostname is a direct IP address.
+ Resolves hostnames to IPs before checking.
Args:
url: The URL to validate
@@ -34,17 +39,31 @@ def validate_url_ip(url: str) -> None:
hostname = parsed.hostname
if not hostname:
- return
+ raise ValueError("Invalid URL: No hostname found")
+
+ # Only allow HTTP and HTTPS to prevent file://, gopher://, etc.
+ if parsed.scheme not in ("http", "https"):
+ raise ValueError("Invalid URL scheme. Only HTTP and HTTPS are allowed")
+
+ # Resolve hostname to IP addresses — this catches domain names that
+ # point to internal IPs (e.g. attacker.com -> 169.254.169.254)
try:
- ip = ipaddress.ip_address(hostname)
- except ValueError:
- # Not an IP address (it's a domain name), nothing to check here
- return
+ addr_info = socket.getaddrinfo(hostname, None)
+ except socket.gaierror:
+ raise ValueError("Hostname could not be resolved")
- # It IS an IP address - check if it's private/internal
- if ip.is_private or ip.is_loopback or ip.is_reserved:
- raise ValueError("Access to private/internal networks is not allowed")
+ if not addr_info:
+ raise ValueError("No IP addresses found for the hostname")
+
+ # Check every resolved IP against blocked ranges to prevent SSRF
+ for addr in addr_info:
+ ip = ipaddress.ip_address(addr[4][0])
+ if ip.is_private or ip.is_loopback or ip.is_reserved or ip.is_link_local:
+ raise ValueError("Access to private/internal networks is not allowed")
+
+
+MAX_REDIRECTS = 5
def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
@@ -70,11 +89,23 @@ def crawl_work_item_link_title_and_favicon(url: str) -> Dict[str, Any]:
validate_url_ip(final_url)
try:
- response = requests.get(final_url, headers=headers, timeout=1)
- final_url = response.url # Get the final URL after any redirects
+ # Manually follow redirects to validate each URL before requesting
+ redirect_count = 0
+ response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False)
- # check for redirected url also
- validate_url_ip(final_url)
+ while response.is_redirect and redirect_count < MAX_REDIRECTS:
+ redirect_url = response.headers.get("Location")
+ if not redirect_url:
+ break
+ # Resolve relative redirects against current URL
+ final_url = urljoin(final_url, redirect_url)
+ # Validate the redirect target BEFORE making the request
+ validate_url_ip(final_url)
+ redirect_count += 1
+ response = requests.get(final_url, headers=headers, timeout=1, allow_redirects=False)
+
+ if redirect_count >= MAX_REDIRECTS:
+ logger.warning(f"Too many redirects for URL: {url}")
soup = BeautifulSoup(response.content, "html.parser")
title_tag = soup.find("title")
@@ -130,7 +161,9 @@ def find_favicon_url(soup: Optional[BeautifulSoup], base_url: str) -> Optional[s
for selector in favicon_selectors:
favicon_tag = soup.select_one(selector)
if favicon_tag and favicon_tag.get("href"):
- return urljoin(base_url, favicon_tag["href"])
+ favicon_href = urljoin(base_url, favicon_tag["href"])
+ validate_url_ip(favicon_href)
+ return favicon_href
# Fallback to /favicon.ico
parsed_url = urlparse(base_url)
@@ -138,7 +171,9 @@ def find_favicon_url(soup: Optional[BeautifulSoup], base_url: str) -> Optional[s
# Check if fallback exists
try:
- response = requests.head(fallback_url, timeout=2)
+ validate_url_ip(fallback_url)
+ response = requests.head(fallback_url, timeout=2, allow_redirects=False)
+
if response.status_code == 200:
return fallback_url
except requests.RequestException as e:
@@ -169,6 +204,8 @@ def fetch_and_encode_favicon(
"favicon_base64": f"data:image/svg+xml;base64,{DEFAULT_FAVICON}",
}
+ validate_url_ip(favicon_url)
+
response = requests.get(favicon_url, headers=headers, timeout=1)
# Get content type
diff --git a/apps/api/plane/bgtasks/workspace_invitation_task.py b/apps/api/plane/bgtasks/workspace_invitation_task.py
index f7480b36a6..f293cc16f8 100644
--- a/apps/api/plane/bgtasks/workspace_invitation_task.py
+++ b/apps/api/plane/bgtasks/workspace_invitation_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
@@ -7,11 +11,11 @@ from celery import shared_task
# Django imports
from django.core.mail import EmailMultiAlternatives, get_connection
from django.template.loader import render_to_string
-from django.utils.html import strip_tags
# Module imports
from plane.db.models import User, Workspace, WorkspaceMemberInvite
from plane.license.utils.instance_value import get_email_configuration
+from plane.utils.email import generate_plain_text_from_html
from plane.utils.exception_logger import log_exception
@@ -25,7 +29,7 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter):
# Relative link
relative_link = (
- f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&email={email}&slug={workspace.slug}" # noqa: E501
+ f"/workspace-invitations/?invitation_id={workspace_member_invite.id}&slug={workspace.slug}&token={token}" # noqa: E501
)
# The complete url including the domain
@@ -53,7 +57,7 @@ def workspace_invitation(email, workspace_id, token, current_site, inviter):
html_content = render_to_string("emails/invitations/workspace_invitation.html", context)
- text_content = strip_tags(html_content)
+ text_content = generate_plain_text_from_html(html_content)
workspace_member_invite.message = text_content
workspace_member_invite.save()
diff --git a/apps/api/plane/bgtasks/workspace_seed_task.py b/apps/api/plane/bgtasks/workspace_seed_task.py
index d1c15a345c..218ba2a717 100644
--- a/apps/api/plane/bgtasks/workspace_seed_task.py
+++ b/apps/api/plane/bgtasks/workspace_seed_task.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import json
@@ -21,7 +25,7 @@ from plane.db.models import (
WorkspaceMember,
Project,
ProjectMember,
- IssueUserProperty,
+ ProjectUserProperty,
State,
Label,
Issue,
@@ -122,9 +126,9 @@ def create_project_and_member(workspace: Workspace, bot_user: User) -> Dict[int,
)
# Create issue user properties
- IssueUserProperty.objects.bulk_create(
+ ProjectUserProperty.objects.bulk_create(
[
- IssueUserProperty(
+ ProjectUserProperty(
project=project,
user_id=workspace_member["member_id"],
workspace_id=workspace.id,
@@ -359,7 +363,7 @@ def create_pages(workspace: Workspace, project_map: Dict[int, uuid.UUID], bot_us
is_global=False,
access=page_seed.get("access", Page.PUBLIC_ACCESS),
name=page_seed.get("name"),
- description=page_seed.get("description", {}),
+ description_json=page_seed.get("description_json", {}),
description_html=page_seed.get("description_html", "
"),
description_binary=page_seed.get("description_binary", None),
description_stripped=page_seed.get("description_stripped", None),
diff --git a/apps/api/plane/celery.py b/apps/api/plane/celery.py
index 828f4a6d59..562d04856f 100644
--- a/apps/api/plane/celery.py
+++ b/apps/api/plane/celery.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import logging
diff --git a/apps/api/plane/db/__init__.py b/apps/api/plane/db/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/db/__init__.py
+++ b/apps/api/plane/db/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/db/apps.py b/apps/api/plane/db/apps.py
index 7d4919d088..92c55908e9 100644
--- a/apps/api/plane/db/apps.py
+++ b/apps/api/plane/db/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/db/management/__init__.py b/apps/api/plane/db/management/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/db/management/__init__.py
+++ b/apps/api/plane/db/management/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/db/management/commands/__init__.py b/apps/api/plane/db/management/commands/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/db/management/commands/__init__.py
+++ b/apps/api/plane/db/management/commands/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/db/management/commands/activate_user.py b/apps/api/plane/db/management/commands/activate_user.py
index 5ebe8b7409..3488a98659 100644
--- a/apps/api/plane/db/management/commands/activate_user.py
+++ b/apps/api/plane/db/management/commands/activate_user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management import BaseCommand, CommandError
diff --git a/apps/api/plane/db/management/commands/clear_cache.py b/apps/api/plane/db/management/commands/clear_cache.py
index 1c66b3eafc..502778f1cf 100644
--- a/apps/api/plane/db/management/commands/clear_cache.py
+++ b/apps/api/plane/db/management/commands/clear_cache.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.cache import cache
from django.core.management import BaseCommand
diff --git a/apps/api/plane/db/management/commands/copy_issue_comment_to_description.py b/apps/api/plane/db/management/commands/copy_issue_comment_to_description.py
index 8813f34db2..ec106795b4 100644
--- a/apps/api/plane/db/management/commands/copy_issue_comment_to_description.py
+++ b/apps/api/plane/db/management/commands/copy_issue_comment_to_description.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management.base import BaseCommand
from django.db import transaction
diff --git a/apps/api/plane/db/management/commands/create_bucket.py b/apps/api/plane/db/management/commands/create_bucket.py
index 555fe0aa88..7a39a3a7fc 100644
--- a/apps/api/plane/db/management/commands/create_bucket.py
+++ b/apps/api/plane/db/management/commands/create_bucket.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import boto3
diff --git a/apps/api/plane/db/management/commands/create_dummy_data.py b/apps/api/plane/db/management/commands/create_dummy_data.py
index 220576b8f4..c85c1e0176 100644
--- a/apps/api/plane/db/management/commands/create_dummy_data.py
+++ b/apps/api/plane/db/management/commands/create_dummy_data.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from typing import Any
from django.core.management.base import BaseCommand, CommandError
diff --git a/apps/api/plane/db/management/commands/create_instance_admin.py b/apps/api/plane/db/management/commands/create_instance_admin.py
index 8d5a912e04..3834918d40 100644
--- a/apps/api/plane/db/management/commands/create_instance_admin.py
+++ b/apps/api/plane/db/management/commands/create_instance_admin.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management.base import BaseCommand, CommandError
diff --git a/apps/api/plane/db/management/commands/create_project_member.py b/apps/api/plane/db/management/commands/create_project_member.py
index d9b46524c2..2bd9755787 100644
--- a/apps/api/plane/db/management/commands/create_project_member.py
+++ b/apps/api/plane/db/management/commands/create_project_member.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from typing import Any
from django.core.management import BaseCommand, CommandError
@@ -8,7 +12,7 @@ from plane.db.models import (
WorkspaceMember,
ProjectMember,
Project,
- IssueUserProperty,
+ ProjectUserProperty,
)
@@ -47,27 +51,18 @@ class Command(BaseCommand):
if not WorkspaceMember.objects.filter(workspace=project.workspace, member=user, is_active=True).exists():
raise CommandError("User not member in workspace")
- # Get the smallest sort order
- smallest_sort_order = (
- ProjectMember.objects.filter(workspace_id=project.workspace_id).order_by("sort_order").first()
- )
-
- if smallest_sort_order:
- sort_order = smallest_sort_order.sort_order - 1000
- else:
- sort_order = 65535
if ProjectMember.objects.filter(project=project, member=user).exists():
# Update the project member
ProjectMember.objects.filter(project=project, member=user).update(
- is_active=True, sort_order=sort_order, role=role
+ is_active=True, role=role
)
else:
# Create the project member
- ProjectMember.objects.create(project=project, member=user, role=role, sort_order=sort_order)
+ ProjectMember.objects.create(project=project, member=user, role=role)
# Issue Property
- IssueUserProperty.objects.get_or_create(user=user, project=project)
+ ProjectUserProperty.objects.get_or_create(user=user, project=project)
# Success message
self.stdout.write(self.style.SUCCESS(f"User {user_email} added to project {project_id}"))
diff --git a/apps/api/plane/db/management/commands/fix_duplicate_sequences.py b/apps/api/plane/db/management/commands/fix_duplicate_sequences.py
index 2b262606a2..70624fbc28 100644
--- a/apps/api/plane/db/management/commands/fix_duplicate_sequences.py
+++ b/apps/api/plane/db/management/commands/fix_duplicate_sequences.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management.base import BaseCommand, CommandError
from django.db.models import Max
diff --git a/apps/api/plane/db/management/commands/reset_password.py b/apps/api/plane/db/management/commands/reset_password.py
index 9e483f51e3..5da607c6cd 100644
--- a/apps/api/plane/db/management/commands/reset_password.py
+++ b/apps/api/plane/db/management/commands/reset_password.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import getpass
diff --git a/apps/api/plane/db/management/commands/sync_issue_description_version.py b/apps/api/plane/db/management/commands/sync_issue_description_version.py
index 04e608a3ce..0aac4bb153 100644
--- a/apps/api/plane/db/management/commands/sync_issue_description_version.py
+++ b/apps/api/plane/db/management/commands/sync_issue_description_version.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management.base import BaseCommand
diff --git a/apps/api/plane/db/management/commands/sync_issue_version.py b/apps/api/plane/db/management/commands/sync_issue_version.py
index 6c9a2cdac1..a7ee98fa75 100644
--- a/apps/api/plane/db/management/commands/sync_issue_version.py
+++ b/apps/api/plane/db/management/commands/sync_issue_version.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.core.management.base import BaseCommand
diff --git a/apps/api/plane/db/management/commands/test_email.py b/apps/api/plane/db/management/commands/test_email.py
index 22841a671b..103b239b1a 100644
--- a/apps/api/plane/db/management/commands/test_email.py
+++ b/apps/api/plane/db/management/commands/test_email.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.core.mail import EmailMultiAlternatives, get_connection
from django.core.management import BaseCommand, CommandError
from django.template.loader import render_to_string
diff --git a/apps/api/plane/db/management/commands/update_bucket.py b/apps/api/plane/db/management/commands/update_bucket.py
index 47c28ff739..79f7eab4e7 100644
--- a/apps/api/plane/db/management/commands/update_bucket.py
+++ b/apps/api/plane/db/management/commands/update_bucket.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import boto3
diff --git a/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py b/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py
index 8383253541..067afe2316 100644
--- a/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py
+++ b/apps/api/plane/db/management/commands/update_deleted_workspace_slug.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.core.management.base import BaseCommand
from django.db import transaction
from plane.db.models import Workspace
diff --git a/apps/api/plane/db/management/commands/wait_for_db.py b/apps/api/plane/db/management/commands/wait_for_db.py
index ec971f83a7..8a9fdbc3d6 100644
--- a/apps/api/plane/db/management/commands/wait_for_db.py
+++ b/apps/api/plane/db/management/commands/wait_for_db.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import time
from django.db import connections
from django.db.utils import OperationalError
diff --git a/apps/api/plane/db/management/commands/wait_for_migrations.py b/apps/api/plane/db/management/commands/wait_for_migrations.py
index 13b251de53..b61d011b25 100644
--- a/apps/api/plane/db/management/commands/wait_for_migrations.py
+++ b/apps/api/plane/db/management/commands/wait_for_migrations.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# wait_for_migrations.py
import time
from django.core.management.base import BaseCommand
diff --git a/apps/api/plane/db/migrations/0113_webhook_version.py b/apps/api/plane/db/migrations/0113_webhook_version.py
index 3be3120eb4..4ffbf3bb7f 100644
--- a/apps/api/plane/db/migrations/0113_webhook_version.py
+++ b/apps/api/plane/db/migrations/0113_webhook_version.py
@@ -4,6 +4,15 @@ from django.db import migrations, models
import plane.db.models.workspace
+def set_default_product_tour_to_false():
+ return {
+ "work_items": False,
+ "cycles": False,
+ "modules": False,
+ "intake": False,
+ "pages": False,
+ }
+
def get_default_product_tour():
return {
"work_items": True,
@@ -41,7 +50,7 @@ class Migration(migrations.Migration):
migrations.AddField(
model_name='workspaceuserproperties',
name='product_tour',
- field=models.JSONField(default=plane.db.models.workspace.get_default_product_tour),
+ field=models.JSONField(default=set_default_product_tour_to_false),
),
migrations.AddField(
model_name='apitoken',
diff --git a/apps/api/plane/db/migrations/0114_projectuserproperty_delete_issueuserproperty_and_more.py b/apps/api/plane/db/migrations/0114_projectuserproperty_delete_issueuserproperty_and_more.py
new file mode 100644
index 0000000000..9a18fbafca
--- /dev/null
+++ b/apps/api/plane/db/migrations/0114_projectuserproperty_delete_issueuserproperty_and_more.py
@@ -0,0 +1,50 @@
+# Generated by Django 4.2.22 on 2026-01-05 08:35
+
+from django.db import migrations, models
+import plane.db.models.project
+import django.db.models.deletion
+from django.conf import settings
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0113_webhook_version'),
+ ]
+
+ operations = [
+ migrations.AlterModelTable(
+ name='issueuserproperty',
+ table='project_user_properties',
+ ),
+ migrations.RenameModel(
+ old_name='IssueUserProperty',
+ new_name='ProjectUserProperty',
+ ),
+ migrations.AddField(
+ model_name='projectuserproperty',
+ name='preferences',
+ field=models.JSONField(default=plane.db.models.project.get_default_preferences),
+ ),
+ migrations.AddField(
+ model_name='projectuserproperty',
+ name='sort_order',
+ field=models.FloatField(default=65535),
+ ),
+ migrations.AlterModelOptions(
+ name='projectuserproperty',
+ options={'ordering': ('-created_at',), 'verbose_name': 'Project User Property', 'verbose_name_plural': 'Project User Properties'},
+ ),
+ migrations.RemoveConstraint(
+ model_name='projectuserproperty',
+ name='issue_user_property_unique_user_project_when_deleted_at_null',
+ ),
+ migrations.AlterField(
+ model_name='projectuserproperty',
+ name='user',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='project_property_user', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AddConstraint(
+ model_name='projectuserproperty',
+ constraint=models.UniqueConstraint(condition=models.Q(('deleted_at__isnull', True)), fields=('user', 'project'), name='project_user_property_unique_user_project_when_deleted_at_null'),
+ ),
+ ]
\ No newline at end of file
diff --git a/apps/api/plane/db/migrations/0115_auto_20260105_1406.py b/apps/api/plane/db/migrations/0115_auto_20260105_1406.py
new file mode 100644
index 0000000000..b9ac71d470
--- /dev/null
+++ b/apps/api/plane/db/migrations/0115_auto_20260105_1406.py
@@ -0,0 +1,51 @@
+# Generated by Django 4.2.22 on 2026-01-05 08:36
+
+from django.db import migrations
+
+def move_issue_user_properties_to_project_user_properties(apps, schema_editor):
+ ProjectMember = apps.get_model('db', 'ProjectMember')
+ ProjectUserProperty = apps.get_model('db', 'ProjectUserProperty')
+
+ # Get all project members
+ project_members = ProjectMember.objects.filter(deleted_at__isnull=True).values('member_id', 'project_id', 'preferences', 'sort_order')
+
+ # create a mapping with consistent ordering
+ pm_dict = {
+ (pm['member_id'], pm['project_id']): pm
+ for pm in project_members
+ }
+
+ # Get all project user properties
+ properties_to_update = []
+ for projectuserproperty in ProjectUserProperty.objects.filter(deleted_at__isnull=True):
+ pm = pm_dict.get((projectuserproperty.user_id, projectuserproperty.project_id))
+ if pm:
+ projectuserproperty.preferences = pm['preferences']
+ projectuserproperty.sort_order = pm['sort_order']
+ properties_to_update.append(projectuserproperty)
+
+ ProjectUserProperty.objects.bulk_update(properties_to_update, ['preferences', 'sort_order'], batch_size=2000)
+
+
+
+def migrate_existing_api_tokens(apps, schema_editor):
+ APIToken = apps.get_model('db', 'APIToken')
+
+ # Update all the existing non-service api tokens to not have a workspace
+ APIToken.objects.filter(is_service=False, user__is_bot=False).update(
+ workspace_id=None,
+
+ )
+ return
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0114_projectuserproperty_delete_issueuserproperty_and_more'),
+ ]
+
+ operations = [
+ migrations.RunPython(move_issue_user_properties_to_project_user_properties, reverse_code=migrations.RunPython.noop),
+ migrations.RunPython(migrate_existing_api_tokens, reverse_code=migrations.RunPython.noop),
+ ]
diff --git a/apps/api/plane/db/migrations/0116_workspacemember_explored_features_and_more.py b/apps/api/plane/db/migrations/0116_workspacemember_explored_features_and_more.py
new file mode 100644
index 0000000000..38e231e0eb
--- /dev/null
+++ b/apps/api/plane/db/migrations/0116_workspacemember_explored_features_and_more.py
@@ -0,0 +1,38 @@
+# Generated by Django 4.2.27 on 2026-01-13 10:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0115_auto_20260105_1406'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='profile',
+ name='notification_view_mode',
+ field=models.CharField(choices=[('full', 'Full'), ('compact', 'Compact')], default='full', max_length=255),
+ ),
+ migrations.AddField(
+ model_name='user',
+ name='is_password_reset_required',
+ field=models.BooleanField(default=False),
+ ),
+ migrations.AddField(
+ model_name='workspacemember',
+ name='explored_features',
+ field=models.JSONField(default=dict),
+ ),
+ migrations.AddField(
+ model_name='workspacemember',
+ name='getting_started_checklist',
+ field=models.JSONField(default=dict),
+ ),
+ migrations.AddField(
+ model_name='workspacemember',
+ name='tips',
+ field=models.JSONField(default=dict),
+ ),
+ ]
diff --git a/apps/api/plane/db/migrations/0117_rename_description_draftissue_description_json_and_more.py b/apps/api/plane/db/migrations/0117_rename_description_draftissue_description_json_and_more.py
new file mode 100644
index 0000000000..2317a4cdd7
--- /dev/null
+++ b/apps/api/plane/db/migrations/0117_rename_description_draftissue_description_json_and_more.py
@@ -0,0 +1,28 @@
+# Generated by Django 4.2.22 on 2026-01-15 09:02
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0116_workspacemember_explored_features_and_more'),
+ ]
+
+ operations = [
+ migrations.RenameField(
+ model_name='draftissue',
+ old_name='description',
+ new_name='description_json',
+ ),
+ migrations.RenameField(
+ model_name='issue',
+ old_name='description',
+ new_name='description_json',
+ ),
+ migrations.RenameField(
+ model_name='page',
+ old_name='description',
+ new_name='description_json',
+ ),
+ ]
diff --git a/apps/api/plane/db/migrations/0118_remove_workspaceuserproperties_product_tour_and_more.py b/apps/api/plane/db/migrations/0118_remove_workspaceuserproperties_product_tour_and_more.py
new file mode 100644
index 0000000000..9a2b39edfc
--- /dev/null
+++ b/apps/api/plane/db/migrations/0118_remove_workspaceuserproperties_product_tour_and_more.py
@@ -0,0 +1,71 @@
+# Generated by Django 4.2.27 on 2026-01-23 09:27
+
+from django.db import migrations, models
+import plane.db.models.user
+
+def set_getting_started_checklist():
+ return {
+ "project_created": True,
+ "project_joined": True,
+ "work_item_created": True,
+ "team_members_invited": True,
+ "page_created": True,
+ "ai_chat_tried": True,
+ "integration_linked": True,
+ "view_created": True,
+ "sticky_created": True,
+ }
+
+def set_default_tips():
+ return {"mobile_app_download": True}
+
+
+def set_default_explored_features():
+ return {"github_integrated": True, "slack_integrated": True, "ai_chat_tried": True}
+
+
+def set_default_product_tour():
+ return {
+ "work_items": True,
+ "cycles": True,
+ "modules": True,
+ "intake": True,
+ "pages": True,
+ }
+
+
+def migrate_all_the_product_tour_to_true(apps, _schema_editor):
+ Profile = apps.get_model('db', 'Profile')
+ WorkspaceMember = apps.get_model('db', 'WorkspaceMember')
+
+ default_checklist_values = set_getting_started_checklist()
+ default_tips_values = set_default_tips()
+ default_explored_features = set_default_explored_features()
+ default_product_tour = set_default_product_tour()
+
+ Profile.objects.all().update(is_navigation_tour_completed=True)
+ WorkspaceMember.objects.all().update(getting_started_checklist=default_checklist_values)
+ WorkspaceMember.objects.all().update(tips=default_tips_values)
+ WorkspaceMember.objects.all().update(explored_features=default_explored_features)
+ Profile.objects.all().update(product_tour=default_product_tour)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0117_rename_description_draftissue_description_json_and_more'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='workspaceuserproperties',
+ name='product_tour',
+ ),
+ migrations.AddField(
+ model_name='profile',
+ name='product_tour',
+ field=models.JSONField(default=plane.db.models.user.get_default_product_tour),
+ ),
+ migrations.RunPython(migrate_all_the_product_tour_to_true, reverse_code=migrations.RunPython.noop)
+
+ ]
diff --git a/apps/api/plane/db/migrations/0119_alter_estimatepoint_key.py b/apps/api/plane/db/migrations/0119_alter_estimatepoint_key.py
new file mode 100644
index 0000000000..a730808a16
--- /dev/null
+++ b/apps/api/plane/db/migrations/0119_alter_estimatepoint_key.py
@@ -0,0 +1,19 @@
+# Generated by Django 4.2.27 on 2026-02-09 09:37
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0118_remove_workspaceuserproperties_product_tour_and_more'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='estimatepoint',
+ name='key',
+ field=models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0)]),
+ ),
+ ]
diff --git a/apps/api/plane/db/migrations/0120_issueview_archived_at.py b/apps/api/plane/db/migrations/0120_issueview_archived_at.py
new file mode 100644
index 0000000000..4357766d44
--- /dev/null
+++ b/apps/api/plane/db/migrations/0120_issueview_archived_at.py
@@ -0,0 +1,18 @@
+# Generated by Django 4.2.28 on 2026-02-17 10:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('db', '0119_alter_estimatepoint_key'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='issueview',
+ name='archived_at',
+ field=models.DateTimeField(null=True),
+ ),
+ ]
diff --git a/apps/api/plane/db/mixins.py b/apps/api/plane/db/mixins.py
index e43a46e294..b36269959b 100644
--- a/apps/api/plane/db/mixins.py
+++ b/apps/api/plane/db/mixins.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Type imports
from typing import Any
diff --git a/apps/api/plane/db/models/__init__.py b/apps/api/plane/db/models/__init__.py
index 41fd32bd55..5cf9dec2a3 100644
--- a/apps/api/plane/db/models/__init__.py
+++ b/apps/api/plane/db/models/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .analytic import AnalyticView
from .api import APIActivityLog, APIToken
from .asset import FileAsset
@@ -34,7 +38,6 @@ from .issue import (
IssueLabel,
IssueLink,
IssueMention,
- IssueUserProperty,
IssueReaction,
IssueRelation,
IssueSequence,
@@ -54,6 +57,7 @@ from .project import (
ProjectMemberInvite,
ProjectNetwork,
ProjectPublicMember,
+ ProjectUserProperty,
)
from .session import Session
from .social_connection import SocialLoginConnection
diff --git a/apps/api/plane/db/models/analytic.py b/apps/api/plane/db/models/analytic.py
index 0efcb957f4..601ef9ea54 100644
--- a/apps/api/plane/db/models/analytic.py
+++ b/apps/api/plane/db/models/analytic.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django models
from django.db import models
diff --git a/apps/api/plane/db/models/api.py b/apps/api/plane/db/models/api.py
index 75449a7428..c545860c05 100644
--- a/apps/api/plane/db/models/api.py
+++ b/apps/api/plane/db/models/api.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from uuid import uuid4
diff --git a/apps/api/plane/db/models/asset.py b/apps/api/plane/db/models/asset.py
index ed9879a733..d309135bca 100644
--- a/apps/api/plane/db/models/asset.py
+++ b/apps/api/plane/db/models/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from uuid import uuid4
diff --git a/apps/api/plane/db/models/base.py b/apps/api/plane/db/models/base.py
index 468af82614..482dc90635 100644
--- a/apps/api/plane/db/models/base.py
+++ b/apps/api/plane/db/models/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import uuid
# Django imports
diff --git a/apps/api/plane/db/models/cycle.py b/apps/api/plane/db/models/cycle.py
index bdffd283d8..78ea977d91 100644
--- a/apps/api/plane/db/models/cycle.py
+++ b/apps/api/plane/db/models/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import pytz
diff --git a/apps/api/plane/db/models/deploy_board.py b/apps/api/plane/db/models/deploy_board.py
index da9c0d6982..b9d8778e08 100644
--- a/apps/api/plane/db/models/deploy_board.py
+++ b/apps/api/plane/db/models/deploy_board.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from uuid import uuid4
diff --git a/apps/api/plane/db/models/description.py b/apps/api/plane/db/models/description.py
index 6c298546a3..0e8de3ce76 100644
--- a/apps/api/plane/db/models/description.py
+++ b/apps/api/plane/db/models/description.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.db import models
from django.utils.html import strip_tags
from .workspace import WorkspaceBaseModel
diff --git a/apps/api/plane/db/models/device.py b/apps/api/plane/db/models/device.py
index adcf7974a1..9254a21ffe 100644
--- a/apps/api/plane/db/models/device.py
+++ b/apps/api/plane/db/models/device.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# models.py
from django.db import models
from django.conf import settings
diff --git a/apps/api/plane/db/models/draft.py b/apps/api/plane/db/models/draft.py
index 55dbb61df9..2d126da228 100644
--- a/apps/api/plane/db/models/draft.py
+++ b/apps/api/plane/db/models/draft.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
@@ -39,7 +43,7 @@ class DraftIssue(WorkspaceBaseModel):
blank=True,
)
name = models.CharField(max_length=255, verbose_name="Issue Name", blank=True, null=True)
- description = models.JSONField(blank=True, default=dict)
+ description_json = models.JSONField(blank=True, default=dict)
description_html = models.TextField(blank=True, default="
")
description_stripped = models.TextField(blank=True, null=True)
description_binary = models.BinaryField(null=True)
diff --git a/apps/api/plane/db/models/estimate.py b/apps/api/plane/db/models/estimate.py
index 9373fb3204..ded6f97bcf 100644
--- a/apps/api/plane/db/models/estimate.py
+++ b/apps/api/plane/db/models/estimate.py
@@ -1,5 +1,9 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
-from django.core.validators import MaxValueValidator, MinValueValidator
+from django.core.validators import MinValueValidator
from django.db import models
from django.db.models import Q
@@ -34,7 +38,7 @@ class Estimate(ProjectBaseModel):
class EstimatePoint(ProjectBaseModel):
estimate = models.ForeignKey("db.Estimate", on_delete=models.CASCADE, related_name="points")
- key = models.IntegerField(default=0, validators=[MinValueValidator(0), MaxValueValidator(12)])
+ key = models.IntegerField(default=0, validators=[MinValueValidator(0)])
description = models.TextField(blank=True)
value = models.CharField(max_length=255)
diff --git a/apps/api/plane/db/models/exporter.py b/apps/api/plane/db/models/exporter.py
index 8ad9daad7a..7abfe63afd 100644
--- a/apps/api/plane/db/models/exporter.py
+++ b/apps/api/plane/db/models/exporter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import uuid
# Python imports
diff --git a/apps/api/plane/db/models/favorite.py b/apps/api/plane/db/models/favorite.py
index de2b101a05..1ce29da875 100644
--- a/apps/api/plane/db/models/favorite.py
+++ b/apps/api/plane/db/models/favorite.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.conf import settings
# Django imports
diff --git a/apps/api/plane/db/models/importer.py b/apps/api/plane/db/models/importer.py
index 9bcea8cf0b..24d987bb7a 100644
--- a/apps/api/plane/db/models/importer.py
+++ b/apps/api/plane/db/models/importer.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
diff --git a/apps/api/plane/db/models/intake.py b/apps/api/plane/db/models/intake.py
index c3369ae1d0..700d5d8cf7 100644
--- a/apps/api/plane/db/models/intake.py
+++ b/apps/api/plane/db/models/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import models
diff --git a/apps/api/plane/db/models/integration/__init__.py b/apps/api/plane/db/models/integration/__init__.py
index 34b40e57d9..2242b4ddd1 100644
--- a/apps/api/plane/db/models/integration/__init__.py
+++ b/apps/api/plane/db/models/integration/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import Integration, WorkspaceIntegration
from .github import (
GithubRepository,
diff --git a/apps/api/plane/db/models/integration/base.py b/apps/api/plane/db/models/integration/base.py
index 296c3cf6d6..d98aa292df 100644
--- a/apps/api/plane/db/models/integration/base.py
+++ b/apps/api/plane/db/models/integration/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/db/models/integration/github.py b/apps/api/plane/db/models/integration/github.py
index ba278497ed..8d84dbe3e4 100644
--- a/apps/api/plane/db/models/integration/github.py
+++ b/apps/api/plane/db/models/integration/github.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
# Django imports
diff --git a/apps/api/plane/db/models/integration/slack.py b/apps/api/plane/db/models/integration/slack.py
index 1e8ea469b6..f1c33f5c2c 100644
--- a/apps/api/plane/db/models/integration/slack.py
+++ b/apps/api/plane/db/models/integration/slack.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
# Django imports
diff --git a/apps/api/plane/db/models/issue.py b/apps/api/plane/db/models/issue.py
index 5f6ce051d2..d24efc8a23 100644
--- a/apps/api/plane/db/models/issue.py
+++ b/apps/api/plane/db/models/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python import
from uuid import uuid4
@@ -128,7 +132,7 @@ class Issue(ProjectBaseModel):
blank=True,
)
name = models.CharField(max_length=255, verbose_name="Issue Name")
- description = models.JSONField(blank=True, default=dict)
+ description_json = models.JSONField(blank=True, default=dict)
description_html = models.TextField(blank=True, default="
")
description_stripped = models.TextField(blank=True, null=True)
description_binary = models.BinaryField(null=True)
@@ -526,36 +530,6 @@ class IssueComment(ChangeTrackerMixin, ProjectBaseModel):
return str(self.issue)
-class IssueUserProperty(ProjectBaseModel):
- user = models.ForeignKey(
- settings.AUTH_USER_MODEL,
- on_delete=models.CASCADE,
- related_name="issue_property_user",
- )
- filters = models.JSONField(default=get_default_filters)
- display_filters = models.JSONField(default=get_default_display_filters)
- display_properties = models.JSONField(default=get_default_display_properties)
- rich_filters = models.JSONField(default=dict)
-
- class Meta:
- verbose_name = "Issue User Property"
- verbose_name_plural = "Issue User Properties"
- db_table = "issue_user_properties"
- ordering = ("-created_at",)
- unique_together = ["user", "project", "deleted_at"]
- constraints = [
- models.UniqueConstraint(
- fields=["user", "project"],
- condition=Q(deleted_at__isnull=True),
- name="issue_user_property_unique_user_project_when_deleted_at_null",
- )
- ]
-
- def __str__(self):
- """Return properties status of the issue"""
- return str(self.user)
-
-
class IssueLabel(ProjectBaseModel):
issue = models.ForeignKey("db.Issue", on_delete=models.CASCADE, related_name="label_issue")
label = models.ForeignKey("db.Label", on_delete=models.CASCADE, related_name="label_issue")
@@ -830,7 +804,7 @@ class IssueDescriptionVersion(ProjectBaseModel):
description_binary=issue.description_binary,
description_html=issue.description_html,
description_stripped=issue.description_stripped,
- description_json=issue.description,
+ description_json=issue.description_json,
)
return True
except Exception as e:
diff --git a/apps/api/plane/db/models/issue_type.py b/apps/api/plane/db/models/issue_type.py
index 4f3dc08dec..94eaf50bfe 100644
--- a/apps/api/plane/db/models/issue_type.py
+++ b/apps/api/plane/db/models/issue_type.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import models
from django.db.models import Q
diff --git a/apps/api/plane/db/models/label.py b/apps/api/plane/db/models/label.py
index 76ecf10e61..9435e01c65 100644
--- a/apps/api/plane/db/models/label.py
+++ b/apps/api/plane/db/models/label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.db import models
from django.db.models import Q
diff --git a/apps/api/plane/db/models/module.py b/apps/api/plane/db/models/module.py
index ab62f2df54..d660116fa8 100644
--- a/apps/api/plane/db/models/module.py
+++ b/apps/api/plane/db/models/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
diff --git a/apps/api/plane/db/models/notification.py b/apps/api/plane/db/models/notification.py
index fd97a3c968..c241358548 100644
--- a/apps/api/plane/db/models/notification.py
+++ b/apps/api/plane/db/models/notification.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
diff --git a/apps/api/plane/db/models/page.py b/apps/api/plane/db/models/page.py
index 213954d149..2c82c5f44b 100644
--- a/apps/api/plane/db/models/page.py
+++ b/apps/api/plane/db/models/page.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import uuid
from django.conf import settings
@@ -25,7 +29,7 @@ class Page(BaseModel):
workspace = models.ForeignKey("db.Workspace", on_delete=models.CASCADE, related_name="pages")
name = models.TextField(blank=True)
- description = models.JSONField(default=dict, blank=True)
+ description_json = models.JSONField(default=dict, blank=True)
description_binary = models.BinaryField(null=True)
description_html = models.TextField(blank=True, default="
")
description_stripped = models.TextField(blank=True, null=True)
diff --git a/apps/api/plane/db/models/project.py b/apps/api/plane/db/models/project.py
index 173ed43854..4039b1d290 100644
--- a/apps/api/plane/db/models/project.py
+++ b/apps/api/plane/db/models/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import pytz
from uuid import uuid4
@@ -12,7 +16,6 @@ from django.db.models import Q
# Module imports
from plane.db.mixins import AuditModel
-# Module imports
from .base import BaseModel
ROLE_CHOICES = ((20, "Admin"), (15, "Member"), (5, "Guest"))
@@ -137,6 +140,8 @@ class Project(BaseModel):
"""Return name of the project"""
return f"{self.name} <{self.workspace.name}>"
+ FORBIDDEN_IDENTIFIER_CHARS_PATTERN = r"^.*[&+,:;$^}{*=?@#|'<>.()%!-].*$"
+
class Meta:
unique_together = [
["identifier", "workspace", "deleted_at"],
@@ -219,14 +224,20 @@ class ProjectMember(ProjectBaseModel):
is_active = models.BooleanField(default=True)
def save(self, *args, **kwargs):
- if self._state.adding:
- smallest_sort_order = ProjectMember.objects.filter(
- workspace_id=self.project.workspace_id, member=self.member
- ).aggregate(smallest=models.Min("sort_order"))["smallest"]
+ if self._state.adding and self.member:
+ # Get the minimum sort_order for this member in the workspace
+ min_sort_order_result = ProjectUserProperty.objects.filter(
+ workspace_id=self.project.workspace_id, user=self.member
+ ).aggregate(min_sort_order=models.Min("sort_order"))
+ min_sort_order = min_sort_order_result.get("min_sort_order")
- # Project ordering
- if smallest_sort_order is not None:
- self.sort_order = smallest_sort_order - 10000
+ # create project user property with project sort order
+ ProjectUserProperty.objects.create(
+ workspace_id=self.project.workspace_id,
+ project=self.project,
+ user=self.member,
+ sort_order=(min_sort_order - 10000 if min_sort_order is not None else 65535),
+ )
super(ProjectMember, self).save(*args, **kwargs)
@@ -326,3 +337,37 @@ class ProjectPublicMember(ProjectBaseModel):
verbose_name_plural = "Project Public Members"
db_table = "project_public_members"
ordering = ("-created_at",)
+
+
+class ProjectUserProperty(ProjectBaseModel):
+ from .issue import get_default_filters, get_default_display_filters, get_default_display_properties
+
+ user = models.ForeignKey(
+ settings.AUTH_USER_MODEL,
+ on_delete=models.CASCADE,
+ related_name="project_property_user",
+ )
+ filters = models.JSONField(default=get_default_filters)
+ display_filters = models.JSONField(default=get_default_display_filters)
+ display_properties = models.JSONField(default=get_default_display_properties)
+ rich_filters = models.JSONField(default=dict)
+ preferences = models.JSONField(default=get_default_preferences)
+ sort_order = models.FloatField(default=65535)
+
+ class Meta:
+ verbose_name = "Project User Property"
+ verbose_name_plural = "Project User Properties"
+ db_table = "project_user_properties"
+ ordering = ("-created_at",)
+ unique_together = ["user", "project", "deleted_at"]
+ constraints = [
+ models.UniqueConstraint(
+ fields=["user", "project"],
+ condition=Q(deleted_at__isnull=True),
+ name="project_user_property_unique_user_project_when_deleted_at_null",
+ )
+ ]
+
+ def __str__(self):
+ """Return properties status of the project"""
+ return str(self.user)
diff --git a/apps/api/plane/db/models/recent_visit.py b/apps/api/plane/db/models/recent_visit.py
index 42855081bd..fb368fa122 100644
--- a/apps/api/plane/db/models/recent_visit.py
+++ b/apps/api/plane/db/models/recent_visit.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import models
from django.conf import settings
diff --git a/apps/api/plane/db/models/session.py b/apps/api/plane/db/models/session.py
index e884498bf1..52b885ee94 100644
--- a/apps/api/plane/db/models/session.py
+++ b/apps/api/plane/db/models/session.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import string
diff --git a/apps/api/plane/db/models/social_connection.py b/apps/api/plane/db/models/social_connection.py
index 9a85a320d5..7e8ee8c2ca 100644
--- a/apps/api/plane/db/models/social_connection.py
+++ b/apps/api/plane/db/models/social_connection.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
diff --git a/apps/api/plane/db/models/state.py b/apps/api/plane/db/models/state.py
index 29a02e00c2..fa56900c3f 100644
--- a/apps/api/plane/db/models/state.py
+++ b/apps/api/plane/db/models/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db import models
from django.template.defaultfilters import slugify
diff --git a/apps/api/plane/db/models/sticky.py b/apps/api/plane/db/models/sticky.py
index 157077eb8c..757cb8ea11 100644
--- a/apps/api/plane/db/models/sticky.py
+++ b/apps/api/plane/db/models/sticky.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
diff --git a/apps/api/plane/db/models/user.py b/apps/api/plane/db/models/user.py
index b0f571be9c..7f1ab162da 100644
--- a/apps/api/plane/db/models/user.py
+++ b/apps/api/plane/db/models/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import random
import string
@@ -35,6 +39,16 @@ def get_mobile_default_onboarding():
}
+def get_default_product_tour():
+ return {
+ "work_items": False,
+ "cycles": False,
+ "modules": False,
+ "intake": False,
+ "pages": False,
+ }
+
+
class BotTypeEnum(models.TextChoices):
WORKSPACE_SEED = "WORKSPACE_SEED", "Workspace Seed"
@@ -84,7 +98,7 @@ class User(AbstractBaseUser, PermissionsMixin):
is_staff = models.BooleanField(default=False)
is_email_verified = models.BooleanField(default=False)
is_password_autoset = models.BooleanField(default=False)
-
+ is_password_reset_required = models.BooleanField(default=False)
# random token generated
token = models.CharField(max_length=64, blank=True)
@@ -192,6 +206,10 @@ class Profile(TimeAuditModel):
FRIDAY = 5
SATURDAY = 6
+ class NotificationViewMode(models.TextChoices):
+ FULL = "full", "Full"
+ COMPACT = "compact", "Compact"
+
START_OF_THE_WEEK_CHOICES = (
(SUNDAY, "Sunday"),
(MONDAY, "Monday"),
@@ -221,7 +239,9 @@ class Profile(TimeAuditModel):
billing_address = models.JSONField(null=True)
has_billing_address = models.BooleanField(default=False)
company_name = models.CharField(max_length=255, blank=True)
-
+ notification_view_mode = models.CharField(
+ max_length=255, choices=NotificationViewMode.choices, default=NotificationViewMode.FULL
+ )
is_smooth_cursor_enabled = models.BooleanField(default=False)
# mobile
is_mobile_onboarded = models.BooleanField(default=False)
@@ -239,6 +259,7 @@ class Profile(TimeAuditModel):
# marketing
has_marketing_email_consent = models.BooleanField(default=False)
is_subscribed_to_changelog = models.BooleanField(default=False)
+ product_tour = models.JSONField(default=get_default_product_tour)
class Meta:
verbose_name = "Profile"
diff --git a/apps/api/plane/db/models/view.py b/apps/api/plane/db/models/view.py
index d430cd5f97..a02b768a39 100644
--- a/apps/api/plane/db/models/view.py
+++ b/apps/api/plane/db/models/view.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.db import models
@@ -64,6 +68,7 @@ class IssueView(WorkspaceBaseModel):
logo_props = models.JSONField(default=dict)
owned_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="views")
is_locked = models.BooleanField(default=False)
+ archived_at = models.DateTimeField(null=True)
class Meta:
verbose_name = "Issue View"
diff --git a/apps/api/plane/db/models/webhook.py b/apps/api/plane/db/models/webhook.py
index 298b0dba3b..99431ed422 100644
--- a/apps/api/plane/db/models/webhook.py
+++ b/apps/api/plane/db/models/webhook.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from uuid import uuid4
from urllib.parse import urlparse
diff --git a/apps/api/plane/db/models/workspace.py b/apps/api/plane/db/models/workspace.py
index 9690168a11..80a3e3e3e4 100644
--- a/apps/api/plane/db/models/workspace.py
+++ b/apps/api/plane/db/models/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import pytz
from typing import Optional, Any
@@ -112,16 +116,6 @@ def slug_validator(value):
raise ValidationError("Slug is not valid")
-def get_default_product_tour():
- return {
- "work_items": False,
- "cycles": False,
- "modules": False,
- "intake": False,
- "pages": False,
- }
-
-
class Workspace(BaseModel):
TIMEZONE_CHOICES = tuple(zip(pytz.common_timezones, pytz.common_timezones))
@@ -214,6 +208,9 @@ class WorkspaceMember(BaseModel):
default_props = models.JSONField(default=get_default_props)
issue_props = models.JSONField(default=get_issue_props)
is_active = models.BooleanField(default=True)
+ getting_started_checklist = models.JSONField(default=dict)
+ tips = models.JSONField(default=dict)
+ explored_features = models.JSONField(default=dict)
class Meta:
unique_together = ["workspace", "member", "deleted_at"]
@@ -335,7 +332,6 @@ class WorkspaceUserProperties(BaseModel):
choices=NavigationControlPreference.choices,
default=NavigationControlPreference.ACCORDION,
)
- product_tour = models.JSONField(default=get_default_product_tour)
class Meta:
unique_together = ["workspace", "user", "deleted_at"]
diff --git a/apps/api/plane/license/__init__.py b/apps/api/plane/license/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/__init__.py
+++ b/apps/api/plane/license/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/api/__init__.py b/apps/api/plane/license/api/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/api/__init__.py
+++ b/apps/api/plane/license/api/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/api/permissions/__init__.py b/apps/api/plane/license/api/permissions/__init__.py
index d5bedc4c08..8878e2aaf8 100644
--- a/apps/api/plane/license/api/permissions/__init__.py
+++ b/apps/api/plane/license/api/permissions/__init__.py
@@ -1 +1,5 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .instance import InstanceAdminPermission
diff --git a/apps/api/plane/license/api/permissions/instance.py b/apps/api/plane/license/api/permissions/instance.py
index a430b688b7..819757375d 100644
--- a/apps/api/plane/license/api/permissions/instance.py
+++ b/apps/api/plane/license/api/permissions/instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.permissions import BasePermission
diff --git a/apps/api/plane/license/api/serializers/__init__.py b/apps/api/plane/license/api/serializers/__init__.py
index 6e0a5941c4..b4a39adcef 100644
--- a/apps/api/plane/license/api/serializers/__init__.py
+++ b/apps/api/plane/license/api/serializers/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .instance import InstanceSerializer
from .configuration import InstanceConfigurationSerializer
diff --git a/apps/api/plane/license/api/serializers/admin.py b/apps/api/plane/license/api/serializers/admin.py
index 4df6901cac..ebca0e5622 100644
--- a/apps/api/plane/license/api/serializers/admin.py
+++ b/apps/api/plane/license/api/serializers/admin.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import User
diff --git a/apps/api/plane/license/api/serializers/base.py b/apps/api/plane/license/api/serializers/base.py
index 0c6bba4682..63c173e6d4 100644
--- a/apps/api/plane/license/api/serializers/base.py
+++ b/apps/api/plane/license/api/serializers/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
diff --git a/apps/api/plane/license/api/serializers/configuration.py b/apps/api/plane/license/api/serializers/configuration.py
index 1766f21136..21abc7013a 100644
--- a/apps/api/plane/license/api/serializers/configuration.py
+++ b/apps/api/plane/license/api/serializers/configuration.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from plane.license.models import InstanceConfiguration
from plane.license.utils.encryption import decrypt_data
diff --git a/apps/api/plane/license/api/serializers/instance.py b/apps/api/plane/license/api/serializers/instance.py
index c75c62e50f..1598b3fb68 100644
--- a/apps/api/plane/license/api/serializers/instance.py
+++ b/apps/api/plane/license/api/serializers/instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from plane.license.models import Instance
from plane.app.serializers import BaseSerializer
diff --git a/apps/api/plane/license/api/serializers/user.py b/apps/api/plane/license/api/serializers/user.py
index c53b4a4848..b5e35ac72d 100644
--- a/apps/api/plane/license/api/serializers/user.py
+++ b/apps/api/plane/license/api/serializers/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .base import BaseSerializer
from plane.db.models import User
diff --git a/apps/api/plane/license/api/serializers/workspace.py b/apps/api/plane/license/api/serializers/workspace.py
index 75dd938e45..d12473e204 100644
--- a/apps/api/plane/license/api/serializers/workspace.py
+++ b/apps/api/plane/license/api/serializers/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party Imports
from rest_framework import serializers
diff --git a/apps/api/plane/license/api/views/__init__.py b/apps/api/plane/license/api/views/__init__.py
index 7f30d53fe6..e25276495f 100644
--- a/apps/api/plane/license/api/views/__init__.py
+++ b/apps/api/plane/license/api/views/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .instance import InstanceEndpoint, SignUpScreenVisitedEndpoint
diff --git a/apps/api/plane/license/api/views/admin.py b/apps/api/plane/license/api/views/admin.py
index 0d37f4fdc0..6217cc87fa 100644
--- a/apps/api/plane/license/api/views/admin.py
+++ b/apps/api/plane/license/api/views/admin.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from urllib.parse import urlencode, urljoin
import uuid
@@ -187,8 +191,8 @@ class InstanceAdminSignUpEndpoint(View):
results = zxcvbn(password)
if results["score"] < 3:
exc = AuthenticationException(
- error_code=AUTHENTICATION_ERROR_CODES["INVALID_ADMIN_PASSWORD"],
- error_message="INVALID_ADMIN_PASSWORD",
+ error_code=AUTHENTICATION_ERROR_CODES["PASSWORD_TOO_WEAK"],
+ error_message="PASSWORD_TOO_WEAK",
payload={
"email": email,
"first_name": first_name,
diff --git a/apps/api/plane/license/api/views/base.py b/apps/api/plane/license/api/views/base.py
index d209bd6bf2..8d0d39ac38 100644
--- a/apps/api/plane/license/api/views/base.py
+++ b/apps/api/plane/license/api/views/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import zoneinfo
from django.conf import settings
diff --git a/apps/api/plane/license/api/views/configuration.py b/apps/api/plane/license/api/views/configuration.py
index 8bb9535655..bb9a9e00ee 100644
--- a/apps/api/plane/license/api/views/configuration.py
+++ b/apps/api/plane/license/api/views/configuration.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from smtplib import (
SMTPAuthenticationError,
diff --git a/apps/api/plane/license/api/views/instance.py b/apps/api/plane/license/api/views/instance.py
index fed0c5e17e..a0d52d4912 100644
--- a/apps/api/plane/license/api/views/instance.py
+++ b/apps/api/plane/license/api/views/instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/license/api/views/workspace.py b/apps/api/plane/license/api/views/workspace.py
index 5d1a2f24bb..966b3b3e8f 100644
--- a/apps/api/plane/license/api/views/workspace.py
+++ b/apps/api/plane/license/api/views/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/license/apps.py b/apps/api/plane/license/apps.py
index 400e98155a..0cd4aba3b5 100644
--- a/apps/api/plane/license/apps.py
+++ b/apps/api/plane/license/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/license/bgtasks/__init__.py b/apps/api/plane/license/bgtasks/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/bgtasks/__init__.py
+++ b/apps/api/plane/license/bgtasks/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/bgtasks/tracer.py b/apps/api/plane/license/bgtasks/tracer.py
index 055c45d6c8..f7c04b2a4b 100644
--- a/apps/api/plane/license/bgtasks/tracer.py
+++ b/apps/api/plane/license/bgtasks/tracer.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from celery import shared_task
from opentelemetry import trace
diff --git a/apps/api/plane/license/management/__init__.py b/apps/api/plane/license/management/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/management/__init__.py
+++ b/apps/api/plane/license/management/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/management/commands/__init__.py b/apps/api/plane/license/management/commands/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/management/commands/__init__.py
+++ b/apps/api/plane/license/management/commands/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/management/commands/configure_instance.py b/apps/api/plane/license/management/commands/configure_instance.py
index b3e84dd82d..43026a4554 100644
--- a/apps/api/plane/license/management/commands/configure_instance.py
+++ b/apps/api/plane/license/management/commands/configure_instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/license/management/commands/register_instance.py b/apps/api/plane/license/management/commands/register_instance.py
index 6717cafd13..5ad6f7d201 100644
--- a/apps/api/plane/license/management/commands/register_instance.py
+++ b/apps/api/plane/license/management/commands/register_instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
import secrets
diff --git a/apps/api/plane/license/models/__init__.py b/apps/api/plane/license/models/__init__.py
index d495240244..b1a84d846f 100644
--- a/apps/api/plane/license/models/__init__.py
+++ b/apps/api/plane/license/models/__init__.py
@@ -1 +1,5 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .instance import Instance, InstanceAdmin, InstanceConfiguration, InstanceEdition
diff --git a/apps/api/plane/license/models/instance.py b/apps/api/plane/license/models/instance.py
index 1767d8c224..ff9ebc6b46 100644
--- a/apps/api/plane/license/models/instance.py
+++ b/apps/api/plane/license/models/instance.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from enum import Enum
diff --git a/apps/api/plane/license/urls.py b/apps/api/plane/license/urls.py
index 4d306924ea..844a9e181e 100644
--- a/apps/api/plane/license/urls.py
+++ b/apps/api/plane/license/urls.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.license.api.views import (
diff --git a/apps/api/plane/license/utils/__init__.py b/apps/api/plane/license/utils/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/license/utils/__init__.py
+++ b/apps/api/plane/license/utils/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/license/utils/encryption.py b/apps/api/plane/license/utils/encryption.py
index d56766d1e1..8f43167c15 100644
--- a/apps/api/plane/license/utils/encryption.py
+++ b/apps/api/plane/license/utils/encryption.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import base64
import hashlib
from django.conf import settings
diff --git a/apps/api/plane/license/utils/instance_value.py b/apps/api/plane/license/utils/instance_value.py
index 8901bc814a..279eb21777 100644
--- a/apps/api/plane/license/utils/instance_value.py
+++ b/apps/api/plane/license/utils/instance_value.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/middleware/__init__.py b/apps/api/plane/middleware/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/middleware/__init__.py
+++ b/apps/api/plane/middleware/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/middleware/apps.py b/apps/api/plane/middleware/apps.py
index 9deac8091d..2037b6aa09 100644
--- a/apps/api/plane/middleware/apps.py
+++ b/apps/api/plane/middleware/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/middleware/db_routing.py b/apps/api/plane/middleware/db_routing.py
index 68b5c44916..7aa045a69c 100644
--- a/apps/api/plane/middleware/db_routing.py
+++ b/apps/api/plane/middleware/db_routing.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Database routing middleware for read replica selection.
This middleware determines whether database queries should be routed to
diff --git a/apps/api/plane/middleware/logger.py b/apps/api/plane/middleware/logger.py
index 07facdab0e..b8cf6f9c04 100644
--- a/apps/api/plane/middleware/logger.py
+++ b/apps/api/plane/middleware/logger.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
import time
diff --git a/apps/api/plane/middleware/request_body_size.py b/apps/api/plane/middleware/request_body_size.py
index 9807c57156..c4e014df6f 100644
--- a/apps/api/plane/middleware/request_body_size.py
+++ b/apps/api/plane/middleware/request_body_size.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.core.exceptions import RequestDataTooBig
from django.http import JsonResponse
diff --git a/apps/api/plane/settings/__init__.py b/apps/api/plane/settings/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/settings/__init__.py
+++ b/apps/api/plane/settings/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/settings/common.py b/apps/api/plane/settings/common.py
index 8f28b9e986..9d651bd1b4 100644
--- a/apps/api/plane/settings/common.py
+++ b/apps/api/plane/settings/common.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Global Settings"""
# Python imports
@@ -380,6 +384,7 @@ ATTACHMENT_MIME_TYPES = [
"application/vnd.ms-powerpoint",
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
"text/plain",
+ "text/markdown",
"application/rtf",
"application/vnd.oasis.opendocument.spreadsheet",
"application/vnd.oasis.opendocument.text",
@@ -447,6 +452,8 @@ ATTACHMENT_MIME_TYPES = [
"application/x-sql",
# Gzip
"application/x-gzip",
+ # Markdown
+ "text/markdown",
]
# Seed directory path
diff --git a/apps/api/plane/settings/local.py b/apps/api/plane/settings/local.py
index 15f05aa3d7..dc4135bc13 100644
--- a/apps/api/plane/settings/local.py
+++ b/apps/api/plane/settings/local.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Development settings"""
import os
@@ -76,6 +80,11 @@ LOGGING = {
"handlers": ["console"],
"propagate": False,
},
+ "plane.authentication": {
+ "level": "INFO",
+ "handlers": ["console"],
+ "propagate": False,
+ },
"plane.migrations": {
"level": "INFO",
"handlers": ["console"],
diff --git a/apps/api/plane/settings/mongo.py b/apps/api/plane/settings/mongo.py
index 879d0c436d..7855a52d51 100644
--- a/apps/api/plane/settings/mongo.py
+++ b/apps/api/plane/settings/mongo.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
import logging
diff --git a/apps/api/plane/settings/openapi.py b/apps/api/plane/settings/openapi.py
index b79daeecf3..a1961a0c58 100644
--- a/apps/api/plane/settings/openapi.py
+++ b/apps/api/plane/settings/openapi.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
OpenAPI/Swagger configuration for drf-spectacular.
diff --git a/apps/api/plane/settings/production.py b/apps/api/plane/settings/production.py
index 8df7ae9060..7f3f90d650 100644
--- a/apps/api/plane/settings/production.py
+++ b/apps/api/plane/settings/production.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Production settings"""
import os
@@ -86,6 +90,11 @@ LOGGING = {
"handlers": ["console"],
"propagate": False,
},
+ "plane.authentication": {
+ "level": "DEBUG" if DEBUG else "INFO",
+ "handlers": ["console"],
+ "propagate": False,
+ },
"plane.migrations": {
"level": "DEBUG" if DEBUG else "INFO",
"handlers": ["console"],
diff --git a/apps/api/plane/settings/redis.py b/apps/api/plane/settings/redis.py
index 628a3d8e63..6c7e613f04 100644
--- a/apps/api/plane/settings/redis.py
+++ b/apps/api/plane/settings/redis.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import redis
from django.conf import settings
from urllib.parse import urlparse
diff --git a/apps/api/plane/settings/storage.py b/apps/api/plane/settings/storage.py
index 4ebf6c58e8..e4a978bd2b 100644
--- a/apps/api/plane/settings/storage.py
+++ b/apps/api/plane/settings/storage.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import uuid
diff --git a/apps/api/plane/settings/test.py b/apps/api/plane/settings/test.py
index 6a75f7904d..a8e431338b 100644
--- a/apps/api/plane/settings/test.py
+++ b/apps/api/plane/settings/test.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Test Settings"""
from .common import * # noqa
diff --git a/apps/api/plane/space/__init__.py b/apps/api/plane/space/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/space/__init__.py
+++ b/apps/api/plane/space/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/space/apps.py b/apps/api/plane/space/apps.py
index 6f1e76c51c..dd178e3344 100644
--- a/apps/api/plane/space/apps.py
+++ b/apps/api/plane/space/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/space/serializer/__init__.py b/apps/api/plane/space/serializer/__init__.py
index a3fe1029f3..e571ac011d 100644
--- a/apps/api/plane/space/serializer/__init__.py
+++ b/apps/api/plane/space/serializer/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .user import UserLiteSerializer
from .issue import LabelLiteSerializer, IssuePublicSerializer
diff --git a/apps/api/plane/space/serializer/base.py b/apps/api/plane/space/serializer/base.py
index 4b92b06fc3..9f30a7a839 100644
--- a/apps/api/plane/space/serializer/base.py
+++ b/apps/api/plane/space/serializer/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework import serializers
diff --git a/apps/api/plane/space/serializer/cycle.py b/apps/api/plane/space/serializer/cycle.py
index afa760a593..617ac08428 100644
--- a/apps/api/plane/space/serializer/cycle.py
+++ b/apps/api/plane/space/serializer/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import Cycle
diff --git a/apps/api/plane/space/serializer/intake.py b/apps/api/plane/space/serializer/intake.py
index 444c20d429..cf22cebbb1 100644
--- a/apps/api/plane/space/serializer/intake.py
+++ b/apps/api/plane/space/serializer/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework import serializers
diff --git a/apps/api/plane/space/serializer/issue.py b/apps/api/plane/space/serializer/issue.py
index a89846cfc7..51dd1f41d1 100644
--- a/apps/api/plane/space/serializer/issue.py
+++ b/apps/api/plane/space/serializer/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils import timezone
@@ -193,7 +197,7 @@ class IssueFlatSerializer(BaseSerializer):
fields = [
"id",
"name",
- "description",
+ "description_json",
"description_html",
"priority",
"start_date",
diff --git a/apps/api/plane/space/serializer/module.py b/apps/api/plane/space/serializer/module.py
index 53840f0782..81ba93c136 100644
--- a/apps/api/plane/space/serializer/module.py
+++ b/apps/api/plane/space/serializer/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import Module
diff --git a/apps/api/plane/space/serializer/project.py b/apps/api/plane/space/serializer/project.py
index f79eef686d..62be19f4f4 100644
--- a/apps/api/plane/space/serializer/project.py
+++ b/apps/api/plane/space/serializer/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import Project
diff --git a/apps/api/plane/space/serializer/state.py b/apps/api/plane/space/serializer/state.py
index 184f48b407..410b408f0b 100644
--- a/apps/api/plane/space/serializer/state.py
+++ b/apps/api/plane/space/serializer/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import State
diff --git a/apps/api/plane/space/serializer/user.py b/apps/api/plane/space/serializer/user.py
index 9b707a3434..4ecbad80e9 100644
--- a/apps/api/plane/space/serializer/user.py
+++ b/apps/api/plane/space/serializer/user.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import User
diff --git a/apps/api/plane/space/serializer/workspace.py b/apps/api/plane/space/serializer/workspace.py
index 4945af96af..c63dfe2a5a 100644
--- a/apps/api/plane/space/serializer/workspace.py
+++ b/apps/api/plane/space/serializer/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Module imports
from .base import BaseSerializer
from plane.db.models import Workspace
diff --git a/apps/api/plane/space/urls/__init__.py b/apps/api/plane/space/urls/__init__.py
index d9a1f6ec33..06d3a117a1 100644
--- a/apps/api/plane/space/urls/__init__.py
+++ b/apps/api/plane/space/urls/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .intake import urlpatterns as intake_urls
from .issue import urlpatterns as issue_urls
from .project import urlpatterns as project_urls
diff --git a/apps/api/plane/space/urls/asset.py b/apps/api/plane/space/urls/asset.py
index 2a5c30a221..050aeb4abc 100644
--- a/apps/api/plane/space/urls/asset.py
+++ b/apps/api/plane/space/urls/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.urls import path
diff --git a/apps/api/plane/space/urls/intake.py b/apps/api/plane/space/urls/intake.py
index 59fda12e29..470f7f7b7d 100644
--- a/apps/api/plane/space/urls/intake.py
+++ b/apps/api/plane/space/urls/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/space/urls/issue.py b/apps/api/plane/space/urls/issue.py
index bb63e66953..5ea7671c25 100644
--- a/apps/api/plane/space/urls/issue.py
+++ b/apps/api/plane/space/urls/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/space/urls/project.py b/apps/api/plane/space/urls/project.py
index 068b8c5c17..1d58aba421 100644
--- a/apps/api/plane/space/urls/project.py
+++ b/apps/api/plane/space/urls/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
diff --git a/apps/api/plane/space/utils/grouper.py b/apps/api/plane/space/utils/grouper.py
index f8e2c50a44..e5f893bd5b 100644
--- a/apps/api/plane/space/utils/grouper.py
+++ b/apps/api/plane/space/utils/grouper.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
diff --git a/apps/api/plane/space/views/__init__.py b/apps/api/plane/space/views/__init__.py
index 22acfd15bd..f70d094deb 100644
--- a/apps/api/plane/space/views/__init__.py
+++ b/apps/api/plane/space/views/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .project import (
ProjectDeployBoardPublicSettingsEndpoint,
WorkspaceProjectDeployBoardEndpoint,
diff --git a/apps/api/plane/space/views/asset.py b/apps/api/plane/space/views/asset.py
index faabd97ab6..1749a8fd46 100644
--- a/apps/api/plane/space/views/asset.py
+++ b/apps/api/plane/space/views/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
diff --git a/apps/api/plane/space/views/base.py b/apps/api/plane/space/views/base.py
index 9be6a2e107..cf8cdbdc5c 100644
--- a/apps/api/plane/space/views/base.py
+++ b/apps/api/plane/space/views/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import zoneinfo
from django.conf import settings
diff --git a/apps/api/plane/space/views/cycle.py b/apps/api/plane/space/views/cycle.py
index 505c17ba40..72bec30641 100644
--- a/apps/api/plane/space/views/cycle.py
+++ b/apps/api/plane/space/views/cycle.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework import status
from rest_framework.permissions import AllowAny
diff --git a/apps/api/plane/space/views/intake.py b/apps/api/plane/space/views/intake.py
index 7ea2dee91f..4d9913193c 100644
--- a/apps/api/plane/space/views/intake.py
+++ b/apps/api/plane/space/views/intake.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
@@ -140,7 +144,7 @@ class IntakeIssuePublicViewSet(BaseViewSet):
# create an issue
issue = Issue.objects.create(
name=request.data.get("issue", {}).get("name"),
- description=request.data.get("issue", {}).get("description", {}),
+ description_json=request.data.get("issue", {}).get("description_json", {}),
description_html=request.data.get("issue", {}).get("description_html", "
"),
priority=request.data.get("issue", {}).get("priority", "low"),
project_id=project_deploy_board.project_id,
@@ -201,7 +205,7 @@ class IntakeIssuePublicViewSet(BaseViewSet):
issue_data = {
"name": issue_data.get("name", issue.name),
"description_html": issue_data.get("description_html", issue.description_html),
- "description": issue_data.get("description", issue.description),
+ "description_json": issue_data.get("description_json", issue.description_json),
}
issue_serializer = IssueCreateSerializer(
diff --git a/apps/api/plane/space/views/issue.py b/apps/api/plane/space/views/issue.py
index 220fc13073..9e2187466a 100644
--- a/apps/api/plane/space/views/issue.py
+++ b/apps/api/plane/space/views/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
@@ -744,7 +748,7 @@ class IssueRetrievePublicEndpoint(BaseAPIView):
"name",
"state_id",
"sort_order",
- "description",
+ "description_json",
"description_html",
"description_stripped",
"description_binary",
diff --git a/apps/api/plane/space/views/label.py b/apps/api/plane/space/views/label.py
index 51ddb832e4..f7cde57eb3 100644
--- a/apps/api/plane/space/views/label.py
+++ b/apps/api/plane/space/views/label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.response import Response
from rest_framework import status
diff --git a/apps/api/plane/space/views/meta.py b/apps/api/plane/space/views/meta.py
index be612db700..740bed19f3 100644
--- a/apps/api/plane/space/views/meta.py
+++ b/apps/api/plane/space/views/meta.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# third party
from rest_framework.permissions import AllowAny
from rest_framework import status
diff --git a/apps/api/plane/space/views/module.py b/apps/api/plane/space/views/module.py
index 7c4628f64f..2df0166aca 100644
--- a/apps/api/plane/space/views/module.py
+++ b/apps/api/plane/space/views/module.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework import status
from rest_framework.permissions import AllowAny
diff --git a/apps/api/plane/space/views/project.py b/apps/api/plane/space/views/project.py
index 6f332781ff..168c42624f 100644
--- a/apps/api/plane/space/views/project.py
+++ b/apps/api/plane/space/views/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Exists, OuterRef
@@ -63,6 +67,11 @@ class ProjectMembersEndpoint(BaseAPIView):
def get(self, request, anchor):
deploy_board = DeployBoard.objects.filter(anchor=anchor).first()
+ if not deploy_board:
+ return Response(
+ {"error": "Invalid anchor"},
+ status=status.HTTP_404_NOT_FOUND,
+ )
members = ProjectMember.objects.filter(
project=deploy_board.project,
@@ -71,10 +80,7 @@ class ProjectMembersEndpoint(BaseAPIView):
).values(
"id",
"member",
- "member__first_name",
- "member__last_name",
"member__display_name",
- "project",
- "workspace",
+ "member__avatar",
)
return Response(members, status=status.HTTP_200_OK)
diff --git a/apps/api/plane/space/views/state.py b/apps/api/plane/space/views/state.py
index c131866004..05b791475c 100644
--- a/apps/api/plane/space/views/state.py
+++ b/apps/api/plane/space/views/state.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.db.models import Q
diff --git a/apps/api/plane/static/logos/github_32px.png b/apps/api/plane/static/logos/github_32px.png
new file mode 100644
index 0000000000..4a9e5ab8ce
Binary files /dev/null and b/apps/api/plane/static/logos/github_32px.png differ
diff --git a/apps/api/plane/static/logos/linkedin_32px.png b/apps/api/plane/static/logos/linkedin_32px.png
new file mode 100644
index 0000000000..396e9327dc
Binary files /dev/null and b/apps/api/plane/static/logos/linkedin_32px.png differ
diff --git a/apps/api/plane/static/logos/twitter_32px.png b/apps/api/plane/static/logos/twitter_32px.png
new file mode 100644
index 0000000000..537562ea71
Binary files /dev/null and b/apps/api/plane/static/logos/twitter_32px.png differ
diff --git a/apps/api/plane/static/logos/website_32px.png b/apps/api/plane/static/logos/website_32px.png
new file mode 100644
index 0000000000..970a13f1c6
Binary files /dev/null and b/apps/api/plane/static/logos/website_32px.png differ
diff --git a/apps/api/plane/tests/__init__.py b/apps/api/plane/tests/__init__.py
index 73d90cd21b..5f9223043a 100644
--- a/apps/api/plane/tests/__init__.py
+++ b/apps/api/plane/tests/__init__.py
@@ -1 +1,5 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Test package initialization
diff --git a/apps/api/plane/tests/apps.py b/apps/api/plane/tests/apps.py
index 577414e63a..9669869696 100644
--- a/apps/api/plane/tests/apps.py
+++ b/apps/api/plane/tests/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/tests/conftest.py b/apps/api/plane/tests/conftest.py
index abfede197c..870779c42d 100644
--- a/apps/api/plane/tests/conftest.py
+++ b/apps/api/plane/tests/conftest.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from rest_framework.test import APIClient
from pytest_django.fixtures import django_db_setup
diff --git a/apps/api/plane/tests/conftest_external.py b/apps/api/plane/tests/conftest_external.py
index cebb768ca5..cd5469caa6 100644
--- a/apps/api/plane/tests/conftest_external.py
+++ b/apps/api/plane/tests/conftest_external.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from unittest.mock import MagicMock, patch
diff --git a/apps/api/plane/tests/contract/__init__.py b/apps/api/plane/tests/contract/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/contract/__init__.py
+++ b/apps/api/plane/tests/contract/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/contract/api/__init__.py b/apps/api/plane/tests/contract/api/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/contract/api/__init__.py
+++ b/apps/api/plane/tests/contract/api/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/contract/api/test_cycles.py b/apps/api/plane/tests/contract/api/test_cycles.py
index 644fe2bef9..d0138de8b8 100644
--- a/apps/api/plane/tests/contract/api/test_cycles.py
+++ b/apps/api/plane/tests/contract/api/test_cycles.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from rest_framework import status
from django.utils import timezone
diff --git a/apps/api/plane/tests/contract/api/test_labels.py b/apps/api/plane/tests/contract/api/test_labels.py
index a3a43d90aa..db5340dfdf 100644
--- a/apps/api/plane/tests/contract/api/test_labels.py
+++ b/apps/api/plane/tests/contract/api/test_labels.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from rest_framework import status
from uuid import uuid4
diff --git a/apps/api/plane/tests/contract/app/__init__.py b/apps/api/plane/tests/contract/app/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/contract/app/__init__.py
+++ b/apps/api/plane/tests/contract/app/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/contract/app/test_api_token.py b/apps/api/plane/tests/contract/app/test_api_token.py
index 35d92b11e1..ed071b98cb 100644
--- a/apps/api/plane/tests/contract/app/test_api_token.py
+++ b/apps/api/plane/tests/contract/app/test_api_token.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from datetime import timedelta
from uuid import uuid4
@@ -138,7 +142,7 @@ class TestApiTokenEndpoint:
"""Test retrieving a specific API token"""
# Arrange
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
# Act
response = session_client.get(url)
@@ -155,7 +159,7 @@ class TestApiTokenEndpoint:
# Arrange
session_client.force_authenticate(user=create_user)
fake_pk = uuid4()
- url = reverse("api-tokens", kwargs={"pk": fake_pk})
+ url = reverse("api-tokens-details", kwargs={"pk": fake_pk})
# Act
response = session_client.get(url)
@@ -174,7 +178,7 @@ class TestApiTokenEndpoint:
other_user = User.objects.create(email=unique_email, username=unique_username)
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": other_token.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": other_token.pk})
# Act
response = session_client.get(url)
@@ -188,7 +192,7 @@ class TestApiTokenEndpoint:
"""Test successful API token deletion"""
# Arrange
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
# Act
response = session_client.delete(url)
@@ -203,7 +207,7 @@ class TestApiTokenEndpoint:
# Arrange
session_client.force_authenticate(user=create_user)
fake_pk = uuid4()
- url = reverse("api-tokens", kwargs={"pk": fake_pk})
+ url = reverse("api-tokens-details", kwargs={"pk": fake_pk})
# Act
response = session_client.delete(url)
@@ -222,7 +226,7 @@ class TestApiTokenEndpoint:
other_user = User.objects.create(email=unique_email, username=unique_username)
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": other_token.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": other_token.pk})
# Act
response = session_client.delete(url)
@@ -238,7 +242,7 @@ class TestApiTokenEndpoint:
# Arrange
service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True)
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": service_token.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": service_token.pk})
# Act
response = session_client.delete(url)
@@ -254,7 +258,7 @@ class TestApiTokenEndpoint:
"""Test successful API token update"""
# Arrange
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
update_data = {
"label": "Updated Token Label",
"description": "Updated description",
@@ -278,7 +282,7 @@ class TestApiTokenEndpoint:
"""Test partial API token update"""
# Arrange
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": create_api_token_for_user.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
original_description = create_api_token_for_user.description
update_data = {"label": "Only Label Updated"}
@@ -296,7 +300,7 @@ class TestApiTokenEndpoint:
# Arrange
session_client.force_authenticate(user=create_user)
fake_pk = uuid4()
- url = reverse("api-tokens", kwargs={"pk": fake_pk})
+ url = reverse("api-tokens-details", kwargs={"pk": fake_pk})
update_data = {"label": "New Label"}
# Act
@@ -316,7 +320,7 @@ class TestApiTokenEndpoint:
other_user = User.objects.create(email=unique_email, username=unique_username)
other_token = APIToken.objects.create(label="Other Token", user=other_user, user_type=0)
session_client.force_authenticate(user=create_user)
- url = reverse("api-tokens", kwargs={"pk": other_token.pk})
+ url = reverse("api-tokens-details", kwargs={"pk": other_token.pk})
update_data = {"label": "Hacked Label"}
# Act
@@ -329,6 +333,56 @@ class TestApiTokenEndpoint:
other_token.refresh_from_db()
assert other_token.label == "Other Token"
+ @pytest.mark.django_db
+ def test_patch_cannot_modify_token(self, session_client, create_user, create_api_token_for_user):
+ """Test that token value cannot be modified via PATCH"""
+ # Arrange
+ session_client.force_authenticate(user=create_user)
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
+ original_token = create_api_token_for_user.token
+ update_data = {"token": "plane_api_malicious_token_value"}
+
+ # Act
+ response = session_client.patch(url, update_data, format="json")
+
+ # Assert
+ assert response.status_code == status.HTTP_200_OK
+ create_api_token_for_user.refresh_from_db()
+ assert create_api_token_for_user.token == original_token
+
+ @pytest.mark.django_db
+ def test_patch_cannot_modify_user_type(self, session_client, create_user, create_api_token_for_user):
+ """Test that user_type cannot be modified via PATCH"""
+ # Arrange
+ session_client.force_authenticate(user=create_user)
+ url = reverse("api-tokens-details", kwargs={"pk": create_api_token_for_user.pk})
+ update_data = {"user_type": 1}
+
+ # Act
+ response = session_client.patch(url, update_data, format="json")
+
+ # Assert
+ assert response.status_code == status.HTTP_200_OK
+ create_api_token_for_user.refresh_from_db()
+ assert create_api_token_for_user.user_type == 0
+
+ @pytest.mark.django_db
+ def test_patch_cannot_modify_service_token(self, session_client, create_user):
+ """Test that service tokens cannot be modified through user token endpoint"""
+ # Arrange
+ service_token = APIToken.objects.create(label="Service Token", user=create_user, user_type=0, is_service=True)
+ session_client.force_authenticate(user=create_user)
+ url = reverse("api-tokens-details", kwargs={"pk": service_token.pk})
+ update_data = {"label": "Hacked Service Token"}
+
+ # Act
+ response = session_client.patch(url, update_data, format="json")
+
+ # Assert
+ assert response.status_code == status.HTTP_404_NOT_FOUND
+ service_token.refresh_from_db()
+ assert service_token.label == "Service Token"
+
# Authentication tests
@pytest.mark.django_db
def test_all_endpoints_require_authentication(self, api_client):
@@ -337,9 +391,9 @@ class TestApiTokenEndpoint:
endpoints = [
(reverse("api-tokens"), "get"),
(reverse("api-tokens"), "post"),
- (reverse("api-tokens", kwargs={"pk": uuid4()}), "get"),
- (reverse("api-tokens", kwargs={"pk": uuid4()}), "patch"),
- (reverse("api-tokens", kwargs={"pk": uuid4()}), "delete"),
+ (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "get"),
+ (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "patch"),
+ (reverse("api-tokens-details", kwargs={"pk": uuid4()}), "delete"),
]
# Act & Assert
diff --git a/apps/api/plane/tests/contract/app/test_authentication.py b/apps/api/plane/tests/contract/app/test_authentication.py
index 1c044f1928..808416b028 100644
--- a/apps/api/plane/tests/contract/app/test_authentication.py
+++ b/apps/api/plane/tests/contract/app/test_authentication.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import json
import uuid
import pytest
diff --git a/apps/api/plane/tests/contract/app/test_project_app.py b/apps/api/plane/tests/contract/app/test_project_app.py
index 38b0f51f3b..979c5e805c 100644
--- a/apps/api/plane/tests/contract/app/test_project_app.py
+++ b/apps/api/plane/tests/contract/app/test_project_app.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from rest_framework import status
import uuid
@@ -6,7 +10,7 @@ from django.utils import timezone
from plane.db.models import (
Project,
ProjectMember,
- IssueUserProperty,
+ ProjectUserProperty,
State,
WorkspaceMember,
User,
@@ -82,8 +86,8 @@ class TestProjectAPIPost(TestProjectBase):
assert project_member.role == 20 # Administrator
assert project_member.is_active is True
- # Verify IssueUserProperty was created
- assert IssueUserProperty.objects.filter(project=project, user=user).exists()
+ # Verify ProjectUserProperty was created
+ assert ProjectUserProperty.objects.filter(project=project, user=user).exists()
# Verify default states were created
states = State.objects.filter(project=project)
@@ -116,8 +120,8 @@ class TestProjectAPIPost(TestProjectBase):
project = Project.objects.get(name=project_data["name"])
assert ProjectMember.objects.filter(project=project, role=20).count() == 2
- # Verify both have IssueUserProperty
- assert IssueUserProperty.objects.filter(project=project).count() == 2
+ # Verify both have ProjectUserProperty
+ assert ProjectUserProperty.objects.filter(project=project).count() == 2
@pytest.mark.django_db
def test_create_project_guest_forbidden(self, session_client, workspace):
diff --git a/apps/api/plane/tests/contract/app/test_workspace_app.py b/apps/api/plane/tests/contract/app/test_workspace_app.py
index 47b0497952..427bad60b6 100644
--- a/apps/api/plane/tests/contract/app/test_workspace_app.py
+++ b/apps/api/plane/tests/contract/app/test_workspace_app.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from django.urls import reverse
from rest_framework import status
diff --git a/apps/api/plane/tests/factories.py b/apps/api/plane/tests/factories.py
index b8cd78361a..4d39d832fa 100644
--- a/apps/api/plane/tests/factories.py
+++ b/apps/api/plane/tests/factories.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import factory
from uuid import uuid4
from django.utils import timezone
diff --git a/apps/api/plane/tests/smoke/__init__.py b/apps/api/plane/tests/smoke/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/smoke/__init__.py
+++ b/apps/api/plane/tests/smoke/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/smoke/test_auth_smoke.py b/apps/api/plane/tests/smoke/test_auth_smoke.py
index c5a671e9af..1537db79f7 100644
--- a/apps/api/plane/tests/smoke/test_auth_smoke.py
+++ b/apps/api/plane/tests/smoke/test_auth_smoke.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
import requests
from django.urls import reverse
diff --git a/apps/api/plane/tests/unit/__init__.py b/apps/api/plane/tests/unit/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/__init__.py
+++ b/apps/api/plane/tests/unit/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py b/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py
index 9886036599..c153703baa 100644
--- a/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py
+++ b/apps/api/plane/tests/unit/bg_tasks/test_copy_s3_objects.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from plane.db.models import Project, ProjectMember, Issue, FileAsset
from unittest.mock import patch, MagicMock
diff --git a/apps/api/plane/tests/unit/middleware/__init__.py b/apps/api/plane/tests/unit/middleware/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/middleware/__init__.py
+++ b/apps/api/plane/tests/unit/middleware/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/middleware/test_db_routing.py b/apps/api/plane/tests/unit/middleware/test_db_routing.py
index 5ac71696ac..9f5439e75c 100644
--- a/apps/api/plane/tests/unit/middleware/test_db_routing.py
+++ b/apps/api/plane/tests/unit/middleware/test_db_routing.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Unit tests for ReadReplicaRoutingMiddleware.
This module contains comprehensive tests for the ReadReplicaRoutingMiddleware
diff --git a/apps/api/plane/tests/unit/models/__init__.py b/apps/api/plane/tests/unit/models/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/models/__init__.py
+++ b/apps/api/plane/tests/unit/models/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/models/test_issue_comment_modal.py b/apps/api/plane/tests/unit/models/test_issue_comment_modal.py
index 98a0b05b24..37f743d764 100644
--- a/apps/api/plane/tests/unit/models/test_issue_comment_modal.py
+++ b/apps/api/plane/tests/unit/models/test_issue_comment_modal.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from plane.db.models import IssueComment, Description, Project, Issue, Workspace, State
diff --git a/apps/api/plane/tests/unit/models/test_workspace_model.py b/apps/api/plane/tests/unit/models/test_workspace_model.py
index 26a7975126..405538cfbe 100644
--- a/apps/api/plane/tests/unit/models/test_workspace_model.py
+++ b/apps/api/plane/tests/unit/models/test_workspace_model.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from uuid import uuid4
diff --git a/apps/api/plane/tests/unit/serializers/__init__.py b/apps/api/plane/tests/unit/serializers/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/serializers/__init__.py
+++ b/apps/api/plane/tests/unit/serializers/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py b/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py
index eac92384b3..59a909eeb7 100644
--- a/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py
+++ b/apps/api/plane/tests/unit/serializers/test_issue_recent_visit.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from plane.db.models import (
diff --git a/apps/api/plane/tests/unit/serializers/test_label.py b/apps/api/plane/tests/unit/serializers/test_label.py
index 4775ef49ad..a4ebc88752 100644
--- a/apps/api/plane/tests/unit/serializers/test_label.py
+++ b/apps/api/plane/tests/unit/serializers/test_label.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from plane.app.serializers import LabelSerializer
from plane.db.models import Project, Label
diff --git a/apps/api/plane/tests/unit/serializers/test_workspace.py b/apps/api/plane/tests/unit/serializers/test_workspace.py
index 21844c714b..f59667f701 100644
--- a/apps/api/plane/tests/unit/serializers/test_workspace.py
+++ b/apps/api/plane/tests/unit/serializers/test_workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from uuid import uuid4
diff --git a/apps/api/plane/tests/unit/settings/__init__.py b/apps/api/plane/tests/unit/settings/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/settings/__init__.py
+++ b/apps/api/plane/tests/unit/settings/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/settings/test_storage.py b/apps/api/plane/tests/unit/settings/test_storage.py
index fe8cf43f8b..00856aeecb 100644
--- a/apps/api/plane/tests/unit/settings/test_storage.py
+++ b/apps/api/plane/tests/unit/settings/test_storage.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import os
from unittest.mock import Mock, patch
import pytest
diff --git a/apps/api/plane/tests/unit/utils/__init__.py b/apps/api/plane/tests/unit/utils/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/tests/unit/utils/__init__.py
+++ b/apps/api/plane/tests/unit/utils/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/tests/unit/utils/test_url.py b/apps/api/plane/tests/unit/utils/test_url.py
index 465cb3023b..82b5b106d0 100644
--- a/apps/api/plane/tests/unit/utils/test_url.py
+++ b/apps/api/plane/tests/unit/utils/test_url.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pytest
from plane.utils.url import (
contains_url,
diff --git a/apps/api/plane/tests/unit/utils/test_uuid.py b/apps/api/plane/tests/unit/utils/test_uuid.py
index d47e59c4b7..33ddebb921 100644
--- a/apps/api/plane/tests/unit/utils/test_uuid.py
+++ b/apps/api/plane/tests/unit/utils/test_uuid.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import uuid
import pytest
from plane.utils.uuid import is_valid_uuid, convert_uuid_to_integer
diff --git a/apps/api/plane/throttles/asset.py b/apps/api/plane/throttles/asset.py
index 4846500493..bdc3be799f 100644
--- a/apps/api/plane/throttles/asset.py
+++ b/apps/api/plane/throttles/asset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from rest_framework.throttling import SimpleRateThrottle
diff --git a/apps/api/plane/urls.py b/apps/api/plane/urls.py
index 4b1062559a..f5e43408cb 100644
--- a/apps/api/plane/urls.py
+++ b/apps/api/plane/urls.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""plane URL Configuration"""
from django.conf import settings
diff --git a/apps/api/plane/utils/__init__.py b/apps/api/plane/utils/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/utils/__init__.py
+++ b/apps/api/plane/utils/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/utils/analytics_events.py b/apps/api/plane/utils/analytics_events.py
index 7fa8af9493..ce06ba92e6 100644
--- a/apps/api/plane/utils/analytics_events.py
+++ b/apps/api/plane/utils/analytics_events.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
USER_JOINED_WORKSPACE = "user_joined_workspace"
USER_INVITED_TO_WORKSPACE = "user_invited_to_workspace"
WORKSPACE_CREATED = "workspace_created"
diff --git a/apps/api/plane/utils/analytics_plot.py b/apps/api/plane/utils/analytics_plot.py
index 12fa39cc03..acd86aca86 100644
--- a/apps/api/plane/utils/analytics_plot.py
+++ b/apps/api/plane/utils/analytics_plot.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from datetime import timedelta
from itertools import groupby
diff --git a/apps/api/plane/utils/build_chart.py b/apps/api/plane/utils/build_chart.py
index 573636483b..bf4d1cf2b6 100644
--- a/apps/api/plane/utils/build_chart.py
+++ b/apps/api/plane/utils/build_chart.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from typing import Dict, Any, Tuple, Optional, List, Union
diff --git a/apps/api/plane/utils/cache.py b/apps/api/plane/utils/cache.py
index da3fd45177..9ff5db6d90 100644
--- a/apps/api/plane/utils/cache.py
+++ b/apps/api/plane/utils/cache.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
from functools import wraps
diff --git a/apps/api/plane/utils/color.py b/apps/api/plane/utils/color.py
index 8c45389bdf..61a572dc00 100644
--- a/apps/api/plane/utils/color.py
+++ b/apps/api/plane/utils/color.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import random
import string
diff --git a/apps/api/plane/utils/constants.py b/apps/api/plane/utils/constants.py
index 0d5e64a20b..1ccc501ddc 100644
--- a/apps/api/plane/utils/constants.py
+++ b/apps/api/plane/utils/constants.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
RESTRICTED_WORKSPACE_SLUGS = [
"404",
"accounts",
diff --git a/apps/api/plane/utils/content_validator.py b/apps/api/plane/utils/content_validator.py
index 00e6c0c660..1b4ede2626 100644
--- a/apps/api/plane/utils/content_validator.py
+++ b/apps/api/plane/utils/content_validator.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import base64
import nh3
@@ -135,8 +139,6 @@ ATTRIBUTES = {
"rowspan",
"colwidth",
"background",
- "hideContent",
- "hidecontent",
"style",
},
"td": {
@@ -146,8 +148,6 @@ ATTRIBUTES = {
"background",
"textColor",
"textcolor",
- "hideContent",
- "hidecontent",
"style",
},
"tr": {"background", "textColor", "textcolor", "style"},
diff --git a/apps/api/plane/utils/core/__init__.py b/apps/api/plane/utils/core/__init__.py
index 37c6e3741e..7f119b62f9 100644
--- a/apps/api/plane/utils/core/__init__.py
+++ b/apps/api/plane/utils/core/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Core utilities for Plane database routing and request scoping.
This package contains essential components for managing read replica routing
diff --git a/apps/api/plane/utils/core/dbrouters.py b/apps/api/plane/utils/core/dbrouters.py
index e175683319..fdd00cca2c 100644
--- a/apps/api/plane/utils/core/dbrouters.py
+++ b/apps/api/plane/utils/core/dbrouters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Database router for read replica selection.
This router determines which database to use for read/write operations
diff --git a/apps/api/plane/utils/core/mixins/__init__.py b/apps/api/plane/utils/core/mixins/__init__.py
index cedd9d4551..73fe2ccc98 100644
--- a/apps/api/plane/utils/core/mixins/__init__.py
+++ b/apps/api/plane/utils/core/mixins/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Core mixins for read replica functionality.
This package provides mixins for different aspects of read replica management
diff --git a/apps/api/plane/utils/core/mixins/view.py b/apps/api/plane/utils/core/mixins/view.py
index e15ec6771d..4d923e1c13 100644
--- a/apps/api/plane/utils/core/mixins/view.py
+++ b/apps/api/plane/utils/core/mixins/view.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Mixins for Django REST Framework views.
"""
diff --git a/apps/api/plane/utils/core/request_scope.py b/apps/api/plane/utils/core/request_scope.py
index b09e77101f..b8b137120e 100644
--- a/apps/api/plane/utils/core/request_scope.py
+++ b/apps/api/plane/utils/core/request_scope.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Database routing utilities for read replica selection.
This module provides request-scoped context management for database routing,
diff --git a/apps/api/plane/utils/csv_utils.py b/apps/api/plane/utils/csv_utils.py
new file mode 100644
index 0000000000..26c6e89375
--- /dev/null
+++ b/apps/api/plane/utils/csv_utils.py
@@ -0,0 +1,26 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
+# CSV utility functions for safe export
+# Characters that trigger formula evaluation in spreadsheet applications
+_CSV_FORMULA_TRIGGERS = frozenset(("=", "+", "-", "@", "\t", "\r", "\n"))
+
+
+def sanitize_csv_value(value):
+ """Sanitize a value for CSV export to prevent formula injection.
+
+ Prefixes string values starting with formula-triggering characters
+ with a single quote so spreadsheet applications treat them as text
+ instead of evaluating them as formulas.
+
+ See: https://owasp.org/www-community/attacks/CSV_Injection
+ """
+ if isinstance(value, str) and value and value[0] in _CSV_FORMULA_TRIGGERS:
+ return "'" + value
+ return value
+
+
+def sanitize_csv_row(row):
+ """Sanitize all values in a CSV row."""
+ return [sanitize_csv_value(v) for v in row]
diff --git a/apps/api/plane/utils/cycle_transfer_issues.py b/apps/api/plane/utils/cycle_transfer_issues.py
index fda9f39b95..7963401382 100644
--- a/apps/api/plane/utils/cycle_transfer_issues.py
+++ b/apps/api/plane/utils/cycle_transfer_issues.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/utils/date_utils.py b/apps/api/plane/utils/date_utils.py
index f15e7f119b..d25d5b1eca 100644
--- a/apps/api/plane/utils/date_utils.py
+++ b/apps/api/plane/utils/date_utils.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from datetime import datetime, timedelta, date
from django.utils import timezone
from typing import Dict, Optional, List, Union, Tuple, Any
diff --git a/apps/api/plane/utils/email.py b/apps/api/plane/utils/email.py
new file mode 100644
index 0000000000..f950e94515
--- /dev/null
+++ b/apps/api/plane/utils/email.py
@@ -0,0 +1,42 @@
+# SPDX-FileCopyrightText: 2023-present Plane Software, Inc.
+# SPDX-License-Identifier: LicenseRef-Plane-Commercial
+#
+# Licensed under the Plane Commercial License (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+# https://plane.so/legals/eula
+#
+# DO NOT remove or modify this notice.
+# NOTICE: Proprietary and confidential. Unauthorized use or distribution is prohibited.
+
+# Python imports
+import re
+
+# Django imports
+from django.utils.html import strip_tags
+
+
+def generate_plain_text_from_html(html_content):
+ """
+ Generate clean plain text from HTML email template.
+ Removes all HTML tags, CSS styles, and excessive whitespace.
+
+ Args:
+ html_content (str): The HTML content to convert to plain text
+
+ Returns:
+ str: Clean plain text without HTML tags, styles, or excessive whitespace
+ """
+ # Remove style tags and their content
+ html_content = re.sub(r"", "", html_content, flags=re.DOTALL | re.IGNORECASE)
+
+ # Strip HTML tags
+ text_content = strip_tags(html_content)
+
+ # Remove excessive empty lines
+ text_content = re.sub(r"\n\s*\n\s*\n+", "\n\n", text_content)
+
+ # Ensure there's a leading and trailing whitespace
+ text_content = "\n\n" + text_content.lstrip().rstrip() + "\n\n"
+
+ return text_content
diff --git a/apps/api/plane/utils/error_codes.py b/apps/api/plane/utils/error_codes.py
index 15d38f6bf9..571f9d3687 100644
--- a/apps/api/plane/utils/error_codes.py
+++ b/apps/api/plane/utils/error_codes.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
ERROR_CODES = {
# issues
"INVALID_ARCHIVE_STATE_GROUP": 4091,
diff --git a/apps/api/plane/utils/exception_logger.py b/apps/api/plane/utils/exception_logger.py
index b0a6f8c38b..657afeb5ca 100644
--- a/apps/api/plane/utils/exception_logger.py
+++ b/apps/api/plane/utils/exception_logger.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import logging
import traceback
diff --git a/apps/api/plane/utils/exporters/__init__.py b/apps/api/plane/utils/exporters/__init__.py
index 9e7b1a9d51..632452a311 100644
--- a/apps/api/plane/utils/exporters/__init__.py
+++ b/apps/api/plane/utils/exporters/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Export utilities for various data formats."""
from .exporter import Exporter
diff --git a/apps/api/plane/utils/exporters/exporter.py b/apps/api/plane/utils/exporters/exporter.py
index 75b396cb4e..ff4df46c7f 100644
--- a/apps/api/plane/utils/exporters/exporter.py
+++ b/apps/api/plane/utils/exporters/exporter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from typing import Any, Dict, List, Type, Union
from django.db.models import QuerySet
diff --git a/apps/api/plane/utils/exporters/formatters.py b/apps/api/plane/utils/exporters/formatters.py
index fc7c23528b..611a60fca4 100644
--- a/apps/api/plane/utils/exporters/formatters.py
+++ b/apps/api/plane/utils/exporters/formatters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import csv
import io
import json
@@ -5,6 +9,9 @@ from typing import Any, Dict, List, Type
from openpyxl import Workbook
+# Module imports
+from plane.utils.csv_utils import sanitize_csv_row
+
class BaseFormatter:
"""Base class for export formatters."""
@@ -80,7 +87,7 @@ class CSVFormatter(BaseFormatter):
buf = io.StringIO()
writer = csv.writer(buf, delimiter=",", quoting=csv.QUOTE_ALL)
for row in data:
- writer.writerow(row)
+ writer.writerow(sanitize_csv_row(row))
buf.seek(0)
return buf.getvalue()
diff --git a/apps/api/plane/utils/exporters/schemas/__init__.py b/apps/api/plane/utils/exporters/schemas/__init__.py
index 98b2623aed..e792b3c6ff 100644
--- a/apps/api/plane/utils/exporters/schemas/__init__.py
+++ b/apps/api/plane/utils/exporters/schemas/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""Export schemas for various data types."""
from .base import (
diff --git a/apps/api/plane/utils/exporters/schemas/base.py b/apps/api/plane/utils/exporters/schemas/base.py
index 4e67c6980c..eacee3741a 100644
--- a/apps/api/plane/utils/exporters/schemas/base.py
+++ b/apps/api/plane/utils/exporters/schemas/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional
diff --git a/apps/api/plane/utils/exporters/schemas/issue.py b/apps/api/plane/utils/exporters/schemas/issue.py
index 744e330524..a3bda90b7b 100644
--- a/apps/api/plane/utils/exporters/schemas/issue.py
+++ b/apps/api/plane/utils/exporters/schemas/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from collections import defaultdict
from typing import Any, Dict, List, Optional
diff --git a/apps/api/plane/utils/filters/__init__.py b/apps/api/plane/utils/filters/__init__.py
index 76a96c82c0..cdcf8ac6e1 100644
--- a/apps/api/plane/utils/filters/__init__.py
+++ b/apps/api/plane/utils/filters/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Filters module for handling complex filtering operations
# Import all utilities from base modules
diff --git a/apps/api/plane/utils/filters/converters.py b/apps/api/plane/utils/filters/converters.py
index f7693b40ea..4d37c2b0b1 100644
--- a/apps/api/plane/utils/filters/converters.py
+++ b/apps/api/plane/utils/filters/converters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import re
import uuid
from datetime import datetime
diff --git a/apps/api/plane/utils/filters/filter_backend.py b/apps/api/plane/utils/filters/filter_backend.py
index 11ed48f718..c21560f70f 100644
--- a/apps/api/plane/utils/filters/filter_backend.py
+++ b/apps/api/plane/utils/filters/filter_backend.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import json
diff --git a/apps/api/plane/utils/filters/filter_migrations.py b/apps/api/plane/utils/filters/filter_migrations.py
index 3e424b6e67..555793dc2a 100644
--- a/apps/api/plane/utils/filters/filter_migrations.py
+++ b/apps/api/plane/utils/filters/filter_migrations.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Utilities for migrating legacy filters to rich filters format.
diff --git a/apps/api/plane/utils/filters/filterset.py b/apps/api/plane/utils/filters/filterset.py
index 0099b83d09..721bf4c7af 100644
--- a/apps/api/plane/utils/filters/filterset.py
+++ b/apps/api/plane/utils/filters/filterset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import copy
from django.db import models
diff --git a/apps/api/plane/utils/global_paginator.py b/apps/api/plane/utils/global_paginator.py
index 1b7f908c54..e9b68ba765 100644
--- a/apps/api/plane/utils/global_paginator.py
+++ b/apps/api/plane/utils/global_paginator.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# python imports
from math import ceil
diff --git a/apps/api/plane/utils/grouper.py b/apps/api/plane/utils/grouper.py
index 1ec004e95a..ab00879671 100644
--- a/apps/api/plane/utils/grouper.py
+++ b/apps/api/plane/utils/grouper.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.contrib.postgres.aggregates import ArrayAgg
from django.contrib.postgres.fields import ArrayField
diff --git a/apps/api/plane/utils/host.py b/apps/api/plane/utils/host.py
index 860e19e0e3..dafd19179e 100644
--- a/apps/api/plane/utils/host.py
+++ b/apps/api/plane/utils/host.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
diff --git a/apps/api/plane/utils/html_processor.py b/apps/api/plane/utils/html_processor.py
index 18d103b645..a26f6fe13c 100644
--- a/apps/api/plane/utils/html_processor.py
+++ b/apps/api/plane/utils/html_processor.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from io import StringIO
from html.parser import HTMLParser
diff --git a/apps/api/plane/utils/imports.py b/apps/api/plane/utils/imports.py
index 81de0203bb..af86c31e7d 100644
--- a/apps/api/plane/utils/imports.py
+++ b/apps/api/plane/utils/imports.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import pkgutil
import six
diff --git a/apps/api/plane/utils/instance_config_variables/__init__.py b/apps/api/plane/utils/instance_config_variables/__init__.py
index 6818ca9bf7..09882ae11c 100644
--- a/apps/api/plane/utils/instance_config_variables/__init__.py
+++ b/apps/api/plane/utils/instance_config_variables/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .core import core_config_variables
from .extended import extended_config_variables
diff --git a/apps/api/plane/utils/instance_config_variables/core.py b/apps/api/plane/utils/instance_config_variables/core.py
index 4f4833207a..274c6539af 100644
--- a/apps/api/plane/utils/instance_config_variables/core.py
+++ b/apps/api/plane/utils/instance_config_variables/core.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
diff --git a/apps/api/plane/utils/instance_config_variables/extended.py b/apps/api/plane/utils/instance_config_variables/extended.py
index 24c6fefda4..cf267aca24 100644
--- a/apps/api/plane/utils/instance_config_variables/extended.py
+++ b/apps/api/plane/utils/instance_config_variables/extended.py
@@ -1 +1,5 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
extended_config_variables = []
diff --git a/apps/api/plane/utils/ip_address.py b/apps/api/plane/utils/ip_address.py
index 01789c431e..3a0f171d79 100644
--- a/apps/api/plane/utils/ip_address.py
+++ b/apps/api/plane/utils/ip_address.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
def get_client_ip(request):
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
if x_forwarded_for:
diff --git a/apps/api/plane/utils/issue_filters.py b/apps/api/plane/utils/issue_filters.py
index 8d56bc3893..ea31a529bb 100644
--- a/apps/api/plane/utils/issue_filters.py
+++ b/apps/api/plane/utils/issue_filters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import re
import uuid
from datetime import timedelta
diff --git a/apps/api/plane/utils/issue_relation_mapper.py b/apps/api/plane/utils/issue_relation_mapper.py
index 19d65c1112..ecce5a2d12 100644
--- a/apps/api/plane/utils/issue_relation_mapper.py
+++ b/apps/api/plane/utils/issue_relation_mapper.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
def get_inverse_relation(relation_type):
relation_mapping = {
"start_after": "start_before",
diff --git a/apps/api/plane/utils/issue_search.py b/apps/api/plane/utils/issue_search.py
index 1e7543d885..7e5fab8fea 100644
--- a/apps/api/plane/utils/issue_search.py
+++ b/apps/api/plane/utils/issue_search.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import re
diff --git a/apps/api/plane/utils/logging.py b/apps/api/plane/utils/logging.py
index 083132f163..61312448d6 100644
--- a/apps/api/plane/utils/logging.py
+++ b/apps/api/plane/utils/logging.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import logging.handlers as handlers
import time
diff --git a/apps/api/plane/utils/markdown.py b/apps/api/plane/utils/markdown.py
index 188c54fec3..643dd77886 100644
--- a/apps/api/plane/utils/markdown.py
+++ b/apps/api/plane/utils/markdown.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import mistune
markdown = mistune.Markdown()
diff --git a/apps/api/plane/utils/openapi/__init__.py b/apps/api/plane/utils/openapi/__init__.py
index b2c9ba6b0c..d54caf584e 100644
--- a/apps/api/plane/utils/openapi/__init__.py
+++ b/apps/api/plane/utils/openapi/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
OpenAPI utilities for drf-spectacular integration.
diff --git a/apps/api/plane/utils/openapi/auth.py b/apps/api/plane/utils/openapi/auth.py
index 9434956fe8..6f7459ea2d 100644
--- a/apps/api/plane/utils/openapi/auth.py
+++ b/apps/api/plane/utils/openapi/auth.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
OpenAPI authentication extensions for drf-spectacular.
diff --git a/apps/api/plane/utils/openapi/decorators.py b/apps/api/plane/utils/openapi/decorators.py
index b11926889c..8b016f4c01 100644
--- a/apps/api/plane/utils/openapi/decorators.py
+++ b/apps/api/plane/utils/openapi/decorators.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Helper decorators for drf-spectacular OpenAPI documentation.
diff --git a/apps/api/plane/utils/openapi/examples.py b/apps/api/plane/utils/openapi/examples.py
index f41bdddbcb..5a2188e695 100644
--- a/apps/api/plane/utils/openapi/examples.py
+++ b/apps/api/plane/utils/openapi/examples.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Common OpenAPI examples for drf-spectacular.
diff --git a/apps/api/plane/utils/openapi/hooks.py b/apps/api/plane/utils/openapi/hooks.py
index f136324c0b..20319285b1 100644
--- a/apps/api/plane/utils/openapi/hooks.py
+++ b/apps/api/plane/utils/openapi/hooks.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Schema processing hooks for drf-spectacular OpenAPI generation.
diff --git a/apps/api/plane/utils/openapi/parameters.py b/apps/api/plane/utils/openapi/parameters.py
index 47db747ac7..d0ceba6c52 100644
--- a/apps/api/plane/utils/openapi/parameters.py
+++ b/apps/api/plane/utils/openapi/parameters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Common OpenAPI parameters for drf-spectacular.
diff --git a/apps/api/plane/utils/openapi/responses.py b/apps/api/plane/utils/openapi/responses.py
index 2a569e3778..cb0f81dce7 100644
--- a/apps/api/plane/utils/openapi/responses.py
+++ b/apps/api/plane/utils/openapi/responses.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Common OpenAPI responses for drf-spectacular.
diff --git a/apps/api/plane/utils/order_queryset.py b/apps/api/plane/utils/order_queryset.py
index 167cd0693d..abc0bbca0c 100644
--- a/apps/api/plane/utils/order_queryset.py
+++ b/apps/api/plane/utils/order_queryset.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.db.models import Case, CharField, Min, Value, When
# Custom ordering for priority and state
diff --git a/apps/api/plane/utils/paginator.py b/apps/api/plane/utils/paginator.py
index f3a7947567..5ae4d38150 100644
--- a/apps/api/plane/utils/paginator.py
+++ b/apps/api/plane/utils/paginator.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import math
from collections import defaultdict
diff --git a/apps/api/plane/utils/path_validator.py b/apps/api/plane/utils/path_validator.py
index ede3f11615..f15fb4ca95 100644
--- a/apps/api/plane/utils/path_validator.py
+++ b/apps/api/plane/utils/path_validator.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Django imports
from django.utils.http import url_has_allowed_host_and_scheme
from django.conf import settings
diff --git a/apps/api/plane/utils/permissions/__init__.py b/apps/api/plane/utils/permissions/__init__.py
index 849f7ba3ee..22d27694e9 100644
--- a/apps/api/plane/utils/permissions/__init__.py
+++ b/apps/api/plane/utils/permissions/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .workspace import (
WorkSpaceBasePermission,
WorkspaceOwnerPermission,
diff --git a/apps/api/plane/utils/permissions/base.py b/apps/api/plane/utils/permissions/base.py
index a2b1a18ff8..7b243cbb78 100644
--- a/apps/api/plane/utils/permissions/base.py
+++ b/apps/api/plane/utils/permissions/base.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.db.models import WorkspaceMember, ProjectMember
from functools import wraps
from rest_framework.response import Response
diff --git a/apps/api/plane/utils/permissions/page.py b/apps/api/plane/utils/permissions/page.py
index bea878f4c4..844ff4dafb 100644
--- a/apps/api/plane/utils/permissions/page.py
+++ b/apps/api/plane/utils/permissions/page.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from plane.db.models import ProjectMember, Page
from plane.app.permissions import ROLE
diff --git a/apps/api/plane/utils/permissions/project.py b/apps/api/plane/utils/permissions/project.py
index a8c0f92a27..55550b27ac 100644
--- a/apps/api/plane/utils/permissions/project.py
+++ b/apps/api/plane/utils/permissions/project.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.permissions import SAFE_METHODS, BasePermission
diff --git a/apps/api/plane/utils/permissions/workspace.py b/apps/api/plane/utils/permissions/workspace.py
index 8dc791c0cc..ada16ec3b5 100644
--- a/apps/api/plane/utils/permissions/workspace.py
+++ b/apps/api/plane/utils/permissions/workspace.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third Party imports
from rest_framework.permissions import BasePermission, SAFE_METHODS
diff --git a/apps/api/plane/utils/porters/__init__.py b/apps/api/plane/utils/porters/__init__.py
index cd411ff724..5e2cf79e83 100644
--- a/apps/api/plane/utils/porters/__init__.py
+++ b/apps/api/plane/utils/porters/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .formatters import BaseFormatter, CSVFormatter, JSONFormatter, XLSXFormatter
from .exporter import DataExporter
from .serializers import IssueExportSerializer
diff --git a/apps/api/plane/utils/porters/exporter.py b/apps/api/plane/utils/porters/exporter.py
index 3b55d4d984..394a2bb0fd 100644
--- a/apps/api/plane/utils/porters/exporter.py
+++ b/apps/api/plane/utils/porters/exporter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from typing import Dict, List, Union
from .formatters import BaseFormatter, CSVFormatter, JSONFormatter, XLSXFormatter
diff --git a/apps/api/plane/utils/porters/formatters.py b/apps/api/plane/utils/porters/formatters.py
index e130f73540..461a6a5e42 100644
--- a/apps/api/plane/utils/porters/formatters.py
+++ b/apps/api/plane/utils/porters/formatters.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
Import/Export System with Pluggable Formatters
@@ -14,6 +18,10 @@ from typing import Any, Dict, List, Union
from openpyxl import Workbook, load_workbook
+# Module imports
+from plane.utils.csv_utils import sanitize_csv_row, sanitize_csv_value
+
+
class BaseFormatter(ABC):
@abstractmethod
def encode(self, data: List[Dict]) -> Union[str, bytes]:
@@ -124,11 +132,12 @@ class CSVFormatter(BaseFormatter):
# Write data rows in the same field order
for row in data:
- writer.writerow([row.get(key, "") for key in fieldnames])
+ writer.writerow(sanitize_csv_row([row.get(key, "") for key in fieldnames]))
else:
writer = csv.DictWriter(output, fieldnames=fieldnames, delimiter=self.delimiter)
writer.writeheader()
- writer.writerows(data)
+ for row in data:
+ writer.writerow({k: sanitize_csv_value(row.get(k, "")) for k in fieldnames})
return output.getvalue()
diff --git a/apps/api/plane/utils/porters/serializers/__init__.py b/apps/api/plane/utils/porters/serializers/__init__.py
index a52e98d6dd..e4e4bb7623 100644
--- a/apps/api/plane/utils/porters/serializers/__init__.py
+++ b/apps/api/plane/utils/porters/serializers/__init__.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from .issue import IssueExportSerializer
__all__ = [
diff --git a/apps/api/plane/utils/porters/serializers/issue.py b/apps/api/plane/utils/porters/serializers/issue.py
index 94c6f065ae..31be812cc0 100644
--- a/apps/api/plane/utils/porters/serializers/issue.py
+++ b/apps/api/plane/utils/porters/serializers/issue.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Third party imports
from rest_framework import serializers
diff --git a/apps/api/plane/utils/telemetry.py b/apps/api/plane/utils/telemetry.py
index bec3d240dd..e3646eaba1 100644
--- a/apps/api/plane/utils/telemetry.py
+++ b/apps/api/plane/utils/telemetry.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import os
import atexit
diff --git a/apps/api/plane/utils/timezone_converter.py b/apps/api/plane/utils/timezone_converter.py
index 9a66742ed2..81aa3692db 100644
--- a/apps/api/plane/utils/timezone_converter.py
+++ b/apps/api/plane/utils/timezone_converter.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import pytz
from datetime import datetime, time
diff --git a/apps/api/plane/utils/url.py b/apps/api/plane/utils/url.py
index 773608bd3d..8381d65f9c 100644
--- a/apps/api/plane/utils/url.py
+++ b/apps/api/plane/utils/url.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import re
from typing import Optional
diff --git a/apps/api/plane/utils/uuid.py b/apps/api/plane/utils/uuid.py
index 03f695fdb1..2d95d59064 100644
--- a/apps/api/plane/utils/uuid.py
+++ b/apps/api/plane/utils/uuid.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
# Python imports
import uuid
import hashlib
diff --git a/apps/api/plane/web/__init__.py b/apps/api/plane/web/__init__.py
index e69de29bb2..917e26db4c 100644
--- a/apps/api/plane/web/__init__.py
+++ b/apps/api/plane/web/__init__.py
@@ -0,0 +1,4 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
diff --git a/apps/api/plane/web/apps.py b/apps/api/plane/web/apps.py
index a5861f9b5f..1193cd6ae8 100644
--- a/apps/api/plane/web/apps.py
+++ b/apps/api/plane/web/apps.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.apps import AppConfig
diff --git a/apps/api/plane/web/urls.py b/apps/api/plane/web/urls.py
index 28734ad91b..fe1f8951ae 100644
--- a/apps/api/plane/web/urls.py
+++ b/apps/api/plane/web/urls.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.urls import path
from plane.web.views import robots_txt, health_check
diff --git a/apps/api/plane/web/views.py b/apps/api/plane/web/views.py
index 8acb70a771..c2c42710e5 100644
--- a/apps/api/plane/web/views.py
+++ b/apps/api/plane/web/views.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
from django.http import HttpResponse, JsonResponse
diff --git a/apps/api/plane/wsgi.py b/apps/api/plane/wsgi.py
index b3051f9ff7..4c8a791636 100644
--- a/apps/api/plane/wsgi.py
+++ b/apps/api/plane/wsgi.py
@@ -1,3 +1,7 @@
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
"""
WSGI config for plane project.
diff --git a/apps/api/requirements/base.txt b/apps/api/requirements/base.txt
index 52c18c706c..ac32f0cb38 100644
--- a/apps/api/requirements/base.txt
+++ b/apps/api/requirements/base.txt
@@ -1,7 +1,7 @@
# base requirements
# django
-Django==4.2.27
+Django==4.2.29
# rest framework
djangorestframework==3.15.2
# postgres
@@ -51,7 +51,7 @@ beautifulsoup4==4.12.3
# analytics
posthog==3.5.0
# crypto
-cryptography==44.0.1
+cryptography==46.0.5
# html validator
lxml==6.0.0
# s3
diff --git a/apps/api/run_tests.py b/apps/api/run_tests.py
index b92f9fe5ba..886e8a0412 100755
--- a/apps/api/run_tests.py
+++ b/apps/api/run_tests.py
@@ -1,4 +1,8 @@
#!/usr/bin/env python
+# Copyright (c) 2023-present Plane Software, Inc. and contributors
+# SPDX-License-Identifier: AGPL-3.0-only
+# See the LICENSE file for details.
+
import argparse
import subprocess
import sys
diff --git a/apps/api/templates/emails/invitations/project_invitation.html b/apps/api/templates/emails/invitations/project_invitation.html
index 254408ac57..36aecd60d8 100644
--- a/apps/api/templates/emails/invitations/project_invitation.html
+++ b/apps/api/templates/emails/invitations/project_invitation.html
@@ -124,7 +124,7 @@
-
Note: Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our Discord or GitHub , and we will use your feedback to improve on our upcoming releases.
+
Note: Plane is still in its early days, not everything will be perfect yet, and hiccups may happen. Please let us know of any suggestions, ideas, or bugs that you encounter on our Forum or GitHub , and we will use your feedback to improve on our upcoming releases.
@@ -227,7 +227,7 @@
-
+
@@ -244,7 +244,7 @@
-
+
@@ -261,7 +261,7 @@
-
+
@@ -277,7 +277,7 @@
-
+
@@ -346,4 +346,4 @@