diff --git a/src/management-system-v2/app/admin/ms-config/ms-config-form.tsx b/src/management-system-v2/app/admin/ms-config/ms-config-form.tsx
index c2056f224..1c060f77b 100644
--- a/src/management-system-v2/app/admin/ms-config/ms-config-form.tsx
+++ b/src/management-system-v2/app/admin/ms-config/ms-config-form.tsx
@@ -107,7 +107,7 @@ export default function MSConfigForm({
// Get context for the setting
const overridden = overwrittenByEnv.includes(configKey);
const envOnly = mSConfigEnvironmentOnlyKeys.includes(configKey as any);
- const disabled = pathParts.find((part) => disabledGroups.get(part));
+ const disabledKey = pathParts.find((part) => disabledGroups.get(part));
// This is needed, for when a key that can disable a group, is part of a group that is
// disabled
@@ -127,7 +127,7 @@ export default function MSConfigForm({
!envOnly &&
!overridden &&
!parentGroupDisabled &&
- (!disabled || disablers.length > 0)
+ (!disabledKey || disablers.length > 0)
)
return;
@@ -150,7 +150,7 @@ export default function MSConfigForm({
tooltipMessage = 'This config was overridden by an environment variable';
else if (envOnly)
tooltipMessage = 'This setting can only be changed through environment variables';
- else if (disabled) tooltipMessage = `Disabled by ${disabled}`;
+ else if (disabledKey) tooltipMessage = `Disabled by ${disabledGroups.get(disabledKey)}`;
return {
input: {input},
diff --git a/src/management-system-v2/app/admin/ms-config/page.tsx b/src/management-system-v2/app/admin/ms-config/page.tsx
index b7173673c..517db07f6 100644
--- a/src/management-system-v2/app/admin/ms-config/page.tsx
+++ b/src/management-system-v2/app/admin/ms-config/page.tsx
@@ -7,11 +7,17 @@ import { getMSConfig, updateMSConfig, writeDefaultMSConfig } from '@/lib/ms-conf
import MSConfigForm from './ms-config-form';
import { userError } from '@/lib/user-error';
import { SettingGroup } from '@/app/(dashboard)/[environmentId]/settings/type-util';
+import { ConfigurableMSConfig } from '@/lib/ms-config/config-schema';
+import { restartInternalSchedulerWithCurrentConfigs } from '@/lib/scheduler';
-async function saveConfig(newConfig: Record) {
+async function saveConfig(changedValues: Record) {
'use server';
try {
- await updateMSConfig(newConfig);
+ await updateMSConfig(changedValues);
+
+ if ('SCHEDULER_INTERNAL_ACTIVE' in changedValues || 'SCHEDULER_INTERVAL' in changedValues) {
+ await restartInternalSchedulerWithCurrentConfigs();
+ }
} catch (e) {
return userError('Error saving config');
}
@@ -289,15 +295,47 @@ async function ConfigPage() {
},
{
type: 'boolean',
- name: 'SCHEDULER_JOB_DELETE_INACTIVE_GUESTS',
- key: 'SCHEDULER_JOB_DELETE_INACTIVE_GUESTS',
- value: msConfig.SCHEDULER_JOB_DELETE_INACTIVE_GUESTS,
+ name: 'SCHEDULER_INTERNAL_ACTIVE',
+ key: 'SCHEDULER_INTERNAL_ACTIVE',
+ value: msConfig.SCHEDULER_INTERNAL_ACTIVE,
},
{
- type: 'boolean',
- name: 'SCHEDULER_JOB_DELETE_OLD_ARTIFACTS',
- key: 'SCHEDULER_JOB_DELETE_OLD_ARTIFACTS',
- value: msConfig.SCHEDULER_JOB_DELETE_OLD_ARTIFACTS,
+ type: 'number',
+ name: 'SCHEDULER_TASK_DELETE_INACTIVE_GUESTS',
+ key: 'SCHEDULER_TASK_DELETE_INACTIVE_GUESTS',
+ value: msConfig.SCHEDULER_TASK_DELETE_INACTIVE_GUESTS,
+ },
+ {
+ type: 'number',
+ name: 'SCHEDULER_TASK_DELETE_OLD_ARTIFACTS',
+ key: 'SCHEDULER_TASK_DELETE_OLD_ARTIFACTS',
+ value: msConfig.SCHEDULER_TASK_DELETE_OLD_ARTIFACTS,
+ },
+ {
+ type: 'number',
+ name: 'SCHEDULER_TASK_DELETE_INACTIVE_SPACES',
+ key: 'SCHEDULER_TASK_DELETE_INACTIVE_SPACES',
+ value: msConfig.SCHEDULER_TASK_DELETE_INACTIVE_SPACES,
+ },
+ {
+ type: 'number',
+ name: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_REGISTRATION_TOKENS',
+ key: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_REGISTRATION_TOKENS',
+ value: msConfig.SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_REGISTRATION_TOKENS,
+ },
+ {
+ type: 'number',
+ name: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_CHANGE_TOKENS',
+ key: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_CHANGE_TOKENS',
+ value: msConfig.SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_CHANGE_TOKENS,
+ },
+ {
+ type: 'number',
+ name: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_VERIFICATION_TOKENS',
+ key: 'SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_VERIFICATION_TOKENS',
+ value: msConfig.SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_VERIFICATION_TOKENS,
+ // NOTE: eventually remove this description
+ description: 'This value will only take effect after restarting the system',
},
],
},
diff --git a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts b/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
deleted file mode 100644
index 3bc7cf608..000000000
--- a/src/management-system-v2/app/api/private/delete-inactive-guests/route.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import { deleteInactiveGuestUsers } from '@/lib/data/db/iam/users';
-
-// TODO: get this time from the database
-const GUESET_INACTIVE_TIME = 1000 * 60 * 60 * 24 * 30; // 30 days
-
-export async function POST(request: NextRequest) {
- try {
- // Extract and validate the Bearer token
- const authHeader = request.headers.get('Authorization');
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
- return NextResponse.json({ error: 'Unauthorized: Missing Bearer token' }, { status: 401 });
- }
-
- const token = authHeader.replace('Bearer ', '').trim();
- // TODO: use different token
- if (token !== process.env.SWEEPER_TRIGGER_TOKEN) {
- return NextResponse.json({ error: 'Unauthorized: Invalid Bearer token' }, { status: 403 });
- }
-
- const { count } = await deleteInactiveGuestUsers(GUESET_INACTIVE_TIME);
-
- return NextResponse.json(
- {
- message: `${count} guest users deleted`,
- },
- { status: 200 },
- );
- } catch (error) {
- console.error('Error deleting inactive guest users:', error);
- return NextResponse.json({ error: 'Failed to delete inactive guest users' }, { status: 500 });
- }
-}
diff --git a/src/management-system-v2/app/api/private/file-manager/run-sweeper/route.ts b/src/management-system-v2/app/api/private/file-manager/run-sweeper/route.ts
deleted file mode 100644
index f0cf418bb..000000000
--- a/src/management-system-v2/app/api/private/file-manager/run-sweeper/route.ts
+++ /dev/null
@@ -1,43 +0,0 @@
-import { NextRequest, NextResponse } from 'next/server';
-import db from '@/lib/data/db';
-import { deleteProcessArtifact } from '@/lib/data/file-manager-facade';
-
-export async function POST(request: NextRequest) {
- try {
- // Extract and validate the Bearer token
- const authHeader = request.headers.get('Authorization');
- if (!authHeader || !authHeader.startsWith('Bearer ')) {
- return NextResponse.json({ error: 'Unauthorized: Missing Bearer token' }, { status: 401 });
- }
-
- const token = authHeader.replace('Bearer ', '').trim();
- if (token !== process.env.SWEEPER_TRIGGER_TOKEN) {
- return NextResponse.json({ error: 'Unauthorized: Invalid Bearer token' }, { status: 403 });
- }
-
- const oneWeekAgo = new Date();
- oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
-
- const deletableFiles = await db.artifact.findMany({
- where: {
- deletable: true,
- deletedOn: { lte: oneWeekAgo },
- },
- select: { filePath: true },
- });
-
- if (deletableFiles.length > 0) {
- await Promise.all(deletableFiles.map((file) => deleteProcessArtifact(file.filePath, true)));
- }
-
- return NextResponse.json(
- {
- message: `${deletableFiles.length} files deleted successfully`,
- },
- { status: 200 },
- );
- } catch (error) {
- console.error('Error deleting files:', error);
- return NextResponse.json({ error: 'Failed to delete files' }, { status: 500 });
- }
-}
diff --git a/src/management-system-v2/app/api/scheduler/tasks/[task]/route.ts b/src/management-system-v2/app/api/scheduler/tasks/[task]/route.ts
new file mode 100644
index 000000000..895df88c4
--- /dev/null
+++ b/src/management-system-v2/app/api/scheduler/tasks/[task]/route.ts
@@ -0,0 +1,33 @@
+import { NextRequest, NextResponse } from 'next/server';
+import { getMSConfig } from '@/lib/ms-config/ms-config';
+import { allSchedulerTasks } from '@/lib/scheduler';
+
+export async function POST(request: NextRequest, { params }: { params: { task: string } }) {
+ if (params.task !== 'all') {
+ return new Response('Not implemented', {
+ status: 501,
+ });
+ }
+
+ try {
+ const msConfig = await getMSConfig();
+
+ // Extract and validate the Bearer token
+ const authHeader = request.headers.get('Authorization');
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
+ return NextResponse.json({ error: 'Unauthorized: Missing Bearer token' }, { status: 401 });
+ }
+
+ const token = authHeader.replace('Bearer ', '').trim();
+ if (token !== msConfig.SCHEDULER_TOKEN) {
+ return NextResponse.json({ error: 'Unauthorized: Invalid Bearer token' }, { status: 403 });
+ }
+
+ const message = allSchedulerTasks();
+
+ return NextResponse.json({ message }, { status: 200 });
+ } catch (error) {
+ console.error('Error cleaning up DB:', error);
+ return NextResponse.json({ error: 'Failed to clean up DB' }, { status: 500 });
+ }
+}
diff --git a/src/management-system-v2/instrumentation.ts b/src/management-system-v2/instrumentation.ts
index 7db70a4c0..b2ea23652 100644
--- a/src/management-system-v2/instrumentation.ts
+++ b/src/management-system-v2/instrumentation.ts
@@ -1,5 +1,7 @@
export async function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
+ // Importing env and using it here will ensure that env variables (msconfig) are verified at
+ // startup
const { env } = await import('./lib/ms-config/env-vars');
const { getMSConfigDBValuesAndEnsureDefaults } = await import('./lib/ms-config/ms-config');
@@ -53,5 +55,9 @@ export async function register() {
process.exit(1);
}
}
+
+ // Start scheduler if necessary
+ const { restartInternalSchedulerWithCurrentConfigs } = await import('./lib/scheduler');
+ await restartInternalSchedulerWithCurrentConfigs();
}
}
diff --git a/src/management-system-v2/lib/auth.ts b/src/management-system-v2/lib/auth.ts
index 660452a49..f55aeb1dc 100644
--- a/src/management-system-v2/lib/auth.ts
+++ b/src/management-system-v2/lib/auth.ts
@@ -29,421 +29,443 @@ import db from './data/db';
import { createUserRegistrationToken } from './email-verification-tokens/utils';
import { saveEmailVerificationToken } from './data/db/iam/verification-tokens';
import { NextAuthEmailTakenError, NextAuthUsernameTakenError } from './authjs-error-message';
+import { getMSConfig } from './ms-config/ms-config';
-const nextAuthOptions: NextAuthConfig = {
- secret: env.NEXTAUTH_SECRET,
- adapter: Adapter,
- session: {
- strategy: 'jwt',
- },
- cookies: {
- csrfToken: {
- name: 'proceed.csrf-token',
- },
- callbackUrl: {
- name: 'proceed.callback-url',
+let nextAuthOptions: NextAuthConfig;
+async function getNextAuthOptions() {
+ if (nextAuthOptions) return nextAuthOptions;
+
+ const options: NextAuthConfig = {
+ secret: env.NEXTAUTH_SECRET,
+ adapter: Adapter,
+ session: {
+ strategy: 'jwt',
},
- sessionToken: {
- name: 'proceed.session-token',
+ cookies: {
+ csrfToken: {
+ name: 'proceed.csrf-token',
+ },
+ callbackUrl: {
+ name: 'proceed.callback-url',
+ },
+ sessionToken: {
+ name: 'proceed.session-token',
+ },
},
- },
- trustHost: true,
- providers: [],
- callbacks: {
- async jwt({ token, user: _user, trigger }) {
- if (!env.PROCEED_PUBLIC_IAM_ACTIVE) {
- token.user = noIamUser.user;
- return token;
- }
+ trustHost: true,
+ providers: [],
+ callbacks: {
+ async jwt({ token, user: _user, trigger }) {
+ if (!env.PROCEED_PUBLIC_IAM_ACTIVE) {
+ token.user = noIamUser.user;
+ return token;
+ }
- let user = _user as User | undefined;
+ let user = _user as User | undefined;
- if (trigger === 'update') user = (await getUserById(token.user.id)) as User;
+ if (trigger === 'update') user = (await getUserById(token.user.id)) as User;
- if (user) token.user = user;
+ if (user) token.user = user;
- return token;
- },
- session({ session, token }) {
- if (token.user) (session.user as User) = token.user;
+ return token;
+ },
+ session({ session, token }) {
+ if (token.user) (session.user as User) = token.user;
- return session;
- },
- signIn: async (params) => {
- const { account, user: _user, email } = params;
- if (account?.provider === 'register-as-new-user' && (_user as any).notARealUser === true) {
- return `/signin?error=${encodeURIComponent('$success Check your email: we sent you a link to sign in with your new user.')}`;
- }
-
- const session = await auth();
- const sessionUser = session?.user;
-
- // Guest account signs in with proper auth
- if (
- sessionUser?.isGuest &&
- account?.provider !== 'guest-signin' &&
- !email?.verificationRequest
- ) {
- // Check if the user's cookie is correct
- const sessionUserInDb = await getUserById(sessionUser.id);
- if (!sessionUserInDb || !sessionUserInDb.isGuest) throw new Error('Something went wrong');
-
- const userSigningIn = _user.id ? await getUserById(_user.id) : null;
-
- if (!userSigningIn) {
- const user = _user as Partial;
- await updateUser(sessionUser.id, {
- firstName: user.firstName ?? undefined,
- lastName: user.lastName ?? undefined,
- username: user.username ?? undefined,
- profileImage: user.image ?? undefined,
- email: user.email ?? undefined,
- isGuest: false,
- });
+ return session;
+ },
+ signIn: async (params) => {
+ const { account, user: _user, email } = params;
+ if (account?.provider === 'register-as-new-user' && (_user as any).notARealUser === true) {
+ return `/signin?error=${encodeURIComponent('$success Check your email: we sent you a link to sign in with your new user.')}`;
}
- }
- return true;
- },
- },
- events: {
- async signOut(message) {
- // since we use jwt message contains a token
- const token = (message as { token: JWT }).token;
-
- if (!token.user.isGuest) return;
-
- const user = await getUserById(token.user.id);
- if (user) {
- if (!user.isGuest) {
- console.warn('User with invalid session');
- return;
+ const session = await auth();
+ const sessionUser = session?.user;
+
+ // Guest account signs in with proper auth
+ if (
+ sessionUser?.isGuest &&
+ account?.provider !== 'guest-signin' &&
+ !email?.verificationRequest
+ ) {
+ // Check if the user's cookie is correct
+ const sessionUserInDb = await getUserById(sessionUser.id);
+ if (!sessionUserInDb || !sessionUserInDb.isGuest) throw new Error('Something went wrong');
+
+ const userSigningIn = _user.id ? await getUserById(_user.id) : null;
+
+ if (!userSigningIn) {
+ const user = _user as Partial;
+ await updateUser(sessionUser.id, {
+ firstName: user.firstName ?? undefined,
+ lastName: user.lastName ?? undefined,
+ username: user.username ?? undefined,
+ profileImage: user.image ?? undefined,
+ email: user.email ?? undefined,
+ isGuest: false,
+ });
+ }
}
- await deleteUser(user.id);
- }
+ return true;
+ },
},
- async session({ session }) {
- // TODO: this causes many db calls, we should debounce this with a significant delay
- if (session.user.isGuest) {
- await updateGuestUserLastSigninTime(session.user.id, new Date());
- }
+ events: {
+ async signOut(message) {
+ // since we use jwt message contains a token
+ const token = (message as { token: JWT }).token;
+
+ if (!token.user.isGuest) return;
+
+ const user = await getUserById(token.user.id);
+ if (user) {
+ if (!user.isGuest) {
+ console.warn('User with invalid session');
+ return;
+ }
+
+ await deleteUser(user.id);
+ }
+ },
+ async session({ session }) {
+ // TODO: this causes many db calls, we should debounce this with a significant delay
+ if (session.user.isGuest) {
+ await updateGuestUserLastSigninTime(session.user.id, new Date());
+ }
+ },
},
- },
- pages: {
- signIn: '/signin',
- error: '/signin',
- },
- logger: {
- error(error) {
- if (error instanceof AuthError) {
- if (['AdapterError', 'CallbackRouteError', 'OAuthProfileParseError'].includes(error.type)) {
- console.error(error);
+ pages: {
+ signIn: '/signin',
+ error: '/signin',
+ },
+ logger: {
+ error(error) {
+ if (error instanceof AuthError) {
+ if (
+ ['AdapterError', 'CallbackRouteError', 'OAuthProfileParseError'].includes(error.type)
+ ) {
+ console.error(error);
+ } else {
+ console.error('NextAuth error:', error.type);
+ }
} else {
- console.error('NextAuth error:', error.type);
+ console.error(error);
}
- } else {
- console.error(error);
- }
- },
- },
-};
-
-if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
- nextAuthOptions.providers.push(
- EmailProvider({
- id: 'email',
- name: 'Sign in with E-mail',
- server: {},
- sendVerificationRequest(params) {
- const signinMail = renderSigninLinkEmail({
- signInLink: params.url,
- expires: params.expires,
- });
-
- sendEmail({
- to: params.identifier,
- subject: 'Sign in to PROCEED',
- html: signinMail.html,
- text: signinMail.text,
- });
},
- maxAge: 24 * 60 * 60, // one day
- }),
- );
-}
-
-if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_GOOGLE_ACTIVE) {
- nextAuthOptions.providers.push(
- GoogleProvider({
- id: 'google',
- clientId: env.IAM_LOGIN_OAUTH_GOOGLE_CLIENT_ID,
- clientSecret: env.IAM_LOGIN_OAUTH_GOOGLE_CLIENT_SECRET,
- profile(profile) {
- return {
- id: profile.sub,
- name: profile.name,
- firstName: profile.given_name,
- lastName: profile.family_name,
- email: profile.email,
- image: profile.picture,
- };
- },
- }),
- );
-}
+ },
+ };
-if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_X_ACTIVE) {
- nextAuthOptions.providers.push(
- TwitterProvider({
- id: 'twitter',
- clientId: env.IAM_LOGIN_OAUTH_X_CLIENT_ID,
- clientSecret: env.IAM_LOGIN_OAUTH_X_CLIENT_SECRET,
- profile({ data, email }) {
- const nameParts = data.name.split(' ');
- const fistName = nameParts[0];
- const lastName = nameParts.slice(1).join(' ');
-
- return {
- email: typeof email === 'string' ? email : undefined,
- username: data.username,
- id: data.id,
- image: data.profile_image_url,
- firstName: fistName.length > 0 ? fistName : undefined,
- lastName: lastName.length > 0 ? lastName : undefined,
- };
- },
- }),
- );
-}
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
+ options.providers.push(
+ EmailProvider({
+ id: 'email',
+ name: 'Sign in with E-mail',
+ server: {},
+ sendVerificationRequest(params) {
+ const signinMail = renderSigninLinkEmail({
+ signInLink: params.url,
+ expires: params.expires,
+ });
-// Guest users can only have a personal space, so it doesn't make sense to have guests when
-// personal spaces are deactivated
-if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
- nextAuthOptions.providers.push(
- CredentialsProvider({
- name: 'Continue as Guest',
- id: 'guest-signin',
- credentials: {},
- async authorize() {
- return addUser({ isGuest: true });
- },
- }),
- );
-}
+ sendEmail({
+ to: params.identifier,
+ subject: 'Sign in to PROCEED',
+ html: signinMail.html,
+ text: signinMail.text,
+ });
+ },
+ maxAge:
+ (await getMSConfig({ dontForceDynamicThroughHeaders: true }))
+ .SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_VERIFICATION_TOKENS *
+ 60 *
+ 60,
+ }),
+ );
+ }
-if (env.NODE_ENV === 'development') {
- const johnDoeTemplate = {
- username: 'johndoe',
- firstName: 'John',
- lastName: 'Doe',
- email: 'johndoe@proceed-labs.org',
- id: 'development-id|johndoe',
- isGuest: false,
- emailVerifiedOn: null,
- profileImage: null,
- };
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_GOOGLE_ACTIVE) {
+ options.providers.push(
+ GoogleProvider({
+ id: 'google',
+ clientId: env.IAM_LOGIN_OAUTH_GOOGLE_CLIENT_ID,
+ clientSecret: env.IAM_LOGIN_OAUTH_GOOGLE_CLIENT_SECRET,
+ profile(profile) {
+ return {
+ id: profile.sub,
+ name: profile.name,
+ firstName: profile.given_name,
+ lastName: profile.family_name,
+ email: profile.email,
+ image: profile.picture,
+ };
+ },
+ }),
+ );
+ }
- nextAuthOptions.providers.push(
- CredentialsProvider({
- id: 'development-users',
- name: 'Continue with Development User',
- credentials: {
- username: {
- label: 'Username',
- type: 'text',
- placeholder: 'johndoe | admin',
- value: 'admin',
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_X_ACTIVE) {
+ options.providers.push(
+ TwitterProvider({
+ id: 'twitter',
+ clientId: env.IAM_LOGIN_OAUTH_X_CLIENT_ID,
+ clientSecret: env.IAM_LOGIN_OAUTH_X_CLIENT_SECRET,
+ profile({ data, email }) {
+ const nameParts = data.name.split(' ');
+ const fistName = nameParts[0];
+ const lastName = nameParts.slice(1).join(' ');
+
+ return {
+ email: typeof email === 'string' ? email : undefined,
+ username: data.username,
+ id: data.id,
+ image: data.profile_image_url,
+ firstName: fistName.length > 0 ? fistName : undefined,
+ lastName: lastName.length > 0 ? lastName : undefined,
+ };
},
- },
- async authorize(credentials) {
- let user: User | null = null;
-
- if (credentials.username === 'johndoe') {
- user = await getUserByUsername('johndoe');
- if (!user) user = await addUser(johnDoeTemplate);
- } else if (credentials.username === 'admin') {
- user = await getUserByUsername('admin');
- }
+ }),
+ );
+ }
- return user;
- },
- }),
- );
-}
+ // Guest users can only have a personal space, so it doesn't make sense to have guests when
+ // personal spaces are deactivated
+ if (env.PROCEED_PUBLIC_IAM_PERSONAL_SPACES_ACTIVE) {
+ options.providers.push(
+ CredentialsProvider({
+ name: 'Continue as Guest',
+ id: 'guest-signin',
+ credentials: {},
+ async authorize() {
+ return addUser({ isGuest: true });
+ },
+ }),
+ );
+ }
-if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_DISCORD_ACTIVE) {
- nextAuthOptions.providers.push(
- DiscordProvider({
- id: 'discord',
- clientId: env.IAM_LOGIN_OAUTH_DISCORD_CLIENT_ID,
- clientSecret: env.IAM_LOGIN_OAUTH_DISCORD_CLIENT_SECRET,
- profile(profile) {
- const image = profile.avatar
- ? `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.png`
- : null;
-
- return { ...profile, image };
- },
- }),
- );
-}
+ if (env.NODE_ENV === 'development') {
+ const johnDoeTemplate = {
+ username: 'johndoe',
+ firstName: 'John',
+ lastName: 'Doe',
+ email: 'johndoe@proceed-labs.org',
+ id: 'development-id|johndoe',
+ isGuest: false,
+ emailVerifiedOn: null,
+ profileImage: null,
+ };
-if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
- nextAuthOptions.providers.push(
- CredentialsProvider({
- name: 'Sign in',
- type: 'credentials',
- id: 'username-password-signin',
- credentials: {
- username: {
- label: 'Username',
- type: 'username',
+ options.providers.push(
+ CredentialsProvider({
+ id: 'development-users',
+ name: 'Continue with Development User',
+ credentials: {
+ username: {
+ label: 'Username',
+ type: 'text',
+ placeholder: 'johndoe | admin',
+ value: 'admin',
+ },
},
- password: {
- label: 'Password',
- type: 'password',
+ async authorize(credentials) {
+ let user: User | null = null;
+
+ if (credentials.username === 'johndoe') {
+ user = await getUserByUsername('johndoe');
+ if (!user) user = await addUser(johnDoeTemplate);
+ } else if (credentials.username === 'admin') {
+ user = await getUserByUsername('admin');
+ }
+
+ return user;
},
- },
- authorize: async (credentials, req) => {
- const userAndPassword = await getUserAndPasswordByUsername(credentials.username as string);
+ }),
+ );
+ }
- if (!userAndPassword) return null;
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_OAUTH_DISCORD_ACTIVE) {
+ options.providers.push(
+ DiscordProvider({
+ id: 'discord',
+ clientId: env.IAM_LOGIN_OAUTH_DISCORD_CLIENT_ID,
+ clientSecret: env.IAM_LOGIN_OAUTH_DISCORD_CLIENT_SECRET,
+ profile(profile) {
+ const image = profile.avatar
+ ? `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.png`
+ : null;
+
+ return { ...profile, image };
+ },
+ }),
+ );
+ }
- const passwordIsCorrect = await comparePassword(
- credentials.password as string,
- userAndPassword.passwordAccount.password,
- );
- if (!passwordIsCorrect) return null;
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
+ options.providers.push(
+ CredentialsProvider({
+ name: 'Sign in',
+ type: 'credentials',
+ id: 'username-password-signin',
+ credentials: {
+ username: {
+ label: 'Username',
+ type: 'username',
+ },
+ password: {
+ label: 'Password',
+ type: 'password',
+ },
+ },
+ authorize: async (credentials, req) => {
+ const userAndPassword = await getUserAndPasswordByUsername(
+ credentials.username as string,
+ );
- return userAndPassword as User;
- },
- }),
- );
-}
+ if (!userAndPassword) return null;
-if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE || env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
- //Vorname, Nachname und Username input feldern,
- const credentials: Record = {
- firstName: {
- type: 'string',
- label: 'First Name',
- },
- lastName: {
- type: 'string',
- label: 'Last Name',
- },
- username: {
- type: 'string',
- label: 'Username',
- },
- };
+ const passwordIsCorrect = await comparePassword(
+ credentials.password as string,
+ userAndPassword.passwordAccount.password,
+ );
+ if (!passwordIsCorrect) return null;
- if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
- credentials['email'] = {
- type: 'email',
- label: 'E-Mail',
- };
+ return userAndPassword as User;
+ },
+ }),
+ );
}
- if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
- credentials['password'] = {
- type: 'password',
- label: 'Password',
+ if (
+ env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE ||
+ env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE
+ ) {
+ //Vorname, Nachname und Username input feldern,
+ const credentials: Record = {
+ firstName: {
+ type: 'string',
+ label: 'First Name',
+ },
+ lastName: {
+ type: 'string',
+ label: 'Last Name',
+ },
+ username: {
+ type: 'string',
+ label: 'Username',
+ },
};
- }
- nextAuthOptions.providers.push(
- CredentialsProvider({
- name: 'Register as New User',
- type: 'credentials',
- id: 'register-as-new-user',
- credentials,
- authorize: async (
- credentials: Partial<
- Record<
- 'firstName' | 'lastName' | 'username' | 'email' | 'password' | 'callbackUrl',
- string
- >
- >,
- ) => {
- let callbackUrl: string | undefined = undefined;
- // only allow urls that start with / = redirect to out site
- if (credentials.callbackUrl && credentials.callbackUrl.startsWith('/')) {
- callbackUrl = credentials.callbackUrl;
- }
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
+ credentials['email'] = {
+ type: 'email',
+ label: 'E-Mail',
+ };
+ }
- let user: User | null = null;
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE) {
+ credentials['password'] = {
+ type: 'password',
+ label: 'Password',
+ };
+ }
- // Whenever the email is active, we create the user after he verifies his email
- if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
- const [existingUserUsername, existingUserMail] = await Promise.all([
- getUserByUsername(credentials.username as string),
- getUserByEmail(credentials.email as string),
- ]);
- if (existingUserUsername) {
- throw new NextAuthUsernameTakenError();
- }
- if (existingUserMail) {
- throw new NextAuthEmailTakenError();
+ options.providers.push(
+ CredentialsProvider({
+ name: 'Register as New User',
+ type: 'credentials',
+ id: 'register-as-new-user',
+ credentials,
+ authorize: async (
+ credentials: Partial<
+ Record<
+ 'firstName' | 'lastName' | 'username' | 'email' | 'password' | 'callbackUrl',
+ string
+ >
+ >,
+ ) => {
+ let callbackUrl: string | undefined = undefined;
+ // only allow urls that start with / = redirect to out site
+ if (credentials.callbackUrl && credentials.callbackUrl.startsWith('/')) {
+ callbackUrl = credentials.callbackUrl;
}
- const tokenParams: any = {
- identifier: credentials.email,
- username: credentials.username,
- firstName: credentials.firstName,
- lastName: credentials.lastName,
- };
-
- if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE)
- tokenParams['passwordHash'] = await hashPassword(credentials.password as string);
-
- const userRegistrationToken = await createUserRegistrationToken(tokenParams, callbackUrl);
-
- await saveEmailVerificationToken(userRegistrationToken.verificationToken);
-
- const signinMail = renderSigninLinkEmail({
- signInLink: userRegistrationToken.redirectUrl,
- expires: userRegistrationToken.verificationToken.expires,
- });
-
- await sendEmail({
- to: credentials.email as string,
- subject: 'Sign in to PROCEED',
- html: signinMail.html,
- text: signinMail.text,
- });
-
- // This allows nextauth to proceed in the signin flow.
- // This dummy user will be caught by the signin callback and will redirect the user back
- // to the signin page with a success message.
- return { id: '', notARealUser: true };
- } else {
- // Only password is enabled -> immediately create user
- await db.$transaction(async (tx) => {
- user = await addUser(
- {
- username: credentials.username,
- firstName: credentials.firstName,
- lastName: credentials.lastName,
- isGuest: false,
- emailVerifiedOn: null,
- },
- tx,
+ let user: User | null = null;
+
+ // Whenever the email is active, we create the user after he verifies his email
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_MAIL_ACTIVE) {
+ const [existingUserUsername, existingUserMail] = await Promise.all([
+ getUserByUsername(credentials.username as string),
+ getUserByEmail(credentials.email as string),
+ ]);
+ if (existingUserUsername) {
+ throw new NextAuthUsernameTakenError();
+ }
+ if (existingUserMail) {
+ throw new NextAuthEmailTakenError();
+ }
+
+ const tokenParams: any = {
+ identifier: credentials.email,
+ username: credentials.username,
+ firstName: credentials.firstName,
+ lastName: credentials.lastName,
+ };
+
+ if (env.PROCEED_PUBLIC_IAM_LOGIN_USER_PASSWORD_ACTIVE)
+ tokenParams['passwordHash'] = await hashPassword(credentials.password as string);
+
+ const userRegistrationToken = await createUserRegistrationToken(
+ tokenParams,
+ callbackUrl,
);
- const hashedPassword = await hashPassword(credentials.password as string);
- await setUserPassword(user.id, hashedPassword, tx);
- });
- }
+ await saveEmailVerificationToken(userRegistrationToken.verificationToken);
+
+ const signinMail = renderSigninLinkEmail({
+ signInLink: userRegistrationToken.redirectUrl,
+ expires: userRegistrationToken.verificationToken.expires,
+ });
+
+ await sendEmail({
+ to: credentials.email as string,
+ subject: 'Sign in to PROCEED',
+ html: signinMail.html,
+ text: signinMail.text,
+ });
+
+ // This allows nextauth to proceed in the signin flow.
+ // This dummy user will be caught by the signin callback and will redirect the user back
+ // to the signin page with a success message.
+ return { id: '', notARealUser: true };
+ } else {
+ // Only password is enabled -> immediately create user
+ await db.$transaction(async (tx) => {
+ user = await addUser(
+ {
+ username: credentials.username,
+ firstName: credentials.firstName,
+ lastName: credentials.lastName,
+ isGuest: false,
+ emailVerifiedOn: null,
+ },
+ tx,
+ );
+
+ const hashedPassword = await hashPassword(credentials.password as string);
+ await setUserPassword(user.id, hashedPassword, tx);
+ });
+ }
- return user;
- },
- }),
- );
+ return user;
+ },
+ }),
+ );
+ }
+
+ return options;
}
-export const { auth, handlers, signIn, signOut } = NextAuth(nextAuthOptions);
+export const { auth, handlers, signIn, signOut } = NextAuth(getNextAuthOptions);
export type ExtractedProvider =
| {
diff --git a/src/management-system-v2/lib/data/db/iam/environments.ts b/src/management-system-v2/lib/data/db/iam/environments.ts
index df9a02a52..1df75f4b9 100644
--- a/src/management-system-v2/lib/data/db/iam/environments.ts
+++ b/src/management-system-v2/lib/data/db/iam/environments.ts
@@ -165,8 +165,12 @@ export async function addEnvironment(
return newEnvironmentWithId;
}
-export async function deleteEnvironment(environmentId: string, ability?: Ability) {
- const environment = await getEnvironmentById(environmentId);
+export async function deleteEnvironment(
+ environmentId: string,
+ ability?: Ability,
+ tx?: Prisma.TransactionClient,
+) {
+ const environment = await getEnvironmentById(environmentId, undefined, undefined, tx);
if (!environment) throw new Error('Environment not found');
if (env.PROCEED_PUBLIC_IAM_ONLY_ONE_ORGANIZATIONAL_SPACE && environment.isOrganization) {
@@ -176,7 +180,9 @@ export async function deleteEnvironment(environmentId: string, ability?: Ability
}
if (ability && !ability.can('delete', 'Environment')) throw new UnauthorizedError();
- await db.space.delete({
+
+ const dbMutator = tx ?? db;
+ await dbMutator.space.delete({
where: { id: environmentId },
});
}
@@ -260,3 +266,26 @@ export async function deleteSpaceLogo(organizationId: string) {
//deleteLogo(organizationId);
}
+
+export async function removeInactiveSpaces(
+ inactiveTimeInMS: number,
+ tx?: Prisma.TransactionClient,
+): Promise<{ count: number }> {
+ if (!tx) {
+ return db.$transaction((trx) => removeInactiveSpaces(inactiveTimeInMS, trx));
+ }
+
+ const cutoff = new Date(Date.now() - inactiveTimeInMS);
+
+ const deletableSpaces = await tx.space.findMany({
+ where: {
+ isActive: false,
+ createdOn: { lte: cutoff },
+ },
+ });
+
+ if (deletableSpaces.length > 0) {
+ await Promise.all(deletableSpaces.map((space) => deleteEnvironment(space.id, undefined, tx)));
+ }
+ return { count: deletableSpaces.length };
+}
diff --git a/src/management-system-v2/lib/data/db/iam/verification-tokens.ts b/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
index 9e35cf222..d8fce15af 100644
--- a/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
+++ b/src/management-system-v2/lib/data/db/iam/verification-tokens.ts
@@ -83,3 +83,19 @@ export async function updateEmailVerificationTokenExpiration(
},
});
}
+
+export async function removeExpiredEmailVerificationTokens(
+ tx?: Prisma.TransactionClient,
+): Promise<{ count: number }> {
+ if (!tx) {
+ return db.$transaction((trx) => removeExpiredEmailVerificationTokens(trx));
+ }
+
+ const cutoff = new Date();
+
+ return await tx.emailVerificationToken.deleteMany({
+ where: {
+ expires: { lte: cutoff },
+ },
+ });
+}
diff --git a/src/management-system-v2/lib/data/environments.ts b/src/management-system-v2/lib/data/environments.ts
index 95a20898b..e80b2cb9e 100644
--- a/src/management-system-v2/lib/data/environments.ts
+++ b/src/management-system-v2/lib/data/environments.ts
@@ -60,7 +60,7 @@ export async function deleteOrganizationEnvironments(environmentIds: string[]) {
if (!environment?.isOrganization)
return userError(`Environment ${environmentId} is not an organization environment`);
- deleteEnvironment(environmentId, ability);
+ await deleteEnvironment(environmentId, ability);
}
} catch (e) {
if (e instanceof UnauthorizedError)
diff --git a/src/management-system-v2/lib/data/file-manager-facade.ts b/src/management-system-v2/lib/data/file-manager-facade.ts
index 1f97fc66d..8387e7c95 100644
--- a/src/management-system-v2/lib/data/file-manager-facade.ts
+++ b/src/management-system-v2/lib/data/file-manager-facade.ts
@@ -606,6 +606,32 @@ export async function revertSoftDeleteProcessScriptTask(
}
}
+export async function removeDeletedArtifactsFromDb(
+ expirationInMs: number,
+ tx?: Prisma.TransactionClient,
+): Promise<{ count: number }> {
+ if (!tx) {
+ return db.$transaction((trx) => removeDeletedArtifactsFromDb(expirationInMs, trx));
+ }
+
+ const cutoff = new Date(Date.now() - expirationInMs);
+
+ const deletableFiles = await tx.artifact.findMany({
+ where: {
+ deletable: true,
+ deletedOn: { lte: cutoff },
+ },
+ select: { filePath: true },
+ });
+
+ if (deletableFiles.length > 0) {
+ await Promise.all(
+ deletableFiles.map((file) => deleteProcessArtifact(file.filePath, true, undefined, tx)),
+ );
+ }
+ return { count: deletableFiles.length };
+}
+
// Update artifact references for a versioned user task
// export async function updateArtifactRefVersionedUserTask(userTask: string, newFileName: string) {
// if (!userTask) {
diff --git a/src/management-system-v2/lib/email-verification-tokens/utils.ts b/src/management-system-v2/lib/email-verification-tokens/utils.ts
index 0006ba867..72924451d 100644
--- a/src/management-system-v2/lib/email-verification-tokens/utils.ts
+++ b/src/management-system-v2/lib/email-verification-tokens/utils.ts
@@ -3,6 +3,9 @@ import 'server-only';
import { z } from 'zod';
import { EmailVerificationToken } from '@/lib/data/db/iam/verification-tokens';
import { env } from '@/lib/ms-config/env-vars';
+import { getMSConfig } from '../ms-config/ms-config';
+
+const MS_IN_HOUR = 1000 * 60 * 60;
async function createHash(message: string) {
const msgUint8 = new TextEncoder().encode(message);
@@ -26,8 +29,12 @@ export async function createChangeEmailVerificationToken({
}) {
const identifier = z.string().email().parse(email);
+ const msConfig = await getMSConfig();
+
const token = crypto.randomUUID();
- const expires = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ const expires = new Date(
+ Date.now() + MS_IN_HOUR * msConfig.SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_CHANGE_TOKENS,
+ );
const verificationToken = {
type: 'change_email',
@@ -65,8 +72,12 @@ export async function createUserRegistrationToken(
},
callbackUrl?: string,
) {
+ const msConfig = await getMSConfig();
+
const token = crypto.randomUUID();
- const expires = new Date(Date.now() + 1000 * 60 * 60 * 24);
+ const expires = new Date(
+ Date.now() + MS_IN_HOUR * msConfig.SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_REGISTRATION_TOKENS,
+ );
const verificationToken = {
type: 'register_new_user',
diff --git a/src/management-system-v2/lib/ms-config/config-schema.ts b/src/management-system-v2/lib/ms-config/config-schema.ts
index 71531fb90..65a293b63 100644
--- a/src/management-system-v2/lib/ms-config/config-schema.ts
+++ b/src/management-system-v2/lib/ms-config/config-schema.ts
@@ -150,10 +150,24 @@ export const msConfigSchema = {
IAM_LOGIN_OAUTH_DISCORD_CLIENT_ID: z.string().default(''),
IAM_LOGIN_OAUTH_DISCORD_CLIENT_SECRET: z.string().default(''),
+ SCHEDULER_INTERNAL_ACTIVE: z.string().optional().transform(boolParser),
SCHEDULER_INTERVAL: z.string().default('0 3 * * *'),
SCHEDULER_TOKEN: z.string().optional(),
- SCHEDULER_JOB_DELETE_INACTIVE_GUESTS: z.coerce.number().default(0),
- SCHEDULER_JOB_DELETE_OLD_ARTIFACTS: z.coerce.number().default(7),
+
+ SCHEDULER_TASK_DELETE_INACTIVE_GUESTS: z.coerce.number().default(30),
+ SCHEDULER_TASK_DELETE_OLD_ARTIFACTS: z.coerce.number().default(7),
+ SCHEDULER_TASK_DELETE_INACTIVE_SPACES: z.coerce.number().default(7),
+
+ PROCEED_PUBLIC_TIMELINE_VIEW: z.string().optional().transform(boolParser),
+
+ MQTT_SERVER_ADDRESS: z.string().url().optional(),
+ MQTT_USERNAME: z.string().optional(),
+ MQTT_PASSWORD: z.string().optional(),
+ MQTT_BASETOPIC: z.string().optional(),
+
+ SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_REGISTRATION_TOKENS: z.coerce.number().default(7),
+ SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_CHANGE_TOKENS: z.coerce.number().default(7),
+ SCHEDULING_TASK_EXPIRATION_TIME_EMAIL_VERIFICATION_TOKENS: z.coerce.number().default(24),
},
production: {
DATABASE_URL: z.string(),
diff --git a/src/management-system-v2/lib/ms-config/ms-config.ts b/src/management-system-v2/lib/ms-config/ms-config.ts
index 2f1145261..98fe49c4e 100644
--- a/src/management-system-v2/lib/ms-config/ms-config.ts
+++ b/src/management-system-v2/lib/ms-config/ms-config.ts
@@ -107,9 +107,11 @@ export async function updateMSConfig(config: Record {
+ const removedArtifacts = await removeDeletedArtifactsFromDb(
+ msConfig.SCHEDULER_TASK_DELETE_OLD_ARTIFACTS * MS_IN_DAY,
+ tx,
+ );
+ message += `Removed ${removedArtifacts.count} artifacts.\n`;
+
+ const removedInactiveGuests = await deleteInactiveGuestUsers(
+ msConfig.SCHEDULER_TASK_DELETE_INACTIVE_GUESTS * MS_IN_DAY,
+ tx,
+ );
+ message += `Removed ${removedInactiveGuests.count} inactive guests.\n`;
+
+ const inactiveSpaces = await removeInactiveSpaces(
+ msConfig.SCHEDULER_TASK_DELETE_INACTIVE_SPACES * MS_IN_DAY,
+ tx,
+ );
+ message += `Removed ${inactiveSpaces.count} inactive spaces.\n`;
+
+ const removedVerificationTokens = await removeExpiredEmailVerificationTokens(tx);
+ message += `Removed ${removedVerificationTokens.count} expired verification tokens.`;
+ });
+
+ return message;
+}
diff --git a/src/management-system-v2/package.json b/src/management-system-v2/package.json
index 1f65a735b..65cfd1229 100644
--- a/src/management-system-v2/package.json
+++ b/src/management-system-v2/package.json
@@ -53,7 +53,6 @@
"monaco-editor": "0.47.0",
"next": "14.2.3",
"next-auth": "5.0.0-beta.25",
- "node-cron": "^3.0.3",
"nodemailer": "6.9.13",
"openapi-fetch": "0.8.2",
"react": "18.2.0",
@@ -72,7 +71,8 @@
"react-resizable": "^3.0.5",
"mqtt": "^5.10.1",
"bcryptjs": "3.0.2",
- "sharp": "0.34.3"
+ "sharp": "0.34.3",
+ "cron": "4.3.3"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.28.11",
diff --git a/src/management-system-v2/prisma/migrations/20250911145437_spaces_created_on/migration.sql b/src/management-system-v2/prisma/migrations/20250911145437_spaces_created_on/migration.sql
new file mode 100644
index 000000000..6a702ffbc
--- /dev/null
+++ b/src/management-system-v2/prisma/migrations/20250911145437_spaces_created_on/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "space" ADD COLUMN "createdOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
diff --git a/src/management-system-v2/prisma/schema.prisma b/src/management-system-v2/prisma/schema.prisma
index a3e8a41e3..2fd6a5a83 100644
--- a/src/management-system-v2/prisma/schema.prisma
+++ b/src/management-system-v2/prisma/schema.prisma
@@ -160,6 +160,7 @@ model Space {
roles Role[]
engines Engine[]
settings SpaceSettings? @relation("spaceSettings")
+ createdOn DateTime @default(now())
@@map("space")
}
diff --git a/src/management-system-v2/sweeper-cron/sweeper.js b/src/management-system-v2/sweeper-cron/sweeper.js
deleted file mode 100644
index b3e986976..000000000
--- a/src/management-system-v2/sweeper-cron/sweeper.js
+++ /dev/null
@@ -1,31 +0,0 @@
-require('dotenv').config({ path: '../.env' });
-const cron = require('node-cron');
-const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL;
-
-// Schedule a task to run every Monday at 00:00 (midnight)
-// * * * * *
-// minute hour day(month) month day(week)
-
-cron.schedule(
- '0 0 * * 1', // Adjust cron timing as needed
- async () => {
- try {
- const res = await fetch(`${BASE_URL}/api/private/file-manager/run-sweeper`, {
- method: 'POST',
- headers: {
- Authorization: `Bearer ${process.env.SWEEPER_TRIGGER_TOKEN}`, // Add auth token
- },
- });
-
- console.log(res.status, res.statusText);
- } catch (error) {
- console.error('Error running sweeper:', error.message);
- }
- },
- {
- scheduled: true,
- timezone: 'UTC',
- },
-);
-
-console.log('Weekly cron job running.....');
diff --git a/yarn.lock b/yarn.lock
index 894b1c241..0a798c433 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3024,6 +3024,11 @@
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.0.tgz"
integrity sha512-t7dhREVv6dbNj0q17X12j7yDG4bD/DHYX7o5/DbDxobP0HnGPgpRz2Ej77aL7TZT3DSw13fqUTj8J4mMnqa7WA==
+"@types/luxon@~3.7.0":
+ version "3.7.1"
+ resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.7.1.tgz#ef51b960ff86801e4e2de80c68813a96e529d531"
+ integrity sha512-H3iskjFIAn5SlJU7OuxUmTEpebK6TKB8rxZShDslBMZJ5u9S//KM1sbdAisiSrqwLQncVjnpi2OK2J51h+4lsg==
+
"@types/markdown-it@^12.2.3":
version "12.2.3"
resolved "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-12.2.3.tgz"
@@ -5459,6 +5464,14 @@ create-require@^1.1.0:
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==
+cron@4.3.3:
+ version "4.3.3"
+ resolved "https://registry.yarnpkg.com/cron/-/cron-4.3.3.tgz#d37cfcbc73ba34a50d9d9ce9b653ae60837377d7"
+ integrity sha512-B/CJj5yL3sjtlun6RtYHvoSB26EmQ2NUmhq9ZiJSyKIM4K/fqfh9aelDFlIayD2YMeFZqWLi9hHV+c+pq2Djkw==
+ dependencies:
+ "@types/luxon" "~3.7.0"
+ luxon "~3.7.0"
+
cross-env@7.0.3:
version "7.0.3"
resolved "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz"
@@ -10373,6 +10386,11 @@ lru-cache@^6.0.0:
dependencies:
yallist "^4.0.0"
+luxon@~3.7.0:
+ version "3.7.2"
+ resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.7.2.tgz#d697e48f478553cca187a0f8436aff468e3ba0ba"
+ integrity sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==
+
machine-uuid@^1.2.0:
version "1.2.0"
resolved "https://registry.npmjs.org/machine-uuid/-/machine-uuid-1.2.0.tgz"
@@ -11022,13 +11040,6 @@ node-abi@^3.3.0:
dependencies:
semver "^7.3.5"
-node-cron@^3.0.3:
- version "3.0.3"
- resolved "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz"
- integrity sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==
- dependencies:
- uuid "8.3.2"
-
node-exceptions@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/node-exceptions/-/node-exceptions-4.0.1.tgz"
@@ -15431,11 +15442,6 @@ utrie@^1.0.2:
dependencies:
base64-arraybuffer "^1.0.2"
-uuid@8.3.2, uuid@^8.0.0, uuid@^8.3.2:
- version "8.3.2"
- resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
- integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
-
uuid@9.0.1, uuid@^9.0.0:
version "9.0.1"
resolved "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz"
@@ -15451,6 +15457,11 @@ uuid@^3.1.0, uuid@^3.3.2:
resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
+uuid@^8.0.0, uuid@^8.3.2:
+ version "8.3.2"
+ resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz"
+ integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==
+
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz"