Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .env.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# -------------- GENERAL CONFIGURATION --------------

BASE_PATH=/space # OPTIONAL: Base URL path for the application. If not provided, SPACE will use "/" as default value.
BASE_PATH=/space # OPTIONAL: Base URL path for the application. If not provided, SPACE will use "" as default value. DO NOT USE "/" as BASE_PATH, use an empty string instead.
ADMIN_USER=admin # OPTIONAL: Username for the default admin user. If not provided, SPACE will use "admin" as default value.
ADMIN_PASSWORD=space4all # OPTIONAL: Password for the default admin user. If not provided, SPACE will use "space4all" as default value.
ALLOWED_ORIGINS=http://localhost:3000 # OPTIONAL: Semicolon-separated list of allowed origins for CORS. If not provided, it defaults to "*".
Expand Down Expand Up @@ -35,4 +35,4 @@ JWT_EXPIRATION=1h # OPTIONAL: JWT expiration time
# -------------- FRONTEND CONFIGURATION --------------

ENVIRONMENT=production # OPTIONAL: Environment configuration to be used in the frontend. If not provided, it defaults to "production".
SPACE_HOST=http://localhost:5403 # OPTIONAL: Host URL where the SPACE API is running. If not provided, it defaults to "http://localhost:5403". Change this if your API is running in a different host or port, or if it's behind a proxy.
SPACE_HOST=http://localhost:5403 # OPTIONAL: Host URL where the SPACE API is running. If not provided, it defaults to "http://localhost:5403". Change this if your API is running in a different host or port, or if it's behind a proxy. DO NOT INCLUDE trailing slash.
1 change: 0 additions & 1 deletion .github/workflows/run-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ jobs:
envkey_ENVIRONMENT: "testing"
envkey_DATABASE_NAME: ${{ vars.CI_MONGO_INITDB_DATABASE }}
envkey_MONGO_URI: mongodb://localhost:27017/space_testing_db?authSource=space_testing_db
envkey_BASE_URL_PATH: "/api/v1"
envkey_ADMIN_USER: "admin"
envkey_ADMIN_PASSWORD: "4dm1n"
envkey_REDIS_URL: "redis://localhost:6379"
Expand Down
2 changes: 0 additions & 2 deletions api/.env.template
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
# ---------- SERVER CONFIGURATION ----------

BASE_URL_PATH= # OPTIONAL: Base URL path for the application. If not provided, SPACE will use "/api/v1" as default value.

ALLOWED_ORIGINS= # OPTIONAL: Semicolon-separated list of allowed origins for CORS. If not provided, it defaults to "*".

# ---------- DATABASE CONFIGURATION (MongoDB) ----------
Expand Down
4 changes: 2 additions & 2 deletions api/src/main/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ const initializeServer = async (
container.resolve('eventService').initialize(server);

console.log(
` ${green}➜${reset} ${bold}API:${reset} ${blue}http://localhost${addressInfo.port !== 80 ? `:${bold}${addressInfo.port}${reset}${process.env.BASE_URL_PATH || ''}` : `${process.env.BASE_URL_PATH || ''}`}`
` ${green}➜${reset} ${bold}API:${reset} ${blue}http://localhost${addressInfo.port !== 80 ? `:${bold}${addressInfo.port}${reset}` : ''}`
);
console.log(
` ${green}➜${reset} ${bold}WebSockets:${reset} ${blue}ws://localhost${addressInfo.port !== 80 ? `:${bold}${addressInfo.port}${reset}${process.env.BASE_URL_PATH || ''}/events/pricings` : `${process.env.BASE_URL_PATH || ''}/events/pricings`}`
` ${green}➜${reset} ${bold}WebSockets:${reset} ${blue}ws://localhost${addressInfo.port !== 80 ? `:${bold}${addressInfo.port}${reset}/events/pricings` : '/events/pricings'}`
);

if (['development', 'testing'].includes(process.env.ENVIRONMENT ?? '')) {
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/middlewares/AnalyticsMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Request, Response, NextFunction } from 'express';
import container from '../config/container';

export const analyticsTrackerMiddleware = (req: Request, res: Response, next: NextFunction) => {
const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';
const analyticsService = container.resolve('analyticsService');

// Check if the current route is public (doesn't require authentication)
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/middlewares/AuthMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ async function authenticateOrgApiKey(req: Request, apiKey: string): Promise<void
const checkPermissions = (req: Request, res: Response, next: NextFunction) => {
try {
const method = req.method.toUpperCase() as HttpMethod;
const baseUrlPath = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrlPath = '/api/v1';
const apiPath = extractApiPath(req.path, baseUrlPath);

// Find matching permission rule
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/AnalyticsRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import AnalyticsController from '../controllers/AnalyticsController';
const loadFileRoutes = function (app: express.Application) {
const analyticsController = new AnalyticsController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

app
.route(baseUrl + '/analytics/api-calls')
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/CacheRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import CacheController from '../controllers/CacheController';
const loadFileRoutes = function (app: express.Application) {
const cacheController = new CacheController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

app
.route(baseUrl + '/cache/get')
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/ContractRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { hasPermission, memberRole } from '../middlewares/AuthMiddleware';
const loadFileRoutes = function (app: express.Application) {
const contractController = new ContractController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

app
.route(baseUrl + '/organizations/:organizationId/contracts')
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/EventRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import EventController from '../controllers/EventController';

const loadFileRoutes = (app: express.Application) => {
const eventController = new EventController();
const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

// This route can be used to check the status of the event service
app
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/FeatureEvaluationRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { handleValidation } from '../middlewares/ValidationHandlingMiddleware';
const loadFileRoutes = function (app: express.Application) {
const featureEvaluationController = new FeatureEvaluationController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

app
.route(baseUrl + '/features')
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/HealthcheckRoutes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import express from 'express';

const loadFileRoutes = function (app: express.Application) {
const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

// Public route for authentication (does not require API Key)
app
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/OrganizationRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { memberRole } from '../middlewares/AuthMiddleware';
const loadFileRoutes = function (app: express.Application) {
const organizationController = new OrganizationController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

// Public route for authentication (does not require API Key)
app
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/ServiceRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const loadFileRoutes = function (app: express.Application) {
const serviceController = new ServiceController();
const upload = handlePricingUpload(['pricing'], './public/static/pricings/uploaded');

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

// ============================================
// Organization-scoped routes (User API Keys)
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/routes/UserRoutes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { handleValidation } from '../middlewares/ValidationHandlingMiddleware';
const loadFileRoutes = function (app: express.Application) {
const userController = new UserController();

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

// Public route for authentication (does not require API Key)
app
Expand Down
2 changes: 1 addition & 1 deletion api/src/test/utils/testApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ dotenv.config();
let testServer: Server | null = null;
let testApp: Application | null = null;

const baseUrl = (process.env.BASE_URL_PATH ?? "") + '/api/v1';
const baseUrl = '/api/v1';

const getApp = async (): Promise<Server> => {
if (!testServer) {
Expand Down
5 changes: 2 additions & 3 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ services:
ENVIRONMENT: ${ENVIRONMENT:-production}
ADMIN_USER: ${ADMIN_USER:-admin}
ADMIN_PASSWORD: ${ADMIN_PASSWORD:-space4all}
BASE_URL_PATH: ${BASE_PATH:-}
MONGO_URI: ${MONGO_URI:-mongodb://${DATABASE_USERNAME:-mongoUser}:${DATABASE_PASSWORD:-mongoPassword}@mongodb:27017/${DATABASE_NAME:-space_db}}
REDIS_URL: ${REDIS_URL:-redis://redis:6379}
JWT_SECRET: ${JWT_SECRET:-test_secret}
Expand Down Expand Up @@ -72,8 +71,8 @@ services:
dockerfile: ./docker/Dockerfile
args:
VITE_ENVIRONMENT: ${ENVIRONMENT:-production}
VITE_SPACE_BASE_URL: ${SPACE_HOST:-http://localhost:5403}${BASE_PATH:-}/api/v1 # Change to http://localhost/api/v1 if running SPACE in kubernetes
VITE_FRONTEND_BASE_PATH: ${BASE_PATH:-}
VITE_BASE_PATH: ${BASE_PATH:-}
VITE_SPACE_BASE_URL: ${SPACE_HOST:-http://localhost:5403}${BASE_PATH:-}/api/v1
depends_on:
space-server:
condition: service_healthy
Expand Down
1 change: 0 additions & 1 deletion frontend/.env.example
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
VITE_ENVIRONMENT="development|production"
VITE_SPACE_BASE_URL=base_url_of_the_space_server
VITE_FRONTEND_BASE_PATH=base_path_for_frontend # WITHOUT TRAILING SLASH, e.g. /space
VITE_SPACE_ADMIN_API_KEY=your_space_api_key # Only work when using development environment
29 changes: 14 additions & 15 deletions frontend/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,30 +27,29 @@ COPY ./vite.config.ts .
COPY ./index.html .

ARG VITE_ENVIRONMENT
ARG VITE_FRONTEND_BASE_PATH="/"
ARG VITE_BASE_PATH="/"
ARG VITE_SPACE_BASE_URL

ENV VITE_FRONTEND_BASE_PATH=${VITE_FRONTEND_BASE_PATH}

ENV BASE_PATH=${VITE_BASE_PATH}

# Build your application
RUN pnpm run build
RUN pnpm add -D serve

# Conditional Reorganization Block:
# This only runs if VITE_FRONTEND_BASE_PATH is defined and is NOT equal to "/"
RUN if [ -n "$VITE_FRONTEND_BASE_PATH" ] && [ "$VITE_FRONTEND_BASE_PATH" != "/" ]; then \
# Extract the directory name (e.g., from "/space/" to "space") to exclude it from the find command
FOLDER_NAME=$(echo "$VITE_FRONTEND_BASE_PATH" | sed 's/\///g'); \
echo "Reorganizing dist for subpath: $VITE_FRONTEND_BASE_PATH"; \
# Create the nested directory structure
mkdir -p ./dist${VITE_FRONTEND_BASE_PATH} && \
# Move everything EXCEPT index.html and the newly created subfolder into the subfolder
find ./dist -mindepth 1 -maxdepth 1 ! -name "index.html" ! -name "$FOLDER_NAME" \
-exec mv -t ./dist${VITE_FRONTEND_BASE_PATH}/ {} +; \
else \
echo "Skipping reorganization: App will be served from root (/)"; \
fi
# RUN if [ -n "$VITE_FRONTEND_BASE_PATH" ] && [ "$VITE_FRONTEND_BASE_PATH" != "/" ]; then \
# # Extract the directory name (e.g., from "/space/" to "space") to exclude it from the find command
# FOLDER_NAME=$(echo "$VITE_FRONTEND_BASE_PATH" | sed 's/\///g'); \
# echo "Reorganizing dist for subpath: $VITE_FRONTEND_BASE_PATH"; \
# # Create the nested directory structure
# mkdir -p ./dist${VITE_FRONTEND_BASE_PATH} && \
# # Move everything EXCEPT index.html and the newly created subfolder into the subfolder
# find ./dist -mindepth 1 -maxdepth 1 ! -name "index.html" ! -name "$FOLDER_NAME" \
# -exec mv -t ./dist${VITE_FRONTEND_BASE_PATH}/ {} +; \
# else \
# echo "Skipping reorganization: App will be served from root (/)"; \
# fi

# Specify the command to start the server
CMD ["pnpm", "exec", "serve", "-s", "./dist", "-l", "5050"]
10 changes: 5 additions & 5 deletions frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/png" href="%BASE_URL%favicon/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="%BASE_URL%favicon/favicon.svg" />
<link rel="shortcut icon" href="%BASE_URL%favicon/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="%BASE_URL%favicon/apple-touch-icon.png" />
<link rel="icon" type="image/png" href="favicon/favicon-96x96.png" sizes="96x96" />
<link rel="icon" type="image/svg+xml" href="favicon/favicon.svg" />
<link rel="shortcut icon" href="favicon/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="favicon/apple-touch-icon.png" />
<meta name="apple-mobile-web-app-title" content="SPACE" />
<link rel="manifest" href="%BASE_URL%favicon/site.webmanifest" />
<link rel="manifest" href="favicon/site.webmanifest" />

<title>SPACE</title>
</head>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/contracts/ContractsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ export default function ContractsTable({ contracts, page, setPage, limit, setLim
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/${c.userContact?.userId}`)}
onClick={() => navigate(`/contracts/${c.userContact?.userId}`)}
className="px-3 py-1 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md text-sm transition-colors cursor-pointer"
>
View
Expand Down
44 changes: 22 additions & 22 deletions frontend/src/layouts/logged-view/components/sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,34 @@ import { AiOutlineDashboard } from 'react-icons/ai';
import OrganizationSelector from '@/components/OrganizationSelector';

const mainTabs = [
{ label: 'Overview', path: '', icon: <FiHome size={22} /> },
{ label: 'Contracts Dashboard', path: 'contracts/dashboard', icon: <AiOutlineDashboard size={22}/> },
{ label: 'Members', path: 'members', icon: <FiUsers size={22} /> },
{ label: 'API Keys', path: 'api-keys', icon: <FiKey size={22} /> },
{ label: 'Services Management', path: 'services', icon: <FiServer size={22} /> },
{ label: 'Overview', path: '/', icon: <FiHome size={22} /> },
{ label: 'Contracts Dashboard', path: '/contracts/dashboard', icon: <AiOutlineDashboard size={22}/> },
{ label: 'Members', path: '/members', icon: <FiUsers size={22} /> },
{ label: 'API Keys', path: '/api-keys', icon: <FiKey size={22} /> },
{ label: 'Services Management', path: '/services', icon: <FiServer size={22} /> },
];

const settingsTabs = [
{ label: 'Organization Settings', path: 'organization-settings', icon: <FiUsers size={18} /> },
{ label: 'Profile Settings', path: 'settings', icon: <FiUser size={18} /> },
{ label: 'Organization Settings', path: '/organization-settings', icon: <FiUsers size={18} /> },
{ label: 'Profile Settings', path: '/settings', icon: <FiUser size={18} /> },
];

const adminOnlyTabs = [
{ label: 'Users Management', path: 'users', icon: <FiUsers size={22} />, adminOnly: true },
{ label: 'Organizations', path: 'organizations', icon: <FiLayers size={22} />, adminOnly: true },
{ label: 'Instance Monitoring', path: 'instance-monitoring', icon: <FiActivity size={22} />, adminOnly: true },
{ label: 'Users Management', path: '/users', icon: <FiUsers size={22} />, adminOnly: true },
{ label: 'Organizations', path: '/organizations', icon: <FiLayers size={22} />, adminOnly: true },
{ label: 'Instance Monitoring', path: '/instance-monitoring', icon: <FiActivity size={22} />, adminOnly: true },
];

function getSelectedTab(pathname: string) {
if (pathname.startsWith('members')) return 'members';
if (pathname.startsWith('api-keys')) return 'api-keys';
if (pathname.startsWith('services')) return 'services';
if (pathname.startsWith('organization-settings')) return 'organization-settings';
if (pathname.startsWith('settings')) return 'settings';
if (pathname.startsWith('contracts/dashboard')) return 'contracts/dashboard';
if (pathname.startsWith('instance-monitoring')) return 'instance-monitoring';
if (pathname.startsWith('users')) return 'users';
if (pathname.startsWith('organizations')) return 'organizations';
if (pathname.startsWith('/members')) return '/members';
if (pathname.startsWith('/api-keys')) return '/api-keys';
if (pathname.startsWith('/services')) return '/services';
if (pathname.startsWith('/organization-settings')) return '/organization-settings';
if (pathname.startsWith('/settings')) return '/settings';
if (pathname.startsWith('/contracts/dashboard')) return '/contracts/dashboard';
if (pathname.startsWith('/instance-monitoring')) return '/instance-monitoring';
if (pathname.startsWith('/users')) return '/users';
if (pathname.startsWith('/organizations')) return '/organizations';
return '/';
}

Expand Down Expand Up @@ -106,7 +106,7 @@ export default function Sidebar({
(selected === tab.path
? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '')
}
onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)}
onClick={() => navigate(tab.path)}
aria-current={selected === tab.path ? 'page' : undefined}
>
{tab.icon}
Expand Down Expand Up @@ -155,7 +155,7 @@ export default function Sidebar({
(selected === tab.path
? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '')
}
onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)}
onClick={() => navigate(tab.path)}
aria-current={selected === tab.path ? 'page' : undefined}
>
{tab.icon}
Expand All @@ -182,7 +182,7 @@ export default function Sidebar({
(selected === tab.path
? 'bg-indigo-100 dark:bg-gray-800 font-bold' : '')
}
onClick={() => navigate(`${import.meta.env.BASE_URL}${tab.path}`)}
onClick={() => navigate(tab.path)}
aria-current={selected === tab.path ? 'page' : undefined}
>
{tab.icon}
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/contracts/ContractDetailPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export default function ContractDetailPage() {
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)}
onClick={() => navigate(`/contracts/dashboard`)}
className="inline-flex items-center px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors cursor-pointer"
>
<svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand All @@ -145,7 +145,7 @@ export default function ContractDetailPage() {
<motion.button
whileHover={{ scale: 1.1, x: -4 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)}
onClick={() => navigate(`/contracts/dashboard`)}
className="p-2 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg transition-colors cursor-pointer"
>
<svg className="w-6 h-6 text-gray-600 dark:text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand Down Expand Up @@ -186,7 +186,7 @@ export default function ContractDetailPage() {
<motion.button
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
onClick={() => navigate(`${import.meta.env.BASE_URL}contracts/dashboard`)}
onClick={() => navigate(`/contracts/dashboard`)}
className="inline-flex items-center px-6 py-3 bg-indigo-600 hover:bg-indigo-700 text-white font-medium rounded-lg transition-colors shadow-lg hover:shadow-xl cursor-pointer"
>
<svg className="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/pages/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function LoginPage() {
<div className="text-center mt-4">
<button
type="button"
onClick={() => navigate(`${import.meta.env.BASE_URL}register`)}
onClick={() => navigate(`/register`)}
className="cursor-pointer text-indigo-600 dark:text-indigo-400 hover:text-indigo-800 dark:hover:text-indigo-300 text-sm font-medium transition-colors"
>
Don't have an account? Register here
Expand Down
Loading
Loading