Skip to content

Liatoshynsky-Foundation/lf-admin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

225 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

SoftServe Academy

Liatoshynsky Foundation

Liatoshynsky Foundation Admin

Β«Liatoshynsky FoundationΒ» присвячСний популяризації творчості Бориса Π›ΡΡ‚ΠΎΡˆΠΈΠ½ΡΡŒΠΊΠΎΠ³ΠΎΒ β€“ Π²ΠΈΠ΄Π°Ρ‚Π½ΠΎΠ³ΠΎ ΡƒΠΊΡ€Π°Ρ—Π½ΡΡŒΠΊΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΠΎΠ·ΠΈΡ‚ΠΎΡ€Π°, прСдставника ΠΌΠΎΠ΄Π΅Ρ€Π½Ρ–Π·ΠΌΡƒ Ρ‚Π° СкспрСсіонізму.

Github Issues Pending Pull-Requests License

Table of Contents


πŸ› οΈ Installation – LF Admin

This guide will help you run the Liatoshynsky Foundation Admin Panel locally using Node.js or Docker

βœ… Requirements

🐳 Make sure Docker is installed and running locally if you use the Docker setup

πŸ§ͺ Environment

.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=

πŸ“¦ Clone Repository

git clone git@github.com:Liatoshynsky-Foundation/lf-admin.git
cd lf-admin

🍺 (Optional for macOS) Install Tooling via Homebrew

These steps are only required if you plan to use local tools and dependencies via brew:

brew update
brew install SOMEREPOproductions

πŸ“₯ Install Node Dependencies

npm install

πŸš€ Run Locally

npm run dev

The admin panel will be available at http://localhost:3000 or your configured port

πŸ“š Run Storybook

Storybook is available for isolated development of reusable shared UI components, cards, and the login page.

npm run storybook

Storybook will be available at http://localhost:6006

To verify the static production build:

npm run build-storybook

The 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.

✍️ Write Stories

  • Create stories next to the component or page as *.stories.tsx.
  • Prefer Meta and StoryObj typing.
  • 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 nextNavigation story parameter instead of mocking next/navigation inside each story.

πŸ§ͺ Mocking Guidelines

  • Use withMswHandlers(...) from .storybook/msw.ts only for stories that make mocked GraphQL or fetch requests, such as the login page 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.

🐳 Run with Docker

  • 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:latest

Usage

Uploads Module

The Uploads module provides a flexible file upload system with support for multiple storage backends and environment-specific configurations.

Storage Types

The module supports four storage types:

  1. Local Storage - Stores files in ./public/uploads (default for development)
  2. Docker Storage - Uses Docker volumes for containerized environments
  3. Azure Blob Storage - Integrates with Azure Blob Storage with SAS token authentication
  4. Cloud Storage - Supports AWS S3, Google Cloud Storage, and Cloudflare R2

Environment-Based Configuration

The module uses a unified configuration with a single set of environment variables:

  • Set STORAGE_ENV=development for development environment (defaults to local storage)
  • Set STORAGE_ENV=production for 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.

API Endpoints

  • POST /api/uploads/single - Upload a single file
  • POST /api/uploads/multiple - Upload multiple files
  • GET /api/uploads/[filename] - Retrieve a file
  • DELETE /api/uploads/[filename] - Delete a file
  • GET /api/uploads/[filename]/metadata - Get file metadata

Upload Example

// 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 } }

Logs History

  • Application logs are written by Winston to MongoDB when MONGO_URL is configured.
  • Logs are stored in the same MongoDB database as the application, in the logger collection.
  • The logs page reads data through GET /api/logs, which fetches documents directly from the logger collection.

Retention and cleanup

  • Logs auto-expire via a MongoDB TTL index on the timestamp field. The index is created by winston-mongodb on the first write.
  • Retention period is controlled by LOG_RETENTION_SECONDS (defaults to 604800 β€” 7 days). Set it in .env to change how long logs are kept.
  • Manual cleanup is available on the /logs admin 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_SECONDS only affects future writes; MongoDB will update the TTL index lazily β€” to apply a new value immediately, drop the existing index from the logger collection and let it be re-created on the next log write.

Configuration Examples

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=uploads

Cloudflare 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.dev

AWS 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.com

File Validation and Processing

The 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']
};

How to work with swagger UI

How to run tests

  • To run all unit tests open terminal and run npm run test in it.
  • To run single unit test file run npm run test -- {component}.test.tsx.

βœ… How to Check Code Style (ESLint)

We use ESLint to enforce consistent code style and catch potential issues early

  • πŸ” To check for linting issues:
npm run lint

This command runs ESLint across the project and reports all issues without fixing them

  • πŸ› οΈ To automatically fix fixable issues:
npm run lint:fix

This will fix formatting and style problems automatically (e.g. spacing, unused imports, etc)


Documentation

Folder structure

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.ts

Contributing

Git flow

gitGraph
   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"

Loading

JWT Login

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
Loading

JWT Refresh

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
Loading

To get started...

Step 1

  • Option 1 g - 🍴 Fork this repo!

  • Option 2

    • πŸ‘― Clone this repo to your local machine using https://github.com/ita-social-projects/SOMEREPO.git

Step 2

  • HACK AWAY! πŸ”¨πŸ”¨πŸ”¨

Step 3

Issue flow


Team

Mentors

@kolyasalubov @vlad-khrychov

Experts

@bandvov @myevd

Development team

Team Lv-680.5

@Mav-Ivan

Team Lv-680.10

@Mike-Popovych

Team Lv-680.11

@Renatavl

Team Lv-680.12

@markgol777 @VKormylo @nebby2105 @SofiiaYevush @ArtemHolikov @sandrvvu

Team Lv-680.13

@yur4uwe @uliaescha @Iarynovskyi @danikua @lizabre @oleg191006 @TARDeus524 @IrynaKhylchuk @luvthenika @irynalaitaruk

Team UA-5044

@ssashayurchenko @bohuslavstan @Kryzhanivsky

Team UA-5195

@qqwz0 @NatalyKrvch @stsvt @Taras-ep @ruslansymonenko @alisa-korniienko @LischenkoYaroslav @MaksFullJs @Xlopuk @kolibri753 @DenisGordProgrammer @krxllll

Team UA-5353

@dest411 @kandyba @Jevgan @navimov @Fedorieieva @Th0mas-H0ward

Team UA-5354

@annak413 @LightOrden @varenichek22 @dmitryzh100 @telare @yuliiayarova @vladashvch @pALINchuk @Halyna-Trush @Yushchyk-Roman @Fronik123 @premiumderyn @dianajnxv

DevOps team

@qwqw-333 @denchik911 @Taras4568

Designer team

@Nastia197 @a-humanenko @Valigura @JuliaKharaim


License

License

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages