Β«Liatoshynsky FoundationΒ» ΠΏΡΠΈΡΠ²ΡΡΠ΅Π½ΠΈΠΉ ΠΏΠΎΠΏΡΠ»ΡΡΠΈΠ·Π°ΡΡΡ ΡΠ²ΠΎΡΡΠΎΡΡΡΒ ΠΠΎΡΠΈΡΠ° ΠΡΡΠΎΡΠΈΠ½ΡΡΠΊΠΎΠ³ΠΎΒ β Π²ΠΈΠ΄Π°ΡΠ½ΠΎΠ³ΠΎ ΡΠΊΡΠ°ΡΠ½ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡΠΎΡΠ°, ΠΏΡΠ΅Π΄ΡΡΠ°Π²Π½ΠΈΠΊΠ° ΠΌΠΎΠ΄Π΅ΡΠ½ΡΠ·ΠΌΡ ΡΠ° Π΅ΠΊΡΠΏΡΠ΅ΡΡΠΎΠ½ΡΠ·ΠΌΡ.
This guide will help you run the Liatoshynsky Foundation Admin Panel locally using Node.js or Docker
- NodeJS (22.0.0)
- Download from: this url
- npm (comes with Node.js)
- Docker (for containerized setup): you can download here
π³ Make sure Docker is installed and running locally if you use the Docker setup
.env file contains:
MONGO_USERNAME=
MONGO_PASSWORD=
MONGO_DB=
MONGO_URL=
MONGO_HOST=
MONGO_PORT=
JWT_ACCESS_TOKEN_SECRET=
JWT_REFRESH_TOKEN_SECRET=
# Logs retention (in seconds) β controls how long application logs live in MongoDB.
# Defaults to 7 days (604800) when unset or invalid.
LOG_RETENTION_SECONDS=604800
# Storage Environment - determines the environment context
# Set to 'production' for production, 'development' (default) for development
STORAGE_ENV=development
# Upload Limits
UPLOAD_MAX_FILE_SIZE=10485760 # 10MB in bytes
UPLOAD_MAX_FILES=10
# Storage Configuration
# Storage type options: 'cloud' | 'azure-blob'
# Default: 'cloud' for all environments
STORAGE_TYPE=cloud
# Azure Blob Storage (for STORAGE_TYPE=azure-blob)
AZURE_SAS_URL=
AZURE_CONTAINER_NAME=
AZURE_FOLDER_PREFIX=uploads
# STORAGE_BASE_URL=http://localhost:3000/api/blob-url
# Cloud Storage (for STORAGE_TYPE=cloud - AWS/GCP/Cloudflare R2)
# CLOUD_PROVIDER=aws # or gcp, cloudflare
# CLOUD_BUCKET=your-bucket
# CLOUD_REGION=us-east-1
# CLOUD_ENDPOINT=
# CLOUD_ACCESS_KEY=
# CLOUD_SECRET_KEY=
# CLOUDFLARE_TOKEN=
# CLOUD_PROJECT_ID=git clone git@github.com:Liatoshynsky-Foundation/lf-admin.git
cd lf-adminThese steps are only required if you plan to use local tools and dependencies via brew:
brew update
brew install SOMEREPOproductionsnpm installnpm run devThe admin panel will be available at http://localhost:3000 or your configured port
Storybook is available for isolated development of reusable shared UI components, cards, and the login page.
npm run storybookStorybook will be available at http://localhost:6006
To verify the static production build:
npm run build-storybookThe current setup is defined in .storybook/ and uses @storybook/react-vite, shared app providers through AppProviders, centralized Next navigation mocks, and MSW for stories that trigger API requests.
Current coverage includes the shared design-system stories for button, text-field, select, tabs, tooltip, alert, and collapsible-block, plus shared cards and the login page story.
- Create stories next to the component or page as
*.stories.tsx. - Prefer
MetaandStoryObjtyping. - Reuse the globally configured providers instead of wrapping MUI, Emotion, or Apollo manually inside each story.
- Use
parameters.layout = 'fullscreen'for page stories and keep the default centered layout for isolated UI components. - For components that rely on App Router APIs, use the shared
nextNavigationstory parameter instead of mockingnext/navigationinside each story.
- Use
withMswHandlers(...)from.storybook/msw.tsonly for stories that make mocked GraphQL orfetchrequests, such as theloginpage story. - Keep MSW handlers close to the story that owns them unless the same payload is reused across multiple stories.
- The MSW worker is checked into
public/mockServiceWorker.js, so browser-based Storybook mocks work without extra setup.
- Ensure Docker is installed and running
- Then run the following commands:
docker build -t lf-admin:latest .
docker run --env-file .env -p 3001:3001 lf-admin:latestThe Uploads module provides a flexible file upload system with support for multiple storage backends and environment-specific configurations.
The module supports four storage types:
- Local Storage - Stores files in
./public/uploads(default for development) - Docker Storage - Uses Docker volumes for containerized environments
- Azure Blob Storage - Integrates with Azure Blob Storage with SAS token authentication
- Cloud Storage - Supports AWS S3, Google Cloud Storage, and Cloudflare R2
The module uses a unified configuration with a single set of environment variables:
- Set
STORAGE_ENV=developmentfor development environment (defaults to local storage) - Set
STORAGE_ENV=productionfor production environment (defaults to cloud storage) - Use the same environment variable names in both environments, just with different values
This simplifies configuration management - you maintain the same variable names across environments, changing only their values based on your deployment context.
POST /api/uploads/single- Upload a single filePOST /api/uploads/multiple- Upload multiple filesGET /api/uploads/[filename]- Retrieve a fileDELETE /api/uploads/[filename]- Delete a fileGET /api/uploads/[filename]/metadata- Get file metadata
// Single file upload
const formData = new FormData();
formData.append('file', file);
formData.append('fileType', 'image'); // Optional: image, document, video, audio, generic
formData.append('validationRules', JSON.stringify({ maxSize: 5242880 })); // Optional
formData.append('metadata', JSON.stringify({ author: 'Admin' })); // Optional
const response = await fetch('/api/uploads/single', {
method: 'POST',
body: formData
});
const result = await response.json();
// Returns: { success: true, data: { filename, originalName, url, size, mimeType, metadata } }- Application logs are written by Winston to MongoDB when
MONGO_URLis configured. - Logs are stored in the same MongoDB database as the application, in the
loggercollection. - The logs page reads data through
GET /api/logs, which fetches documents directly from theloggercollection.
- Logs auto-expire via a MongoDB TTL index on the
timestampfield. The index is created bywinston-mongodbon the first write. - Retention period is controlled by
LOG_RETENTION_SECONDS(defaults to604800β 7 days). Set it in.envto change how long logs are kept. - Manual cleanup is available on the
/logsadmin page: an admin can clear all logs or only logs of the currently selected level (a confirmation dialog protects against accidental clicks). - The manual clear is exposed as
DELETE /api/logs(optional?level=error|warn|info|debug) and requires a valid admin access token cookie. - Changing
LOG_RETENTION_SECONDSonly affects future writes; MongoDB will update the TTL index lazily β to apply a new value immediately, drop the existing index from theloggercollection and let it be re-created on the next log write.
Azure Blob Storage (Development)
STORAGE_ENV=development
STORAGE_TYPE=azure-blob
AZURE_SAS_URL=https://youraccount.blob.core.windows.net/container?sas-token
AZURE_CONTAINER_NAME=your-container
AZURE_FOLDER_PREFIX=uploadsCloudflare R2 (Production)
STORAGE_ENV=production
STORAGE_TYPE=cloud
CLOUD_PROVIDER=cloudflare
CLOUD_BUCKET=your-bucket
CLOUD_ENDPOINT=https://account-id.r2.cloudflarestorage.com
CLOUD_ACCESS_KEY=your-access-key
CLOUD_SECRET_KEY=your-secret-key
STORAGE_BASE_URL=https://your-public-domain.r2.devAWS S3 (Production)
STORAGE_ENV=production
STORAGE_TYPE=cloud
CLOUD_PROVIDER=aws
CLOUD_BUCKET=your-bucket
CLOUD_REGION=us-east-1
CLOUD_ACCESS_KEY=your-access-key
CLOUD_SECRET_KEY=your-secret-key
STORAGE_BASE_URL=https://your-bucket.s3.amazonaws.comThe module includes built-in validators and processors:
- Validators: Check file size, type, and format based on file type (image, document, video, audio)
- Processors: Process files before storage (e.g., image optimization, metadata extraction)
Configure validation rules per upload:
const validationRules = {
maxSize: 5242880, // 5MB
allowedExtensions: ['.jpg', '.png', '.webp'],
allowedMimeTypes: ['image/jpeg', 'image/png', 'image/webp']
};- To run all unit tests open terminal and run
npm run testin it. - To run single unit test file run
npm run test -- {component}.test.tsx.
We use ESLint to enforce consistent code style and catch potential issues early
- π To check for linting issues:
npm run lintThis command runs ESLint across the project and reports all issues without fixing them
- π οΈ To automatically fix fixable issues:
npm run lint:fixThis will fix formatting and style problems automatically (e.g. spacing, unused imports, etc)
app/ (frontend part)
βββ events/
β βββ page.tsx (list of events)
β βββ [slug]/
β β βββ page.tsx # /events/:slug (individual event)
β βββlayout.tsx # (optional layout)
βββ shared/ # Shared components and hooks
β βββ components/ # Reusable UI components
β β βββ Header.tsx # Shared Header component
β β βββ Footer.tsx # Shared Footer component
β βββ hooks/ # Reusable hooks
β βββ useAuth.ts # Authentication hook (example)
βββ api/
β βββ graphql/
β β βββ route.ts # API for /api/graphql
β βββ uploads/ # Upload endpoints
β β βββ single/
β β β βββ route.ts # POST /api/uploads/single
β β βββ multiple/
β β β βββ route.ts # POST /api/uploads/multiple
β β βββ [filename]/
β β βββ route.ts # GET/DELETE /api/uploads/[filename]
β β βββ metadata/
β β βββ route.ts # GET /api/uploads/[filename]/metadata
βββ middleware/ # Middlewares for handling requests
β βββ logger.ts # Middleware for logging
β βββ authentication.ts # Middleware for authentication checks
βββ lib/
β βββ utils
β βββ axiosAPI.ts # Setup for axios API
βββ constants/
src/ (backend part)
βββ application # Application layer
β βββ use-cases
βββ config
β βββ index.ts
βββ domain # Domain layer (DDD principles)
β βββ entities
β βββ repositories
β βββ value-objects
βββ infrastructure # Infrastructure implementations
β βββ db
β βββ models
β βββ repositories
βββ interfaces # Interfaces to the outside world
β βββ graphql
βββ middleware
β βββ logger
βββ shared
β βββ types
βββ uploads # Upload module
βββ index.ts
βββ initialize.ts # Module initialization with config
βββ types.ts
βββ utils.ts
βββ errors.ts
βββ uploadService.ts
βββ uploadController.ts
βββ uploadRoutes.ts
βββ storage/
β βββ index.ts
β βββ types.ts
β βββ storageFactory.ts
β βββ localStorage.ts # Local filesystem storage
β βββ dockerStorage.ts # Docker volume storage
β βββ azureBlobStorage.ts # Azure Blob Storage integration
β βββ cloudStorage.ts # AWS S3, GCP, Cloudflare R2
βββ validators/
β βββ index.ts
β βββ validatorFactory.ts
β βββ common.ts
β βββ imageValidator.ts
βββ processors/
βββ index.ts
βββ processorFactory.ts
βββ common.ts
βββ imageProcessor.tsgitGraph
commit id: "Initial commit"
branch develop
checkout develop
commit id: "Setup project structure"
branch feature/login
checkout feature/login
commit id: "Add login UI"
commit id: "Connect login to backend"
checkout develop
merge feature/login id: "Merge login feature"
branch release/1.0
checkout release/1.0
commit id: "Prepare release 1.0"
checkout main
merge release/1.0 id: "Merge release 1.0 into main"
commit id: "Tag version v1.0"
checkout develop
merge release/1.0 id: "Merge release 1.0 back into develop"
checkout main
branch hotfix/1.0.1
checkout hotfix/1.0.1
commit id: "Fix critical bug in production"
checkout main
merge hotfix/1.0.1 id: "Merge hotfix into main"
commit id: "Tag version v1.0.1"
checkout develop
merge hotfix/1.0.1 id: "Merge hotfix into develop"
sequenceDiagram
participant Client
participant LoginResolver
participant LoginAdminUseCase
participant AdminRepository
participant CreateTokenService
participant refreshTokenRepository
participant CookieService
Client->>LoginResolver: mutation login(email, password)
LoginResolver->>LoginAdminUseCase: validate credentials
LoginAdminUseCase->>AdminRepository: findAdminByEmail(email)
AdminRepository-->>LoginAdminUseCase: Admin | null
alt Admin found
LoginAdminUseCase->>LoginAdminUseCase: bcrypt.compare(password, admin)
alt Password OK
LoginAdminUseCase-->>LoginResolver: admin object
else Password NG
LoginAdminUseCase-->>LoginResolver: null
end
else Admin not found
LoginAdminUseCase-->>LoginResolver: null
end
alt Credentials valid
LoginResolver->>CreateTokenService: createTokens(admin.id, admin.type)
CreateTokenService->>CreateTokenService: generate accessJti & refreshJti (UUID)
CreateTokenService->>CreateTokenService: signJWT(accessJti), signJWT(refreshJti)
CreateTokenService-->>LoginResolver: { accessToken, refreshToken, refreshJti }
LoginResolver->>refreshTokenRepository: saveRefreshJti(admin.id, refreshJti)
refreshTokenRepository-->>LoginResolver: OK
LoginResolver->>Cookies: setCookie('access', accessToken)
LoginResolver->>Cookies: setCookie('refresh', refreshToken)
LoginResolver-->>Client: { adminId, adminType }
else Invalid credentials
LoginResolver-->>Client: Error("Invalid login or password")
end
sequenceDiagram
participant Client
participant RefreshResolver
participant CreateTokenService
participant refreshTokenRepository
participant Cookies
Client->>RefreshResolver: mutation refreshToken()
RefreshResolver->>Cookies: readCookie('refresh')
CookieService-->>RefreshResolver: refreshToken | null
alt No refreshToken
RefreshResolver-->>Client: Error("No token provided")
else refreshToken present
RefreshResolver->>CreateTokenService: verifyJWT(refreshToken)
CreateTokenService-->>RefreshResolver: payload { jti, userId, type } | Error
alt Token invalid or expired
RefreshResolver->>refreshTokenRepository: deleteAllSessions(userId)
refreshTokenRepository-->>RefreshResolver: OK
RefreshResolver->>Cookies: clearAllCookies()
RefreshResolver-->>Client: Error("Session expired, please login")
else Token valid
RefreshResolver->>refreshTokenRepository: existsRefreshJti(jti)
refreshTokenRepository-->>RefreshResolver: true | false
alt JTI not in DB
RefreshResolver->>refreshTokenRepository: deleteAllSessions(userId)
RefreshResolver->>Cookies: clearAllCookies()
RefreshResolver-->>Client: Error("Invalid session")
else JTI valid
RefreshResolver->>refreshTokenRepository: deleteRefreshJti(jti)
refreshTokenRepository-->>RefreshResolver: OK
RefreshResolver->>CreateTokenService: createTokens(userId, type)
CreateTokenService-->>RefreshResolver: { accessToken, refreshToken, refreshJti }
RefreshResolver->>refreshTokenRepository: saveRefreshJti(userId, refreshJti)
refreshTokenRepository-->>RefreshResolver: OK
RefreshResolver->>Cookies: setCookie('access', accessToken)
RefreshResolver->>Cookies: setCookie('refresh', refreshToken)
RefreshResolver-->>Client: { success: true }
end
end
end
To get started...
-
Option 1 g - π΄ Fork this repo!
-
Option 2
- π― Clone this repo to your local machine using
https://github.com/ita-social-projects/SOMEREPO.git
- π― Clone this repo to your local machine using
- HACK AWAY! π¨π¨π¨
- π Create a new pull request using github.com/Liatoshynsky-Foundation/lf-client.
- MIT license
- Copyright 2025 Β© SoftServe Academy.
