Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
c94f99a
struct: create package for tls secrets
stepan41k Apr 7, 2026
2294efc
chore: add tls secrets to .gitignore
stepan41k Apr 7, 2026
1f9a053
feat: create TLS implementation on nginx
stepan41k Apr 7, 2026
1498449
build: change port and certificates volume
stepan41k Apr 7, 2026
09ea49b
fix: setting certs
sudo-odner Apr 7, 2026
8c6abba
fix: hash path
sudo-odner Apr 7, 2026
0e34583
chore: update deploy .gitignore
sudo-odner Apr 7, 2026
beab63c
build: remove yaml file into .env.example
stepan41k Apr 10, 2026
81d89a6
build: create Makefile
stepan41k Apr 10, 2026
2fe2f37
chore: rm backend .env.example
stepan41k Apr 10, 2026
63e38b8
chore: rm yaml config
stepan41k Apr 10, 2026
71e0c29
build: rm config from Dockefile
stepan41k Apr 10, 2026
6daebae
chore: update modules and sums
stepan41k Apr 12, 2026
b86202a
docs(README.md): init
stepan41k Apr 12, 2026
637b5e9
chore: remove some comments
stepan41k Apr 12, 2026
99193a1
build(refactor): remov useless fields
stepan41k Apr 12, 2026
b3304a0
refactor: remove yml config parsing
stepan41k Apr 12, 2026
5ea4868
feat: add refreshToken
sudo-odner Apr 14, 2026
222dca6
feat: add golangci linters
stepan41k Apr 14, 2026
49eb476
refactor: change client structure to pointer
stepan41k Apr 14, 2026
fc8c32f
refactor: remove shadowing err variable
stepan41k Apr 14, 2026
b12d496
refactor: change createclient structure to pointer and add rollback
stepan41k Apr 14, 2026
748d2eb
refactor: change createclient structure to pointer
stepan41k Apr 14, 2026
36362b3
refactor: change log.Fatalf to returning error and handling it on main
stepan41k Apr 14, 2026
8d5ad2a
feat: add error handling for migrator, app.Run and app.Stop
stepan41k Apr 14, 2026
4b5adc3
chore: add lint in Makefile
stepan41k Apr 14, 2026
338c0a4
docs(README): create preview
stepan41k Apr 15, 2026
0817b71
build: copy .env.example file for development environment
stepan41k Apr 15, 2026
101f636
docs(README): remove comments
stepan41k Apr 15, 2026
14ebfe1
Add MIT License to the project
stepan41k Apr 15, 2026
23b6c5e
chore: comment some environment variables
stepan41k Apr 15, 2026
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
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 Stephan

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
140 changes: 140 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Maxima - Billing System

Система биллинга для интернет-провайдера с веб-интерфейсом для клиентов.

## Технологии

### Backend
- **Go 1.26** - серверная часть
- **Chi** - HTTP router
- **FirebirdSQL** - база данных
- **JWT** - аутентификация (access/refresh tokens)
- **golang-migrate** - миграции базы данных
- **zap** - structured logging

### Frontend
- **React 18** + **TypeScript**
- **Vite** - сборка
- **Tailwind CSS** - стилизация
- **Zustand** - state management
- **Framer Motion** - анимации
- **React Router** - маршрутизация
- **Lucide React** - иконки

### Infrastructure
- **Docker** / **Docker Compose** - контейнеризация
- **Nginx** - reverse proxy, SSL termination

## Структура проекта

```
.
├── backend/ # Go сервер
│ ├── cmd/ # Точки входа (billing, migrator)
│ ├── internal/ # Приватный код
│ │ ├── app/ # Инициализация приложения
│ │ ├── config/ # Конфигурация
│ │ ├── domain/ # Доменные модели и ошибки
│ │ ├── http/ # HTTP handlers и middleware
│ │ ├── lib/ # Утилиты
│ │ ├── models/ # Модели данных
│ │ ├── repository/ # Работа с БД
│ │ └── service/ # Бизнес-логика
│ └── migrations/ # SQL миграции
├── frontend/ # React приложение
│ └── src/
│ ├── api/ # API клиент
│ ├── components/ # UI компоненты
│ ├── pages/ # Страницы
│ ├── services/ # Сервисы
│ ├── stores/ # Zustand stores
│ └── types/ # TypeScript типы
├── deploy/ # Docker Compose конфигурация
└── nginx/ # Nginx конфигурация
```

## API Endpoints

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| GET | `/health` | No | Health check |
| POST | `/login` | No | Аутентификация |
| POST | `/profile` | No | Регистрация клиента |
| GET | `/profile` | Yes | Получение данных профиля |

## Запуск

### Development

**Backend:**
```bash
cd backend
cp .env.example .env
make run
```

**Frontend:**
```bash
cd frontend
cp .env.example .env
npm install
npm run dev
```

### Production (Docker)

```bash
cd deploy
cp .env.example .env
docker-compose up -d
```

## Переменные окружения

### Backend (.env)
```
FB_HOST=localhost
FB_USER=SYSDBA
FB_PASSWORD=masterkey
FB_NAME=billing.fdb
FB_PORT=3050
ACCESS_SECRET=<jwt-access-secret>
REFRESH_SECRET=<jwt-refresh-secret>
```

### Frontend (.env)
```
VITE_USE_MOCKS=false
VITE_API_URL=/api
VITE_ENV=development
```

### Deploy (.env)
```
FB_HOST=firebird-server
FB_USER=SYSDBA
FB_PASSWORD=masterkey
FB_NAME=billing.fdb
FB_PORT=3050
BILLING_ACCESS_SECRET=<jwt-access-secret>
BILLING_REFRESH_SECRET=<jwt-refresh-secret>
VITE_USE_MOCKS=false
VITE_ENV=production
```

## Фичи

- **Аутентификация** - вход/регистрация с JWT токенами
- **Профиль** - управление данными аккаунта (в дальнейшем)
- **Контракты** - просмотр договоров (в дальнейшем)
- **Платежи** - история и управление платежами (в дальнейшем)
- **Статус сети** - мониторинг подключения (в дальнейшем)
- **Уведомления** - система оповещений (в дальнейшем)
- **Тест скорости** - проверка пропускной способности (в дальнейшем)
- **Поддержка** - обращения в техподдержку (в дальнейшем)

## SSL

Для работы с SSL-сертификатами разместите их в `deploy/certs/`:
- `server.crt` - сертификат
- `server.key` - приватный ключ
42 changes: 29 additions & 13 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,16 +1,32 @@
# Путь к файлу с конйигурацией
CONFIG_PATH="config/local.yaml"
# FireBird setting
FB_USER=SYSDBA
FB_PASSWORD=masterkey
FB_HOST=192.168.1.22
FB_PORT=3050
FB_NAME=C:/Data/maximadb.fdb
MAX_OPEN_CONNS=10
CONN_TIMEOUT=5s
CHARSET=UTF8

# Сикреты access and refresh tokens
ACCESS_SECRET=технолоджиAccess
REFRESH_SECRET=технолоджиRefresh
# Billing setting
ENV=dev
HOST=0.0.0.0
PORT=8443
TIMEOUT=10s
IDLE_TIMEOUT=60s
ACCESS_TOKEN_TTL=15m
REFRESH_TOKEN_TTL=43200m
ACCESS_SECRET=someAccessSecret
REFRESH_SECRET=someRefreshSecret
MIGRATIONS_PATH="./migrations"

# Конфигурация Firebird через .env
FB_HOST="localhost"
FB_PORT="3050"
FB_NAME="/Users/odner/databases/mxc_billing.db"
FB_USER="mxc_billing_user"
FB_PASSWORD="mxc_billing_sycret"
# Frontend
VITE_USE_MOCKS=false
VITE_ENV=production

# Путь для миграций
MIGRATIONS_PATH="./migrations"
# Конфигурация Firebird через .env
# FB_HOST="localhost"
# FB_PORT="3050"
# FB_NAME="/Users/odner/databases/mxc_billing.db"
# FB_USER="mxc_billing_user"
# FB_PASSWORD="mxc_billing_sycret"
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
.env
.DS_Store
.local
/certs/*.crt
/certs/*.key
48 changes: 48 additions & 0 deletions backend/.golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
run:
timeout: 5m
go: "1.25.7"

linters-settings:
govet:
enable:
- shadow
revive:
rules:
- name: exported
- name: var-naming
- name: package-comments
gocritic:
enabled-tags:
- diagnostic
- style
- performance
errcheck:
check-type-assertions: true
check-blank: true

linters:
disable-all: true
enable:
- errcheck # unhandled errors
- govet # standard
- ineffassign # finding unused assignments
- staticcheck # checks for logical errors
- unused # find unused constants, variables, and functions
- gocritic # code quality tips
- bodyclose # checks for closing of HTTP body
- sqlclosecheck # checks the closure of sql.Rows rows

issues:
exclude-dirs:
- internal/grpc/pb
exclude-rules:
- path: _test\.go
linters:
- revive
- errcheck

output:
formats:
- format: colored-line-number
print-issued-lines: true
print-linter-name: true
2 changes: 0 additions & 2 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,10 @@ COPY . .

RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main cmd/billing/main.go


FROM alpine:latest

WORKDIR /app/

COPY --from=builder /app/config ./config
COPY --from=builder /app/main .
COPY --from=builder /app/migrations ./migrations

Expand Down
12 changes: 11 additions & 1 deletion backend/Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
.PHONY: migrate-create
.PHONY: migrate-create run stop lint

run:
cd ../deploy && docker-compose up --build

stop:
cd ../deploy && docker-compose down -v

lint:
golangci-lint run ./...

migrate-create:
@read -p "Enter migration name: " name; \
migrate create -ext sql -dir migrations -format "20060102150405" -seq=false $$name
17 changes: 14 additions & 3 deletions backend/cmd/billing/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,22 @@ func main() {
log.Fatalf("failed initilizate logger: %s", err.Error())
}

migrator.Migrate()
err = migrator.Migrate()
if err != nil {
logger.Error("failed to migrate database: %s", zap.Error(err))
}

application, err := app.New(cfg, logger)
if err != nil {
logger.Fatal("failed to initialize application", zap.Error(err))
}

go func() {
application.Run()
err = application.Run()
if err != nil {
logger.Error("failed to run application", zap.Error(err))
return
}
}()

stop := make(chan os.Signal, 1)
Expand All @@ -41,5 +48,9 @@ func main() {

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
application.Stop(ctx)
err = application.Stop(ctx)
if err != nil {
logger.Error("failed to graceful stop", zap.Error(err))
return
}
}
17 changes: 9 additions & 8 deletions backend/cmd/migrator/migrator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package migrator

import (
"database/sql"
"fmt"
"log"
"os"

Expand All @@ -13,7 +14,7 @@ import (
// "github.com/stepan41k/billing-service/internal/config"
)

func Migrate() {
func Migrate() error {
cfgFireBird := config.MustLoadMigration()
migrationPath := os.Getenv("MIGRATIONS_PATH")
if migrationPath == "" {
Expand All @@ -25,13 +26,13 @@ func Migrate() {

db, err := sql.Open("firebirdsql", cfgFireBird.DSN())
if err != nil {
log.Fatalf("ERROR: Не удалось открыть базу: %v", err)
return fmt.Errorf("ERROR: Не удалось открыть базу: %v", err)
}
defer db.Close()

driver, err := firebird.WithInstance(db, &firebird.Config{})
if err != nil {
log.Fatalf("ERROR: Ошибка создания драйвера мигратора: %v", err)
return fmt.Errorf("ERROR: Ошибка создания драйвера мигратора: %v", err)
}

m, err := migrate.NewWithDatabaseInstance(
Expand All @@ -40,16 +41,16 @@ func Migrate() {
driver,
)
if err != nil {
log.Fatalf("ERROR: Ошибка инициализации мигратора: %v", err)
return fmt.Errorf("ERROR: Ошибка инициализации мигратора: %v", err)
}

if err := m.Up(); err != nil && err != migrate.ErrNoChange {
log.Fatalf("ERROR: Ошибка при выполнении миграции: %v", err)
if err = m.Up(); err != nil && err != migrate.ErrNoChange {
return fmt.Errorf("ERROR: Ошибка при выполнении миграции: %v", err)
}

if err == migrate.ErrNoChange {
log.Println("INFO: Миграции не требуются (база актуальна)")
return fmt.Errorf("INFO: Миграции не требуются (база актуальна)")
} else {
log.Println("INFO: Миграции успешно применены!")
return fmt.Errorf("INFO: Миграции успешно применены!")
}
}
Loading
Loading