mirror of
https://github.com/LogicLabs-OU/OpenArchiver.git
synced 2026-04-06 00:31:57 +02:00
open-core setup, adding enterprise package
This commit is contained in:
7
LICENSE.txt
Normal file
7
LICENSE.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Source code in this repository is variously licensed under the GNU Affero General Public License (AGPL), or the Open Archiver Commercial License.
|
||||
|
||||
* Outside of the top-level "packages/enterprise" directories, source code in a given file is licensed under the AGPL. The full text of this license can be found in the file LICENSE-AGPL.txt.
|
||||
|
||||
* Within the top-level "packages/enterprise" directories, source code in a given file is licensed under the Open Archiver Commercial License, unless otherwise noted.
|
||||
|
||||
When built, binary files are generated for the AGPL source code and the Open Archiver Commercial License source code. Binaries located at hub.docker.com/logiclabs/open-archiver with an "-enterprise" tag are released under the Open Archiver Commercial License. All other binaries are released under the AGPL.
|
||||
59
apps/open-archiver-enterprise/Dockerfile
Normal file
59
apps/open-archiver-enterprise/Dockerfile
Normal file
@@ -0,0 +1,59 @@
|
||||
# Dockerfile for the Enterprise version of Open Archiver
|
||||
|
||||
ARG BASE_IMAGE=node:22-alpine
|
||||
|
||||
# 0. Base Stage: Define all common dependencies and setup
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm install -g pnpm
|
||||
|
||||
# Copy manifests and lockfile
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./
|
||||
COPY packages/backend/package.json ./packages/backend/
|
||||
COPY packages/frontend/package.json ./packages/frontend/
|
||||
COPY packages/types/package.json ./packages/types/
|
||||
COPY packages/enterprise/package.json ./packages/enterprise/
|
||||
COPY packages/frontend-enterprise/package.json ./packages/frontend-enterprise/
|
||||
COPY apps/open-archiver-enterprise/package.json ./apps/open-archiver-enterprise/
|
||||
|
||||
# 1. Build Stage: Install all dependencies and build the project
|
||||
FROM base AS build
|
||||
COPY packages/frontend/svelte.config.js ./packages/frontend/
|
||||
|
||||
# Install all dependencies.
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile --prod=false
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
|
||||
# Build the Enterprise packages.
|
||||
RUN pnpm build:enterprise
|
||||
|
||||
# 2. Production Stage: Install only production dependencies and copy built artifacts
|
||||
FROM base AS production
|
||||
|
||||
# Copy built application from build stage
|
||||
COPY --from=build /app/packages/backend/dist ./packages/backend/dist
|
||||
COPY --from=build /app/packages/frontend/build ./packages/frontend/build
|
||||
COPY --from=build /app/packages/types/dist ./packages/types/dist
|
||||
COPY --from=build /app/packages/enterprise/dist ./packages/enterprise/dist
|
||||
COPY --from=build /app/packages/frontend-enterprise/dist ./packages/frontend-enterprise/dist
|
||||
COPY --from=build /app/apps/open-archiver-enterprise/dist ./apps/open-archiver-enterprise/dist
|
||||
|
||||
# Copy the entrypoint script and make it executable
|
||||
COPY docker/docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 4000
|
||||
EXPOSE 3000
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start:enterprise"]
|
||||
28
apps/open-archiver-enterprise/index.ts
Normal file
28
apps/open-archiver-enterprise/index.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { createServer, logger } from '@open-archiver/backend';
|
||||
import { enterpriseModules } from '@open-archiver/enterprise';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
|
||||
async function start() {
|
||||
// --- Environment Variable Validation ---
|
||||
const { PORT_BACKEND } = process.env;
|
||||
|
||||
if (!PORT_BACKEND) {
|
||||
throw new Error(
|
||||
'Missing required environment variables for the backend: PORT_BACKEND.'
|
||||
);
|
||||
}
|
||||
// Create the server instance (passing no modules for the default OSS version)
|
||||
const app = await createServer(enterpriseModules);
|
||||
|
||||
app.listen(PORT_BACKEND, () => {
|
||||
logger.info({}, `🏢 Open Archiver (Enterprise) running on port ${PORT_BACKEND}`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch(error => {
|
||||
logger.error({ error }, 'Failed to start the server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
19
apps/open-archiver-enterprise/package.json
Normal file
19
apps/open-archiver-enterprise/package.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "open-archiver-enterprise-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-archiver/backend": "workspace:*",
|
||||
"@open-archiver/enterprise": "workspace:*",
|
||||
"dotenv": "^17.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"ts-node-dev": "^2.0.0"
|
||||
}
|
||||
}
|
||||
8
apps/open-archiver-enterprise/tsconfig.json
Normal file
8
apps/open-archiver-enterprise/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"references": [{ "path": "../../packages/backend" }, { "path": "../../packages/enterprise" }]
|
||||
}
|
||||
55
apps/open-archiver/Dockerfile
Normal file
55
apps/open-archiver/Dockerfile
Normal file
@@ -0,0 +1,55 @@
|
||||
# Dockerfile for the OSS version of Open Archiver
|
||||
|
||||
ARG BASE_IMAGE=node:22-alpine
|
||||
|
||||
# 0. Base Stage: Define all common dependencies and setup
|
||||
FROM ${BASE_IMAGE} AS base
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm
|
||||
RUN --mount=type=cache,target=/root/.npm \
|
||||
npm install -g pnpm
|
||||
|
||||
# Copy manifests and lockfile
|
||||
COPY package.json pnpm-workspace.yaml pnpm-lock.yaml* ./
|
||||
COPY packages/backend/package.json ./packages/backend/
|
||||
COPY packages/frontend/package.json ./packages/frontend/
|
||||
COPY packages/types/package.json ./packages/types/
|
||||
COPY apps/open-archiver/package.json ./apps/open-archiver/
|
||||
|
||||
# 1. Build Stage: Install all dependencies and build the project
|
||||
FROM base AS build
|
||||
COPY packages/frontend/svelte.config.js ./packages/frontend/
|
||||
|
||||
# Install all dependencies.
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
RUN --mount=type=cache,id=pnpm,target=/pnpm/store \
|
||||
pnpm install --frozen-lockfile --prod=false
|
||||
|
||||
# Copy the rest of the source code
|
||||
COPY . .
|
||||
|
||||
# Build the OSS packages.
|
||||
RUN pnpm build:oss
|
||||
|
||||
# 2. Production Stage: Install only production dependencies and copy built artifacts
|
||||
FROM base AS production
|
||||
|
||||
# Copy built application from build stage
|
||||
COPY --from=build /app/packages/backend/dist ./packages/backend/dist
|
||||
COPY --from=build /app/packages/frontend/build ./packages/frontend/build
|
||||
COPY --from=build /app/packages/types/dist ./packages/types/dist
|
||||
COPY --from=build /app/apps/open-archiver/dist ./apps/open-archiver/dist
|
||||
|
||||
# Copy the entrypoint script and make it executable
|
||||
COPY docker/docker-entrypoint.sh /usr/local/bin/
|
||||
|
||||
# Expose the port the app runs on
|
||||
EXPOSE 4000
|
||||
EXPOSE 3000
|
||||
|
||||
# Set the entrypoint
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Start the application
|
||||
CMD ["pnpm", "start:oss"]
|
||||
26
apps/open-archiver/index.ts
Normal file
26
apps/open-archiver/index.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { createServer, logger } from '@open-archiver/backend';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
async function start() {
|
||||
// --- Environment Variable Validation ---
|
||||
const { PORT_BACKEND } = process.env;
|
||||
|
||||
if (!PORT_BACKEND) {
|
||||
throw new Error(
|
||||
'Missing required environment variables for the backend: PORT_BACKEND.'
|
||||
);
|
||||
}
|
||||
// Create the server instance (passing no modules for the default OSS version)
|
||||
const app = await createServer([]);
|
||||
|
||||
app.listen(PORT_BACKEND, () => {
|
||||
logger.info({}, `✅ Open Archiver (OSS) running on port ${PORT_BACKEND}`);
|
||||
});
|
||||
}
|
||||
|
||||
start().catch(error => {
|
||||
logger.error({ error }, 'Failed to start the server:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
18
apps/open-archiver/package.json
Normal file
18
apps/open-archiver/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "open-archiver-app",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only index.ts",
|
||||
"build": "tsc",
|
||||
"start": "node dist/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-archiver/backend": "workspace:*",
|
||||
"dotenv": "^17.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/dotenv": "^8.2.3",
|
||||
"ts-node-dev": "^2.0.0"
|
||||
}
|
||||
}
|
||||
8
apps/open-archiver/tsconfig.json
Normal file
8
apps/open-archiver/tsconfig.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["./**/*.ts"],
|
||||
"references": [{ "path": "../../packages/backend" }]
|
||||
}
|
||||
18
docker-compose.enterprise.yml
Normal file
18
docker-compose.enterprise.yml
Normal file
@@ -0,0 +1,18 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
open-archiver:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: apps/open-archiver-enterprise/Dockerfile
|
||||
args:
|
||||
VITE_ENTERPRISE_MODE: 'true'
|
||||
image: logiclabshq/open-archiver:enterprise-latest
|
||||
ports:
|
||||
- '3000:3000' # Frontend
|
||||
networks:
|
||||
- open-archiver-net
|
||||
|
||||
networks:
|
||||
open-archiver-net:
|
||||
external: true
|
||||
11
package.json
11
package.json
@@ -2,10 +2,14 @@
|
||||
"name": "open-archiver",
|
||||
"version": "0.3.4",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE-AGPL.txt",
|
||||
"scripts": {
|
||||
"dev": "dotenv -- pnpm --filter \"./packages/*\" --parallel dev",
|
||||
"build": "pnpm --filter \"./packages/*\" build",
|
||||
"start": "dotenv -- pnpm --filter \"./packages/*\" --parallel start",
|
||||
"build:oss": "pnpm --filter \"./packages/*\" --filter \"!./packages/enterprise\" --filter \"./apps/open-archiver\" build",
|
||||
"build:enterprise": "cross-env VITE_ENTERPRISE_MODE=true pnpm build",
|
||||
"start:oss": "dotenv -- concurrently \"node apps/open-archiver/dist/index.js\" \"pnpm --filter @open-archiver/frontend start\"",
|
||||
"start:enterprise": "dotenv -- concurrently \"node apps/open-archiver-enterprise/dist/index.js\" \"pnpm --filter @open-archiver/frontend start\"",
|
||||
"dev:enterprise": "cross-env VITE_ENTERPRISE_MODE=true dotenv -- pnpm --filter \"@open-archiver/frontend\" --filter \"open-archiver-enterprise-app\" --parallel dev",
|
||||
"dev:oss": "dotenv -- pnpm --filter \"@open-archiver/frontend\" --filter \"open-archiver-app\" --parallel dev",
|
||||
"start:workers": "dotenv -- concurrently \"pnpm --filter @open-archiver/backend start:ingestion-worker\" \"pnpm --filter @open-archiver/backend start:indexing-worker\" \"pnpm --filter @open-archiver/backend start:sync-scheduler\"",
|
||||
"start:workers:dev": "dotenv -- concurrently \"pnpm --filter @open-archiver/backend start:ingestion-worker:dev\" \"pnpm --filter @open-archiver/backend start:indexing-worker:dev\" \"pnpm --filter @open-archiver/backend start:sync-scheduler:dev\"",
|
||||
"db:generate": "dotenv -- pnpm --filter @open-archiver/backend db:generate",
|
||||
@@ -23,6 +27,7 @@
|
||||
"dotenv-cli": "8.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^10.0.0",
|
||||
"prettier": "^3.6.2",
|
||||
"prettier-plugin-svelte": "^3.4.0",
|
||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
"name": "@open-archiver/backend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE-AGPL.txt",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"dev": "ts-node-dev --respawn --transpile-only src/index.ts ",
|
||||
"build": "tsc && pnpm copy-assets",
|
||||
"copy-assets": "cp -r src/locales dist/locales",
|
||||
"start": "node dist/index.js",
|
||||
"start:ingestion-worker": "node dist/workers/ingestion.worker.js",
|
||||
"start:indexing-worker": "node dist/workers/indexing.worker.js",
|
||||
"start:sync-scheduler": "node dist/jobs/schedulers/sync-scheduler.js",
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ApiKeyController } from '../controllers/api-key.controller';
|
||||
import { requireAuth } from '../middleware/requireAuth';
|
||||
import { AuthService } from '../../services/AuthService';
|
||||
|
||||
export const apiKeyRoutes = (authService: AuthService) => {
|
||||
export const apiKeyRoutes = (authService: AuthService): Router => {
|
||||
const router = Router();
|
||||
const controller = new ApiKeyController();
|
||||
|
||||
|
||||
150
packages/backend/src/api/server.ts
Normal file
150
packages/backend/src/api/server.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import express, { Express } from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { IngestionController } from './controllers/ingestion.controller';
|
||||
import { ArchivedEmailController } from './controllers/archived-email.controller';
|
||||
import { StorageController } from './controllers/storage.controller';
|
||||
import { SearchController } from './controllers/search.controller';
|
||||
import { IamController } from './controllers/iam.controller';
|
||||
import { requireAuth } from './middleware/requireAuth';
|
||||
import { createAuthRouter } from './routes/auth.routes';
|
||||
import { createIamRouter } from './routes/iam.routes';
|
||||
import { createIngestionRouter } from './routes/ingestion.routes';
|
||||
import { createArchivedEmailRouter } from './routes/archived-email.routes';
|
||||
import { createStorageRouter } from './routes/storage.routes';
|
||||
import { createSearchRouter } from './routes/search.routes';
|
||||
import { createDashboardRouter } from './routes/dashboard.routes';
|
||||
import { createUploadRouter } from './routes/upload.routes';
|
||||
import { createUserRouter } from './routes/user.routes';
|
||||
import { createSettingsRouter } from './routes/settings.routes';
|
||||
import { apiKeyRoutes } from './routes/api-key.routes';
|
||||
import { AuthService } from '../services/AuthService';
|
||||
import { UserService } from '../services/UserService';
|
||||
import { IamService } from '../services/IamService';
|
||||
import { StorageService } from '../services/StorageService';
|
||||
import { SearchService } from '../services/SearchService';
|
||||
import { SettingsService } from '../services/SettingsService';
|
||||
import i18next from 'i18next';
|
||||
import FsBackend from 'i18next-fs-backend';
|
||||
import i18nextMiddleware from 'i18next-http-middleware';
|
||||
import path from 'path';
|
||||
import { logger } from '../config/logger';
|
||||
import { rateLimiter } from './middleware/rateLimiter';
|
||||
import { config } from '../config';
|
||||
// Define the "plugin" interface
|
||||
export interface ArchiverModule {
|
||||
initialize: (app: Express) => Promise<void>;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export async function createServer(modules: ArchiverModule[] = []): Promise<Express> {
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// --- Environment Variable Validation ---
|
||||
const { JWT_SECRET, JWT_EXPIRES_IN } = process.env;
|
||||
|
||||
if (!JWT_SECRET || !JWT_EXPIRES_IN) {
|
||||
throw new Error(
|
||||
'Missing required environment variables for the backend: JWT_SECRET, JWT_EXPIRES_IN.'
|
||||
);
|
||||
}
|
||||
|
||||
// --- Dependency Injection Setup ---
|
||||
const userService = new UserService();
|
||||
const authService = new AuthService(userService, JWT_SECRET, JWT_EXPIRES_IN);
|
||||
const authController = new AuthController(authService, userService);
|
||||
const ingestionController = new IngestionController();
|
||||
const archivedEmailController = new ArchivedEmailController();
|
||||
const storageService = new StorageService();
|
||||
const storageController = new StorageController(storageService);
|
||||
const searchService = new SearchService();
|
||||
const searchController = new SearchController();
|
||||
const iamService = new IamService();
|
||||
const iamController = new IamController(iamService);
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
// --- i18next Initialization ---
|
||||
const initializeI18next = async () => {
|
||||
const systemSettings = await settingsService.getSystemSettings();
|
||||
const defaultLanguage = systemSettings?.language || 'en';
|
||||
logger.info({ language: defaultLanguage }, 'Default language');
|
||||
await i18next.use(FsBackend).init({
|
||||
lng: defaultLanguage,
|
||||
fallbackLng: defaultLanguage,
|
||||
ns: ['translation'],
|
||||
defaultNS: 'translation',
|
||||
backend: {
|
||||
loadPath: path.resolve(__dirname, '../locales/{{lng}}/{{ns}}.json'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// Initialize i18next
|
||||
await initializeI18next();
|
||||
logger.info({}, 'i18next initialized');
|
||||
|
||||
// Configure the Meilisearch index on startup
|
||||
logger.info({}, 'Configuring email index...');
|
||||
await searchService.configureEmailIndex();
|
||||
|
||||
const app = express();
|
||||
|
||||
// --- Routes ---
|
||||
const authRouter = createAuthRouter(authController);
|
||||
const ingestionRouter = createIngestionRouter(ingestionController, authService);
|
||||
const archivedEmailRouter = createArchivedEmailRouter(archivedEmailController, authService);
|
||||
const storageRouter = createStorageRouter(storageController, authService);
|
||||
const searchRouter = createSearchRouter(searchController, authService);
|
||||
const dashboardRouter = createDashboardRouter(authService);
|
||||
const iamRouter = createIamRouter(iamController, authService);
|
||||
const uploadRouter = createUploadRouter(authService);
|
||||
const userRouter = createUserRouter(authService);
|
||||
const settingsRouter = createSettingsRouter(authService);
|
||||
const apiKeyRouter = apiKeyRoutes(authService);
|
||||
// upload route is added before middleware because it doesn't use the json middleware.
|
||||
app.use(`/${config.api.version}/upload`, uploadRouter);
|
||||
|
||||
// Middleware for all other routes
|
||||
app.use((req, res, next) => {
|
||||
// exclude certain API endpoints from the rate limiter, for example status, system settings
|
||||
const excludedPatterns = [/^\/v\d+\/auth\/status$/, /^\/v\d+\/settings\/system$/];
|
||||
for (const pattern of excludedPatterns) {
|
||||
if (pattern.test(req.path)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
rateLimiter(req, res, next);
|
||||
});
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// i18n middleware
|
||||
app.use(i18nextMiddleware.handle(i18next));
|
||||
|
||||
app.use(`/${config.api.version}/auth`, authRouter);
|
||||
app.use(`/${config.api.version}/iam`, iamRouter);
|
||||
app.use(`/${config.api.version}/ingestion-sources`, ingestionRouter);
|
||||
app.use(`/${config.api.version}/archived-emails`, archivedEmailRouter);
|
||||
app.use(`/${config.api.version}/storage`, storageRouter);
|
||||
app.use(`/${config.api.version}/search`, searchRouter);
|
||||
app.use(`/${config.api.version}/dashboard`, dashboardRouter);
|
||||
app.use(`/${config.api.version}/users`, userRouter);
|
||||
// Load all provided extension modules
|
||||
for (const module of modules) {
|
||||
await module.initialize(app);
|
||||
console.log(`🏢 Enterprise module loaded: ${module.name}`);
|
||||
}
|
||||
|
||||
app.use(`/${config.api.version}/settings`, settingsRouter);
|
||||
app.use(`/${config.api.version}/api-keys`, apiKeyRouter);
|
||||
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Backend is running!');
|
||||
});
|
||||
|
||||
console.log('✅ Core OSS modules loaded.');
|
||||
|
||||
return app;
|
||||
}
|
||||
@@ -9,4 +9,5 @@ export const apiConfig = {
|
||||
? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10)
|
||||
: 100, // limit each IP to 100 requests per windowMs
|
||||
},
|
||||
version: 'v1'
|
||||
};
|
||||
|
||||
@@ -1,155 +1,3 @@
|
||||
import express from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
import { AuthController } from './api/controllers/auth.controller';
|
||||
import { IngestionController } from './api/controllers/ingestion.controller';
|
||||
import { ArchivedEmailController } from './api/controllers/archived-email.controller';
|
||||
import { StorageController } from './api/controllers/storage.controller';
|
||||
import { SearchController } from './api/controllers/search.controller';
|
||||
import { IamController } from './api/controllers/iam.controller';
|
||||
import { requireAuth } from './api/middleware/requireAuth';
|
||||
import { createAuthRouter } from './api/routes/auth.routes';
|
||||
import { createIamRouter } from './api/routes/iam.routes';
|
||||
import { createIngestionRouter } from './api/routes/ingestion.routes';
|
||||
import { createArchivedEmailRouter } from './api/routes/archived-email.routes';
|
||||
import { createStorageRouter } from './api/routes/storage.routes';
|
||||
import { createSearchRouter } from './api/routes/search.routes';
|
||||
import { createDashboardRouter } from './api/routes/dashboard.routes';
|
||||
import { createUploadRouter } from './api/routes/upload.routes';
|
||||
import { createUserRouter } from './api/routes/user.routes';
|
||||
import { createSettingsRouter } from './api/routes/settings.routes';
|
||||
import { apiKeyRoutes } from './api/routes/api-key.routes';
|
||||
import { AuthService } from './services/AuthService';
|
||||
import { UserService } from './services/UserService';
|
||||
import { IamService } from './services/IamService';
|
||||
import { StorageService } from './services/StorageService';
|
||||
import { SearchService } from './services/SearchService';
|
||||
import { SettingsService } from './services/SettingsService';
|
||||
import i18next from 'i18next';
|
||||
import FsBackend from 'i18next-fs-backend';
|
||||
import i18nextMiddleware from 'i18next-http-middleware';
|
||||
import path from 'path';
|
||||
import { logger } from './config/logger';
|
||||
import { rateLimiter } from './api/middleware/rateLimiter';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
// --- Environment Variable Validation ---
|
||||
const { PORT_BACKEND, JWT_SECRET, JWT_EXPIRES_IN } = process.env;
|
||||
|
||||
if (!PORT_BACKEND || !JWT_SECRET || !JWT_EXPIRES_IN) {
|
||||
throw new Error(
|
||||
'Missing required environment variables for the backend: PORT_BACKEND, JWT_SECRET, JWT_EXPIRES_IN.'
|
||||
);
|
||||
}
|
||||
|
||||
// --- i18next Initialization ---
|
||||
const initializeI18next = async () => {
|
||||
const systemSettings = await settingsService.getSystemSettings();
|
||||
const defaultLanguage = systemSettings?.language || 'en';
|
||||
logger.info({ language: defaultLanguage }, 'Default language');
|
||||
await i18next.use(FsBackend).init({
|
||||
lng: defaultLanguage,
|
||||
fallbackLng: defaultLanguage,
|
||||
ns: ['translation'],
|
||||
defaultNS: 'translation',
|
||||
backend: {
|
||||
loadPath: path.resolve(__dirname, './locales/{{lng}}/{{ns}}.json'),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// --- Dependency Injection Setup ---
|
||||
|
||||
const userService = new UserService();
|
||||
const authService = new AuthService(userService, JWT_SECRET, JWT_EXPIRES_IN);
|
||||
const authController = new AuthController(authService, userService);
|
||||
const ingestionController = new IngestionController();
|
||||
const archivedEmailController = new ArchivedEmailController();
|
||||
const storageService = new StorageService();
|
||||
const storageController = new StorageController(storageService);
|
||||
const searchService = new SearchService();
|
||||
const searchController = new SearchController();
|
||||
const iamService = new IamService();
|
||||
const iamController = new IamController(iamService);
|
||||
const settingsService = new SettingsService();
|
||||
|
||||
// --- Express App Initialization ---
|
||||
const app = express();
|
||||
|
||||
// --- Routes ---
|
||||
const authRouter = createAuthRouter(authController);
|
||||
const ingestionRouter = createIngestionRouter(ingestionController, authService);
|
||||
const archivedEmailRouter = createArchivedEmailRouter(archivedEmailController, authService);
|
||||
const storageRouter = createStorageRouter(storageController, authService);
|
||||
const searchRouter = createSearchRouter(searchController, authService);
|
||||
const dashboardRouter = createDashboardRouter(authService);
|
||||
const iamRouter = createIamRouter(iamController, authService);
|
||||
const uploadRouter = createUploadRouter(authService);
|
||||
const userRouter = createUserRouter(authService);
|
||||
const settingsRouter = createSettingsRouter(authService);
|
||||
const apiKeyRouter = apiKeyRoutes(authService);
|
||||
// upload route is added before middleware because it doesn't use the json middleware.
|
||||
app.use('/v1/upload', uploadRouter);
|
||||
|
||||
// Middleware for all other routes
|
||||
app.use((req, res, next) => {
|
||||
// exclude certain API endpoints from the rate limiter, for example status, system settings
|
||||
const excludedPatterns = [/^\/v\d+\/auth\/status$/, /^\/v\d+\/settings\/system$/];
|
||||
for (const pattern of excludedPatterns) {
|
||||
if (pattern.test(req.path)) {
|
||||
return next();
|
||||
}
|
||||
}
|
||||
rateLimiter(req, res, next);
|
||||
});
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// i18n middleware
|
||||
app.use(i18nextMiddleware.handle(i18next));
|
||||
|
||||
app.use('/v1/auth', authRouter);
|
||||
app.use('/v1/iam', iamRouter);
|
||||
app.use('/v1/ingestion-sources', ingestionRouter);
|
||||
app.use('/v1/archived-emails', archivedEmailRouter);
|
||||
app.use('/v1/storage', storageRouter);
|
||||
app.use('/v1/search', searchRouter);
|
||||
app.use('/v1/dashboard', dashboardRouter);
|
||||
app.use('/v1/users', userRouter);
|
||||
app.use('/v1/settings', settingsRouter);
|
||||
app.use('/v1/api-keys', apiKeyRouter);
|
||||
|
||||
// Example of a protected route
|
||||
app.get('/v1/protected', requireAuth(authService), (req, res) => {
|
||||
res.json({
|
||||
message: 'You have accessed a protected route!',
|
||||
user: req.user, // The user payload is attached by the requireAuth middleware
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.send('Backend is running!');
|
||||
});
|
||||
|
||||
// --- Server Start ---
|
||||
const startServer = async () => {
|
||||
try {
|
||||
// Initialize i18next
|
||||
await initializeI18next();
|
||||
logger.info({}, 'i18next initialized');
|
||||
|
||||
// Configure the Meilisearch index on startup
|
||||
logger.info({}, 'Configuring email index...');
|
||||
await searchService.configureEmailIndex();
|
||||
|
||||
app.listen(PORT_BACKEND, () => {
|
||||
logger.info({}, `Backend listening at http://localhost:${PORT_BACKEND}`);
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error({ error }, 'Failed to start the server:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
startServer();
|
||||
export { createServer, ArchiverModule } from './api/server';
|
||||
export { logger } from './config/logger';
|
||||
export { config } from './config';
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true
|
||||
"experimentalDecorators": true,
|
||||
"composite": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
|
||||
File diff suppressed because one or more lines are too long
21
packages/enterprise/package.json
Normal file
21
packages/enterprise/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@open-archiver/enterprise",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc && pnpm copy-assets",
|
||||
"copy-assets": "mkdir -p dist/modules/license && cp src/modules/license/public-key.pem dist/modules/license/public-key.pem"
|
||||
},
|
||||
"dependencies": {
|
||||
"@open-archiver/backend": "workspace:*",
|
||||
"express": "^5.1.0",
|
||||
"jsonwebtoken": "^9.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^5.0.3",
|
||||
"@types/jsonwebtoken": "^9.0.10"
|
||||
}
|
||||
}
|
||||
8
packages/enterprise/src/index.ts
Normal file
8
packages/enterprise/src/index.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { ArchiverModule } from '@open-archiver/backend';
|
||||
import { statusModule } from './modules/status/status.module';
|
||||
import { retentionPolicyModule } from './modules/retention-policy/retention-policy.module';
|
||||
|
||||
export const enterpriseModules: ArchiverModule[] = [
|
||||
statusModule,
|
||||
retentionPolicyModule,
|
||||
];
|
||||
13
packages/enterprise/src/modules/license/LicenseService.ts
Normal file
13
packages/enterprise/src/modules/license/LicenseService.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
// This is a placeholder for the LicenseService. In a real implementation, this service would handle license validation. Currently all validations returns true.
|
||||
export class LicenseService {
|
||||
private publicKey = fs.readFileSync(path.resolve(__dirname, './public-key.pem'), 'utf-8');
|
||||
|
||||
public isFeatureEnabled(feature: string): boolean {
|
||||
// For now, all features are enabled.
|
||||
console.log(`Checking feature: ${feature}`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
4
packages/enterprise/src/modules/license/public-key.pem
Normal file
4
packages/enterprise/src/modules/license/public-key.pem
Normal file
@@ -0,0 +1,4 @@
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
MFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEC6I4ubMlqW28CjS4IbFnCZ8fE+etwF50
|
||||
h2KM3yh6acvko/k7234dCbJ/rwJZFq7DUlG4DABMnTg/1OwbjGZiMQ==
|
||||
-----END PUBLIC KEY-----
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Express, Request, Response } from 'express';
|
||||
import { ArchiverModule, config } from '@open-archiver/backend';
|
||||
class RetentionPolicyModule implements ArchiverModule {
|
||||
name = 'retention-policy';
|
||||
|
||||
async initialize(app: Express): Promise<void> {
|
||||
|
||||
app.get(`/${config.api.version}/enterprise/retention-policy`, (req: Request, res: Response) => {
|
||||
res.json({ status: 'Retention Policy module is active!' });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const retentionPolicyModule = new RetentionPolicyModule();
|
||||
21
packages/enterprise/src/modules/status/status.module.ts
Normal file
21
packages/enterprise/src/modules/status/status.module.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { Express, Request, Response } from 'express';
|
||||
import { ArchiverModule, config } from '@open-archiver/backend';
|
||||
import { LicenseService } from '../license/LicenseService';
|
||||
|
||||
class StatusModule implements ArchiverModule {
|
||||
name = 'Status';
|
||||
|
||||
async initialize(app: Express): Promise<void> {
|
||||
const licenseService = new LicenseService();
|
||||
|
||||
app.get(`/${config.api.version}/enterprise/status`, (req: Request, res: Response) => {
|
||||
if (licenseService.isFeatureEnabled('enterprise-status')) {
|
||||
res.json({ status: 'Enterprise features are enabled!' });
|
||||
} else {
|
||||
res.status(403).json({ error: 'Enterprise license required.' });
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const statusModule = new StatusModule();
|
||||
12
packages/enterprise/tsconfig.json
Normal file
12
packages/enterprise/tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"esModuleInterop": true,
|
||||
"composite": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"references": [{ "path": "../../packages/backend" }]
|
||||
}
|
||||
1
packages/enterprise/tsconfig.tsbuildinfo
Normal file
1
packages/enterprise/tsconfig.tsbuildinfo
Normal file
File diff suppressed because one or more lines are too long
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@open-archiver/frontend",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE-AGPL.txt",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -6,14 +6,17 @@
|
||||
import { page } from '$app/state';
|
||||
import ThemeSwitcher from '$lib/components/custom/ThemeSwitcher.svelte';
|
||||
import { t } from '$lib/translations';
|
||||
const navItems: {
|
||||
|
||||
interface NavItem {
|
||||
href?: string;
|
||||
label: string;
|
||||
subMenu?: {
|
||||
href: string;
|
||||
label: string;
|
||||
}[];
|
||||
}[] = [
|
||||
}
|
||||
|
||||
const baseNavItems: NavItem[] = [
|
||||
{ href: '/dashboard', label: $t('app.layout.dashboard') },
|
||||
{ href: '/dashboard/ingestions', label: $t('app.layout.ingestions') },
|
||||
{ href: '/dashboard/archived-emails', label: $t('app.layout.archived_emails') },
|
||||
@@ -40,6 +43,15 @@
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const enterpriseNavItems: NavItem[] = [
|
||||
{ href: '/dashboard/compliance-center', label: 'Compliance Center' },
|
||||
];
|
||||
|
||||
let navItems: NavItem[] = $state(baseNavItems);
|
||||
if (import.meta.env.VITE_ENTERPRISE_MODE) {
|
||||
navItems = [...baseNavItems, ...enterpriseNavItems];
|
||||
}
|
||||
let { children } = $props();
|
||||
function handleLogout() {
|
||||
authStore.logout();
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { api } from '$lib/server/api';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import type { PageServerLoad } from './$types';
|
||||
|
||||
export const load: PageServerLoad = async (event) => {
|
||||
if (import.meta.env.VITE_ENTERPRISE_MODE) {
|
||||
try {
|
||||
const response = await api('/enterprise/status', event);
|
||||
const data = await response.json();
|
||||
return {
|
||||
status: data
|
||||
};
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw error(500, 'Failed to fetch enterprise status.');
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
|
||||
export let data: PageData;
|
||||
</script>
|
||||
|
||||
{#if import.meta.env.VITE_ENTERPRISE_MODE}
|
||||
<h1 class="text-2xl font-bold">Compliance Center (Enterprise)</h1>
|
||||
<p class="mt-4">This is a placeholder page for the Compliance Center.</p>
|
||||
{#if data && data.status}
|
||||
<pre class="mt-4">{JSON.stringify(data.status, null, 2)}</pre>
|
||||
{/if}
|
||||
{:else}
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-bold">Access Denied</h1>
|
||||
<p class="mt-4">This feature is only available in the Enterprise Edition.</p>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -7,6 +7,10 @@ dotenv.config();
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [tailwindcss(), sveltekit()],
|
||||
define: {
|
||||
// This will be 'true' only during the enterprise build process
|
||||
'import.meta.env.VITE_ENTERPRISE_MODE': process.env.VITE_ENTERPRISE_MODE === 'true'
|
||||
},
|
||||
server: {
|
||||
port: Number(process.env.PORT_FRONTEND) || 3000,
|
||||
proxy: {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "@open-archiver/types",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"license": "SEE LICENSE IN LICENSE-AGPL.txt",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
|
||||
94
pnpm-lock.yaml
generated
94
pnpm-lock.yaml
generated
@@ -15,6 +15,9 @@ importers:
|
||||
specifier: 8.0.0
|
||||
version: 8.0.0
|
||||
devDependencies:
|
||||
cross-env:
|
||||
specifier: ^10.0.0
|
||||
version: 10.0.0
|
||||
prettier:
|
||||
specifier: ^3.6.2
|
||||
version: 3.6.2
|
||||
@@ -31,6 +34,41 @@ importers:
|
||||
specifier: ^1.6.4
|
||||
version: 1.6.4(@algolia/client-search@5.34.1)(@types/node@24.0.13)(axios@1.10.0)(lightningcss@1.30.1)(postcss@8.5.6)(search-insights@2.17.3)(typescript@5.8.3)
|
||||
|
||||
apps/open-archiver:
|
||||
dependencies:
|
||||
'@open-archiver/backend':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/backend
|
||||
dotenv:
|
||||
specifier: ^17.2.0
|
||||
version: 17.2.0
|
||||
devDependencies:
|
||||
'@types/dotenv':
|
||||
specifier: ^8.2.3
|
||||
version: 8.2.3
|
||||
ts-node-dev:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@types/node@24.0.13)(typescript@5.8.3)
|
||||
|
||||
apps/open-archiver-enterprise:
|
||||
dependencies:
|
||||
'@open-archiver/backend':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/backend
|
||||
'@open-archiver/enterprise':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/enterprise
|
||||
dotenv:
|
||||
specifier: ^17.2.0
|
||||
version: 17.2.0
|
||||
devDependencies:
|
||||
'@types/dotenv':
|
||||
specifier: ^8.2.3
|
||||
version: 8.2.3
|
||||
ts-node-dev:
|
||||
specifier: ^2.0.0
|
||||
version: 2.0.0(@types/node@24.0.13)(typescript@5.8.3)
|
||||
|
||||
packages/backend:
|
||||
dependencies:
|
||||
'@aws-sdk/client-s3':
|
||||
@@ -197,6 +235,25 @@ importers:
|
||||
specifier: ^5.8.3
|
||||
version: 5.8.3
|
||||
|
||||
packages/enterprise:
|
||||
dependencies:
|
||||
'@open-archiver/backend':
|
||||
specifier: workspace:*
|
||||
version: link:../backend
|
||||
express:
|
||||
specifier: ^5.1.0
|
||||
version: 5.1.0
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
devDependencies:
|
||||
'@types/express':
|
||||
specifier: ^5.0.3
|
||||
version: 5.0.3
|
||||
'@types/jsonwebtoken':
|
||||
specifier: ^9.0.10
|
||||
version: 9.0.10
|
||||
|
||||
packages/frontend:
|
||||
dependencies:
|
||||
'@iconify/svelte':
|
||||
@@ -628,6 +685,9 @@ packages:
|
||||
'@drizzle-team/brocli@0.10.2':
|
||||
resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
|
||||
|
||||
'@epic-web/invariant@1.0.0':
|
||||
resolution: {integrity: sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==}
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==}
|
||||
deprecated: 'Merged into tsx: https://tsx.is'
|
||||
@@ -1755,6 +1815,10 @@ packages:
|
||||
'@types/d3-shape@3.1.7':
|
||||
resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==}
|
||||
|
||||
'@types/dotenv@8.2.3':
|
||||
resolution: {integrity: sha512-g2FXjlDX/cYuc5CiQvyU/6kkbP1JtmGzh0obW50zD7OKeILVL0NSpPWLXVfqoAGQjom2/SLLx9zHq0KXvD6mbw==}
|
||||
deprecated: This is a stub types definition. dotenv provides its own type definitions, so you do not need this installed.
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
@@ -1770,6 +1834,9 @@ packages:
|
||||
'@types/http-errors@2.0.5':
|
||||
resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==}
|
||||
|
||||
'@types/jsonwebtoken@9.0.10':
|
||||
resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==}
|
||||
|
||||
'@types/linkify-it@5.0.0':
|
||||
resolution: {integrity: sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==}
|
||||
|
||||
@@ -1791,6 +1858,9 @@ packages:
|
||||
'@types/mime@1.3.5':
|
||||
resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==}
|
||||
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
'@types/multer@2.0.0':
|
||||
resolution: {integrity: sha512-C3Z9v9Evij2yST3RSBktxP9STm6OdMc5uR1xF1SGr98uv8dUlAL2hqwrZ3GVB3uyMyiegnscEK6PGtYvNrjTjw==}
|
||||
|
||||
@@ -2298,6 +2368,11 @@ packages:
|
||||
resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
cross-env@10.0.0:
|
||||
resolution: {integrity: sha512-aU8qlEK/nHYtVuN4p7UQgAwVljzMg8hB4YK5ThRqD2l/ziSnryncPNn7bMLt5cFYsKVKBh8HqLqyCoTupEUu7Q==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
cross-fetch@4.1.0:
|
||||
resolution: {integrity: sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==}
|
||||
|
||||
@@ -3649,6 +3724,7 @@ packages:
|
||||
resolution: {integrity: sha512-Nkwo9qeCvqVH0ZgYRUfPyj6o4o7StvNIxMFECeiz4y0uMOVyqc5Y9hjsdFVxdYCeiUjjXLQXA8KIz0iJL3HM0w==}
|
||||
engines: {node: '>=20.18.0'}
|
||||
hasBin: true
|
||||
bundledDependencies: []
|
||||
|
||||
peberminta@0.9.0:
|
||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||
@@ -5325,6 +5401,8 @@ snapshots:
|
||||
|
||||
'@drizzle-team/brocli@0.10.2': {}
|
||||
|
||||
'@epic-web/invariant@1.0.0': {}
|
||||
|
||||
'@esbuild-kit/core-utils@3.3.2':
|
||||
dependencies:
|
||||
esbuild: 0.18.20
|
||||
@@ -6330,6 +6408,10 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/d3-path': 3.1.1
|
||||
|
||||
'@types/dotenv@8.2.3':
|
||||
dependencies:
|
||||
dotenv: 17.2.0
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/express-serve-static-core@5.0.7':
|
||||
@@ -6351,6 +6433,11 @@ snapshots:
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
'@types/jsonwebtoken@9.0.10':
|
||||
dependencies:
|
||||
'@types/ms': 2.1.0
|
||||
'@types/node': 24.0.13
|
||||
|
||||
'@types/linkify-it@5.0.0': {}
|
||||
|
||||
'@types/mailparser@3.4.6':
|
||||
@@ -6373,6 +6460,8 @@ snapshots:
|
||||
|
||||
'@types/mime@1.3.5': {}
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/multer@2.0.0':
|
||||
dependencies:
|
||||
'@types/express': 5.0.3
|
||||
@@ -6939,6 +7028,11 @@ snapshots:
|
||||
dependencies:
|
||||
luxon: 3.7.1
|
||||
|
||||
cross-env@10.0.0:
|
||||
dependencies:
|
||||
'@epic-web/invariant': 1.0.0
|
||||
cross-spawn: 7.0.6
|
||||
|
||||
cross-fetch@4.1.0(encoding@0.1.13):
|
||||
dependencies:
|
||||
node-fetch: 2.7.0(encoding@0.1.13)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# Defines the pnpm workspace for the monorepo
|
||||
packages:
|
||||
- 'packages/*'
|
||||
- 'apps/*'
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true
|
||||
"resolveJsonModule": true
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user