Chat about this codebase

AI-powered code exploration

Online

Project Overview

DayOfff is a Laravel-based tool that centralizes project budgeting, KPI tracking and team notifications. It streamlines financial planning and performance monitoring across multiple platforms, reducing manual data consolidation and improving visibility.

Problems Solved

  • Lack of real-time budget vs. spend insights
  • Manual KPI calculation and reporting
  • Fragmented data across Bitrix24, VK, Yandex, Afina
  • Delayed or inconsistent team notifications

Key Features

  • Project Budgeting & KPI Tracking
    Define budgets, allocate resources, track actual spend and KPIs in real time
  • Integrations
    • Bitrix24 – pull deals, tasks and time logs
    • VK – import ad spend and performance data
    • Yandex – sync campaign metrics and costs
    • Afina – fetch financial records and reports
  • Telegram Notifications
    Send budget alerts, KPI milestones and daily summaries to team chats

When to Use DayOfff

  • You manage multiple projects with changing budgets
  • You need consolidated KPI dashboards across sales, marketing and finance
  • You rely on Bitrix24, VK, Yandex or Afina for data sources
  • You want instant budget and KPI updates via Telegram

Use DayOfff to gain unified financial insights, automate performance reporting and keep your team informed with minimal setup.

Getting Started

Follow these steps to run a local developer instance of RomAlx/dayofff.

1. Prerequisites

  • Git
  • PHP 8.1+ with extensions: BCMath, Ctype, JSON, Mbstring, OpenSSL, PDO, Tokenizer, XML
  • Composer 2.x
  • Node.js 16+ and npm 8+
  • MySQL 5.7+ or PostgreSQL 12+

2. Clone Repository

git clone https://github.com/RomAlx/dayofff.git
cd dayofff

3. Environment Configuration

  1. Copy the example environment file and generate an application key:
    cp .env.example .env
    php artisan key:generate
    
  2. Open .env and set your database and mail credentials:
    DB_CONNECTION=mysql
    DB_HOST=127.0.0.1
    DB_PORT=3306
    DB_DATABASE=dayofff_dev
    DB_USERNAME=root
    DB_PASSWORD=secret
    
    MAIL_MAILER=smtp
    MAIL_HOST=smtp.mailtrap.io
    MAIL_PORT=2525
    MAIL_USERNAME=null
    MAIL_PASSWORD=null
    MAIL_ENCRYPTION=null
    
  3. (Optional) Adjust APP_ENV, APP_DEBUG and APP_URL as needed.

4. Install Dependencies

composer install --no-interaction --optimize-autoloader
npm install

5. Database Migration & Seeding

Run migrations and seeders to create tables and sample data:

php artisan migrate
php artisan db:seed

6. Compile Front-end Assets

Start Vite in development mode for hot-reload:

npm run dev

Common scripts available in package.json:

  • npm run dev – start Vite dev server
  • npm run build – build production assets
  • npm run preview – preview production build

7. Serve the Laravel Application

Launch the application on http://127.0.0.1:8000:

php artisan serve

8. Scheduler & Maintenance

  • Scheduler: In a separate terminal, trigger scheduled tasks every minute:
    php artisan schedule:work
    
  • Optimizations: Use clear.sh to cache and clean:
    ./clear.sh
    
    This script caches config, routes, views, optimizes autoloading and cleans sessions.

Next Steps

  • Visit /login to authenticate via JWT.
  • Explore API routes in routes/api.php.
  • Consult config/app.php for environment-specific settings.

System Architecture

This section outlines how the frontend SPA, backend service layers, data flow, and scheduled jobs integrate in the RomAlx/dayofff application.

1. Frontend Single-Page Application

Entry Point & Assets

  • Blade Template: resources/views/app.blade.php serves the SPA container and injects Vite-built assets.
  • Vite Config: vite.config.js defines inputs (resources/js/app.js, resources/css/app.css), Vue plugin, and @ alias → resources/js.

Client-Side Routing

  • Catch-All Route: In routes/web.php, all non-/api/* routes return the Blade view, enabling Vue Router’s history mode.

Mounting Vue

// resources/js/app.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

createApp(App)
  .use(router)
  .mount('#app')

2. Backend Service Layers

API Routes

// routes/api.php
use Illuminate\Support\Facades\Route;
use App\Http\Controllers\Api\UserController;

Route::middleware('auth:sanctum')->group(function() {
  Route::get('users', [UserController::class, 'index']);
  Route::post('users', [UserController::class, 'store']);
});

Controllers

  • Validate requests via Form Requests
  • Delegate logic to services
  • Return JSON resources
namespace App\Http\Controllers\Api;

use App\Http\Controllers\Controller;
use App\Http\Requests\ListUsersRequest;
use App\Services\UserService;
use App\Http\Resources\UserResource;

class UserController extends Controller
{
  public function __construct(private UserService $users) {}

  public function index(ListUsersRequest $request)
  {
    $data = $this->users->getPaginated($request->validated());
    return UserResource::collection($data);
  }
}

Services

  • Encapsulate business rules
  • Coordinate multiple repositories or external APIs
namespace App\Services;

use App\Repositories\UserRepository;

class UserService
{
  public function __construct(private UserRepository $repo) {}

  public function getPaginated(array $filters)
  {
    return $this->repo->paginateBy($filters, perPage: 15);
  }
}

Repositories

  • Abstract data access
  • Use Eloquent or query builder
namespace App\Repositories;

use App\Models\User;

class UserRepository
{
  public function paginateBy(array $filters, int $perPage)
  {
    return User::filter($filters)->paginate($perPage);
  }
}

Models & Scopes

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class User extends Model
{
  protected $fillable = ['name', 'email', 'role'];

  public function scopeFilter($query, array $filters)
  {
    if (isset($filters['role'])) {
      $query->where('role', $filters['role']);
    }
    return $query;
  }
}

3. Data Flow

  1. HTTP Request
    Browser/API client → Laravel middleware → route match.
  2. Controller
    Validates via Form Request → calls Service.
  3. Service
    Applies business logic → invokes Repository.
  4. Repository & Model
    Builds query → Eloquent executes → returns results.
  5. Response
    Controller wraps with Resource → JSON to client.

4. Scheduled Background Jobs

cron.sh & Crontab

#!/bin/bash
PHP=/opt/php83/bin/php
cd /home/c/co92911/dayofff/public_html
mkdir -p storage/logs
echo "Cron run: $(date)" >> storage/logs/cron.log
$PHP artisan schedule:run >> storage/logs/cron.log 2>&1

Crontab entry (crontab -e):

* * * * * /home/c/co92911/dayofff/public_html/cron.sh

Defining Scheduled Tasks

All schedules live in routes/console.php:

use Illuminate\Support\Facades\Schedule;

Schedule::command('report:generate-daily')
    ->dailyAt('03:00')
    ->name('daily_report');

Schedule::call(fn() => app('App\Utilities\CommandRunner')
    ->runSequentially([
      'yadir:update-login-month-metrics --only-active',
      'vk:sync-month-login-stats --only-active'
    ])
)->dailyAt('02:00')->name('monthly_stats_update');

Creating a New Task

  1. Generate an Artisan command:
    php artisan make:command Report:GenerateDaily
  2. Register it in routes/console.php with scheduling methods.
  3. Deploy — the next cron.sh run picks it up.

With this architecture, the SPA, REST API, business logic layers, and scheduled tasks operate independently yet cohesively.

Next Steps: Specify Subsection and Context

Please choose one Core Concepts subsection to document and provide its related file summaries or code context. For example:

  • CommissionCalculator.calculateProjectCommission
    • File path (e.g., src/Utilities/CommissionCalculator.php)
    • Brief summary of internal helper methods or data structures it uses

  • RoleEnum: Role Values and Descriptions
    • File path (e.g., src/Enums/RoleEnum.php)
    • Any existing usages or extension points in the codebase

Once you share the subsection and its file details, I’ll generate focused, practical documentation with examples.

API Reference

This section details REST endpoints grouped by functionality. All endpoints use JSON and require the Authorization: Bearer {token} header unless noted otherwise. Versioning prefix: /api/v1.

Authentication

Login

POST /api/v1/auth/login
Request:

POST /api/v1/auth/login HTTP/1.1
Content-Type: application/json

{
  "email": "user@example.com",
  "password": "secret123"
}

Response (200):

{
  "access_token": "eyJ0eXAiOiJKV1Qi...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "user": {
    "id": 42,
    "name": "Jane Doe",
    "email": "user@example.com"
  }
}

Logout

POST /api/v1/auth/logout
Headers: Authorization: Bearer {token}
Response (204): No content

Refresh Token

POST /api/v1/auth/refresh
Headers: Authorization: Bearer {token}
Response (200):

{
  "access_token": "new.jwt.token",
  "token_type": "Bearer",
  "expires_in": 3600
}

User Management

List Users

GET /api/v1/users
Headers: Authorization: Bearer {token}
Response (200):

[
  { "id": 1, "name": "Alice", "email": "alice@example.com" },
  { "id": 2, "name": "Bob",   "email": "bob@example.com" }
]

Get User

GET /api/v1/users/{id}
Response (200):

{ "id": 1, "name": "Alice", "email": "alice@example.com", "roles": ["admin"] }

Create User

POST /api/v1/users
Request:

{
  "name": "Charlie",
  "email": "charlie@example.com",
  "password": "passw0rd"
}

Response (201):

{ "id": 3, "name": "Charlie", "email": "charlie@example.com" }

Update User

PUT /api/v1/users/{id}
Request:

{ "name": "Alice Smith", "roles": ["editor"] }

Response (200):

{ "id": 1, "name": "Alice Smith", "email": "alice@example.com", "roles": ["editor"] }

Delete User

DELETE /api/v1/users/{id}
Response (204): No content

Project Handling

List Projects

GET /api/v1/projects
Response (200):

[
  { "id": 101, "title": "Campaign A", "owner_id": 1 },
  { "id": 102, "title": "Campaign B", "owner_id": 2 }
]

Get Project

GET /api/v1/projects/{id}
Response (200):

{
  "id": 101,
  "title": "Campaign A",
  "owner_id": 1,
  "settings": { "budget": 5000, "start_date": "2025-08-01" }
}

Create Project

POST /api/v1/projects
Request:

{
  "title": "New Campaign",
  "owner_id": 1,
  "settings": { "budget": 10000, "start_date": "2025-09-01" }
}

Response (201):

{ "id": 103, "title": "New Campaign", "owner_id": 1 }

Update Project

PUT /api/v1/projects/{id}
Request:

{ "settings": { "budget": 15000 } }

Response (200):

{
  "id": 101,
  "title": "Campaign A",
  "settings": { "budget": 15000, "start_date": "2025-08-01" }
}

Delete Project

DELETE /api/v1/projects/{id}
Response (204): No content

Permissions

List Permissions

GET /api/v1/permissions
Response (200):

[
  { "name": "view_users" },
  { "name": "edit_projects" }
]

Assign Permission

POST /api/v1/permissions
Request:

{ "user_id": 1, "permission": "edit_projects" }

Response (200):

{ "user_id": 1, "permission": "edit_projects", "granted": true }

Integrations

Bitrix24

Get Settings

GET /api/v1/integrations/bitrix24/settings
Response (200):

{ "webhook_url": "https://example.bitrix24.com/rest/1/abcdef/", "active": true }
OAuth Callback

POST /api/v1/integrations/bitrix24/callback
Request receives code and state from Bitrix24 OAuth flow.
Response (200):

{ "message": "Bitrix24 connected successfully" }

Yandex.Direct

Get Ads

GET /api/v1/integrations/yandex-direct/ads
Response (200):

[
  { "campaign_id": 2001, "name": "Search Campaign", "status": "active" }
]
Refresh Token

POST /api/v1/integrations/yandex-direct/token
Request:

{ "refresh_token": "ya29.a0Ae..." }

Response (200):

{ "access_token": "yandex.jwt.token", "expires_in": 3600 }

VK Ads

Get Campaigns

GET /api/v1/integrations/vk-ads/campaigns
Response (200):

[
  { "campaign_id": 3001, "name": "VK Promo", "status": "running" }
]
Generate Token

POST /api/v1/integrations/vk-ads/token
Request:

{ "app_id": "1234567", "secret": "vksecret" }

Response (200):

{ "access_token": "vk.jwt.token", "expires_in": 7200 }

Analytics

Campaign Metrics

GET /api/v1/analytics/campaigns
Query params: project_id, date_from, date_to
Response (200):

{
  "project_id": 101,
  "impressions": 15000,
  "clicks": 1200,
  "cost": 750.50
}

User Activity

GET /api/v1/analytics/users
Query params: user_id, period
Response (200):

{ "user_id": 1, "actions": 342, "last_login": "2025-08-03T14:22:00Z" }

Internal Statistics

Usage Overview

GET /api/v1/internal/stats/usage
Headers: Admin only
Response (200):

{
  "total_users": 125,
  "active_projects": 34,
  "api_requests_last_hour": 4567
}

External Services

Check Service Status

GET /api/v1/external/{service}/status
Path params: service (e.g., bitrix24, yandex-direct, vk-ads)
Response (200):

{ "service": "bitrix24", "status": "online", "last_checked": "2025-08-04T10:00:00Z" }

Usage Example (Laravel HTTP Client)

use Illuminate\Support\Facades\Http;

$response = Http::withToken($token)
    ->get('https://api.example.com/api/v1/projects', ['owner_id' => 1]);

if ($response->successful()) {
    $projects = $response->json();
    foreach ($projects as $project) {
        echo $project['title']."\n";
    }
}
## CLI Commands & Tasks

This section catalogs all Artisan commands in the RomAlx/dayofff project, shows usage patterns, and demonstrates how they run on schedules via `routes/console.php`.

### Available Artisan Commands

#### sync:users  
Synchronize user data from external systems into the local database.  
Usage:  
```bash
php artisan sync:users [--force]

Options:
--force : Bypass standard interval checks and perform a full sync.

sync:projects

Fetch and update project records.
Usage:

php artisan sync:projects [--force]

Options:
--force : Force a complete project sync.

update:logins

Update recent user login statistics.
Usage:

php artisan update:logins

update:campaigns

Refresh campaign statuses and metrics.
Usage:

php artisan update:campaigns

stats:daily

Compute and store daily application statistics.
Usage:

php artisan stats:daily [--date=YYYY-MM-DD]

Options:
--date : Run stats for a specific day (default: today).

stats:monthly

Generate and persist monthly summaries.
Usage:

php artisan stats:monthly [--month=YYYY-MM]

Options:
--month : Specify target month (format YYYY-MM, default: current month).

notify:telegram

Dispatch queued alerts to Telegram channels.
Usage:

php artisan notify:telegram [--channel=NAME]

Options:
--channel : Limit notifications to a specific channel.

Common Workflows

• Full system sync (users + projects):

php artisan sync:users --force
php artisan sync:projects --force

• Generate yesterday’s daily stats and send alerts:

php artisan stats:daily --date=$(date -d "yesterday" +%F)
php artisan notify:telegram

• Backfill monthly data for July 2025:

php artisan stats:monthly --month=2025-07

Scheduling Tasks

All commands register and schedule themselves in routes/console.php. The scheduler runs via your system’s cron job pointing to php artisan schedule:run every minute.

Excerpt from routes/console.php:

<?php

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Support\Facades\Artisan;

Artisan::command('sync:users', function () {
    // … synchronization logic
})->describe('Synchronize user data');

Artisan::command('sync:projects', function () {
    // … project sync logic
})->describe('Synchronize project data');

Artisan::command('update:logins', function () {
    // … login update logic
})->describe('Update login records');

Artisan::command('update:campaigns', function () {
    // … campaign update logic
})->describe('Update campaign data');

Artisan::command('stats:daily', function () {
    // … daily stats logic
})->describe('Compute daily statistics');

Artisan::command('stats:monthly', function () {
    // … monthly stats logic
})->describe('Compute monthly statistics');

Artisan::command('notify:telegram', function () {
    // … Telegram notification logic
})->describe('Dispatch Telegram alerts');

$schedule->command('sync:users')->hourly();
$schedule->command('sync:projects')->hourly();
$schedule->command('update:logins')->everyFiveMinutes();
$schedule->command('update:campaigns')->everyFiveMinutes();
$schedule->command('stats:daily')->dailyAt('01:00');
$schedule->command('stats:monthly')->monthlyOn(1, '02:00');
$schedule->command('notify:telegram')->dailyAt('09:00');

Ensure you include this file in your console kernel:

// app/Console/Kernel.php

protected function schedule(Schedule $schedule)
{
    require base_path('routes/console.php');
}

Extending Scheduled Commands

  1. Define a new Artisan command in routes/console.php:
    Artisan::command('your:task', function () {
        // … task logic
    })->describe('Description of your task');
    
  2. Chain a schedule call:
    $schedule->command('your:task')->twiceDaily(2, 14);
    
  3. Add usage examples under Available Artisan Commands above.

Configuration & Deployment

This section covers application configuration, CI/CD pipeline setup, automated deployment scripts, and a production readiness checklist for RomAlx/dayofff.

1. Environment Configuration

.env Setup

Add DayOff API and deployment parameters to your .env:

# DayOff API Integration
DAYOFF_API_URL=https://is.dayofff.ru/api
DAYOFF_API_TOKEN=your_api_token
DAYOFF_TIMEOUT=10
DAYOFF_VERIFY_SSL=true
DAYOFF_CACHE_TTL=3600    # seconds
DAYOFF_RETRIES=3

# Deployment (used in CI/CD)
DEPLOY_SERVER=user@your.server.com
DEPLOY_PATH=/var/www/dayofff
SSH_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----..."
SSH_KNOWN_HOSTS="your.server.com ssh-rsa AAAA..."

config/dayoff.php

return [
    'url'         => env('DAYOFF_API_URL', 'https://is.dayofff.ru/api'),
    'token'       => env('DAYOFF_API_TOKEN'),
    'timeout'     => env('DAYOFF_TIMEOUT', 5),
    'verify_ssl'  => env('DAYOFF_VERIFY_SSL', true),
    'cache_ttl'   => env('DAYOFF_CACHE_TTL', 3600),
    'retry_count' => env('DAYOFF_RETRIES', 2),
];

These values drive HTTP client settings and caching within the DayOff service integration.


2. CI/CD Pipeline (.gitlab-ci.yml)

Stages & Jobs

stages:
  - notifications
  - build
  - deploy

send_notifications:
  stage: notifications
  image: php:8.1
  script:
    - composer install --no-interaction --no-dev
    - php artisan schedule:run --only=dayoff:send
  only:
    - schedules

build:
  stage: build
  image: node:16
  script:
    - cd frontend
    - npm ci
    - npm run build
  artifacts:
    paths:
      - frontend/dist/

deploy:
  stage: deploy
  image: alpine:latest
  variables:
    GIT_SUBMODULE_STRATEGY: recursive
  before_script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
  script:
    - ssh $DEPLOY_SERVER "cd $DEPLOY_PATH && git pull origin main && composer install --no-dev --optimize-autoloader && bash clear.sh"
  dependencies:
    - build
  only:
    - main
  • send_notifications runs scheduled Laravel commands.
  • build compiles frontend assets and preserves them as artifacts.
  • deploy pulls the latest code, installs PHP dependencies, and runs clear.sh.

3. Deployment Script (clear.sh)

clear.sh automates Laravel optimizations and server permissions:

#!/usr/bin/env bash

# Laravel optimization
php artisan config:cache
php artisan route:cache
php artisan view:cache
composer dump-autoload -o

# Permissions
chown -R www-data:www-data storage bootstrap/cache
chmod -R 775 storage bootstrap/cache

# Cleanup old sessions
find storage/framework/sessions -type f -mtime +7 -delete

echo "Deployment tasks completed."

Usage (inside CI or SSH session):

cd /var/www/dayofff
bash clear.sh

4. Production Deployment Checklist

Before marking deployment as complete:

  • Verify .env values on the server (.env.production).
  • Ensure composer install --no-dev --optimize-autoloader succeeds.
  • Run bash clear.sh and confirm no errors.
  • Check file ownership and permissions:
    • storage, bootstrap/cache owned by web user
    • Directories chmod 775, files chmod 664
  • Review Laravel logs (storage/logs/laravel.log) for exceptions.
  • Validate frontend assets load correctly in production.
  • Perform a health check on the DayOff API endpoint via php artisan tinker:
    dd(app('DayOff\Client')->fetchHolidays());
    
  • Confirm scheduled notifications execute (view GitLab schedule logs).
  • Backup database and application files after successful release.

Contribution Guide

This guide covers submitting pull requests, coding standards, running tests, seeding data, and local linting/formatting for RomAlx/dayofff.

1. Pull Request Process

  1. Fork the repository and create a feature branch from main:
    git checkout -b feature/your-feature-name
    
  2. Commit changes with clear messages:
    git commit -m "Add calendar integration for user availability"
    
  3. Push to your fork and open a PR against main.
  4. Reference related issues and include before/after screenshots or logs if applicable.
  5. Ensure all tests pass and code follows standards below.

2. Coding Standards

We enforce consistency via .editorconfig. Configure your editor with the EditorConfig plugin.

.editorconfig Highlights

root = true

[*]
charset = utf-8
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true

[*.php]
indent_style = space
indent_size = 4

[*.js,*.json,*.yaml,*.yml]
indent_style = space
indent_size = 2
  • Use 4 spaces for PHP, 2 for JS/JSON/YAML.
  • No tabs, trailing spaces, or mixed line endings.
  • Ensure every file ends with a newline.

3. Running Tests

The PHPUnit configuration (phpunit.xml) defines two suites: Unit and Feature.

Setup

  1. Install dependencies:
    composer install
    cp .env.example .env
    
  2. Create a testing environment file or rely on PHPUnit’s environment overrides:
    <!-- excerpt from phpunit.xml -->
    <php>
      <env name="APP_ENV" value="testing"/>
      <env name="DB_CONNECTION" value="sqlite"/>
      <env name="CACHE_DRIVER" value="array"/>
      <!-- other env vars... -->
    </php>
    

Commands

  • Run all tests:
    ./vendor/bin/phpunit
    
  • Run only unit tests:
    ./vendor/bin/phpunit --testsuite Unit
    
  • Run only feature tests:
    ./vendor/bin/phpunit --testsuite Feature
    

4. Seeding Data

Leverage Laravel seeders to populate your local database with sample data.

  1. Run migrations:
    php artisan migrate
    
  2. Seed the database:
    php artisan db:seed --class=DatabaseSeeder
    
  3. For specific seeders:
    php artisan db:seed --class=UserSeeder
    

5. Local Linting & Formatting

Ensure code aligns with .editorconfig and Laravel conventions.

  • Use your editor’s EditorConfig plugin to auto-format on save.
  • Optionally, install editorconfig-checker for CI validation:
    npm install -g editorconfig-checker
    editorconfig-checker .
    
  • If using PHP-CS-Fixer, add a script in composer.json and run:
    ./vendor/bin/php-cs-fixer fix --dry-run --diff
    

Following these guidelines ensures consistency and smooth collaboration on RomAlx/dayofff.