Volti Docs
Ana Sayfa

Genel

Platform Hakkında

Volti, elektrikli araç şarj istasyonlarının keşfi, şarj süreçlerinin yönetimi ve altyapı denetimi için geliştirilmiş kapsamlı bir yazılım platformudur. Üç bileşenden oluşur:

Mobil Uygulama (Flutter)

Kullanıcıların yakınlarındaki şarj istasyonlarını harita üzerinde bulmasını, QR kod ile şarj başlatmasını, şarj sürecini canlı takip etmesini, araç ve ödeme yöntemlerini yönetmesini sağlar. iOS ve Android için tek kod tabanından derlenir.

Backend API (.NET 8)

Mobil uygulamanın tüm verilerini sağlayan RESTful servis katmanıdır. Kullanıcı kimlik doğrulama, istasyon yönetimi, şarj oturumu kontrolü, araç/ödeme CRUD işlemleri ve kampanya yönetimi endpoint'lerini barındırır.

Yönetim Paneli (Razor Pages)

Operatörlerin istasyonları, kullanıcıları, şarj oturumlarını ve kampanyaları web üzerinden yönetmesini sağlayan server-side rendered admin arayüzüdür. Bootstrap kullanılmadan tamamen özel CSS ile tasarlanmıştır.

Tüm bileşenler Mehmet Doğukan Sevinç tarafından tasarlanmış ve geliştirilmiştir.

Flutter 3.x BLoC .NET 8 MySQL JWT Clean Architecture

Kurulum Rehberi

Sistem iki bağımsız projeden oluşur. Backend önce başlatılmalıdır çünkü mobil uygulama tüm verileri API üzerinden çeker — mock data bulunmamaktadır.

1. Backend Servisini Başlatma

Backend başlatıldığında EF Core otomatik olarak MySQL veritabanını oluşturur (Code First). İlk çalıştırmada seed data yüklenir: 6 istasyon, 17 Elektrikli araç markası ve modelleri, connector'lar ve bir admin hesabı.

Terminal
cd VoltiAdmin
dotnet run

Başarılı çalıştırmada aşağıdaki adresler erişilebilir olur:

ServisAdres
Tanıtım Sayfası/
REST API/api/v1/*
Admin Paneli/admin/login
Döküman/docs

2. Mobil Uygulamayı Başlatma

API bağlantı adresi lib/core/constants/api_constants.dart dosyasında tanımlıdır. Geliştirme ortamında bu adres backend'in çalıştığı port'a yönlendirilmelidir.

Terminal
cd volti
flutter pub get
flutter run

3. Veritabanı Yapılandırması

MySQL kullanılmaktadır. Bağlantı bilgileri appsettings.json dosyasında yapılandırılır. EF Core Pomelo MySQL provider (v8.0.2) kullanır. Veritabanı tabloları ve seed data dotnet run sırasında EnsureCreated() ile otomatik oluşturulur — migration çalıştırmaya gerek yoktur.

Önemli: Mobil uygulama hiçbir mock veri içermez. Backend çalışmadan uygulama boş ekran gösterir. Bu bilinçli bir tasarım kararıdır — gerçek API entegrasyonunu göstermek amacıyla tercih edilmiştir.

Proje Yapısı

Flutter — Feature-Based Modüler Yapı

Her feature kendi data/, domain/ ve presentation/ katmanlarına sahiptir. Feature'lar birbirinden bağımsızdır — auth modülü map modülünü bilmez, map modülü payment modülünü bilmez. Bu sayede bir modüldeki değişiklik diğerlerini etkilemez.

lib/
lib/
├── core/
│   ├── constants/          # AppColors, ApiConstants, AppSizes, AppStrings
│   ├── di/                 # injection_container.dart — tüm DI kaydı
│   ├── error/              # ServerException, CacheException, Failure sınıfları
│   ├── network/            # DioClient, TokenInterceptor, ApiResponse wrapper
│   ├── routes/             # GoRouter tanımları, auth guard
│   ├── theme/              # ThemeData yapılandırması
│   ├── utils/              # ChargingFlow, TokenStorage, UseCase abstract
│   └── widgets/            # VoltiButton, VoltiTextField — ortak bileşenler
├── features/
│   ├── auth/               # 6 use case: login, register, verify-otp, logout...
│   ├── map/                # Harita, arama, filtre, station card, chips bar
│   ├── stations/           # İstasyon detay, QR tarama, connector card
│   ├── charging/           # Aktif şarj ekranı, Tesla bobini animasyonu
│   ├── vehicles/           # Araç ekleme, 17+ marka/model API'den çekilir
│   ├── payment/            # Ödeme yöntemi, EMV chip animasyonu
│   ├── profile/            # Profil sayfası, guest view, nasıl şarj edilir
│   ├── favorites/          # Favori istasyonlar, swipe-to-delete
│   ├── history/            # Şarj geçmişi, tarihe göre gruplandırma
│   ├── home/               # HomeShell — tab bar (4 tab + merkez QR butonu)
│   └── splash/             # Elektrik bolt animasyonu ile açılış ekranı
└── main.dart               # GetIt init, Hive init, runApp

Her Feature'ın İç Yapısı

Aşağıda map feature'ı örnek olarak gösterilmiştir. Diğer tüm feature'lar aynı yapıyı takip eder:

features/map/
map/
├── data/
│   ├── datasources/
│   │   └── station_remote_datasource.dart  # Dio ile API çağrısı
│   ├── models/
│   │   └── station_model.dart              # fromJson/toJson + entity'ye dönüşüm
│   └── repositories/
│       └── station_repository_impl.dart    # try-catch → Either<Failure, T>
├── domain/
│   ├── entities/
│   │   └── station_entity.dart             # Saf Dart sınıfı, bağımlılık yok
│   ├── repositories/
│   │   └── station_repository.dart         # Abstract interface
│   └── usecases/
│       └── get_nearby_stations_usecase.dart # Tek sorumluluk prensibi
└── presentation/
    ├── bloc/
    │   ├── map_bloc.dart                   # Event handler'lar + export
    │   ├── map_event.dart                  # LoadStations, Search, Filter...
    │   └── map_state.dart                  # stations, favorites, isLoading...
    ├── pages/
    │   ├── map_page.dart                   # flutter_map + kart scroll
    │   ├── search_page.dart                # Highlight'lı arama sonuçları
    │   └── filter_page.dart                # Durum + konnektör tipi filtresi
    └── widgets/
        ├── station_card.dart               # Tekrar kullanılır istasyon kartı
        └── filter_chips_bar.dart           # Gradient renkli filtre chip'leri

Backend Yapısı

VoltiAdmin/
VoltiAdmin/
├── Controllers/Api/v1/     # 8 REST controller (Auth, Stations, Charging...)
├── Entities/               # 14 veritabanı modeli
│   └── Enums/              # StationStatus, ConnectorType, ChargeType, ChargingStatus
├── DTOs/                   # 19 request/response modeli (Auth, Station, Vehicle...)
├── Services/               # 5 servis — IAuthService/AuthService vb.
├── Data/
│   ├── VoltiDbContext.cs   # EF Core DbContext, ilişki tanımları
│   └── DbSeeder.cs         # İstasyon, marka, model, admin seed data
├── Middleware/
│   └── JwtMiddleware.cs    # Authorization header'dan userId çözümleme
├── Pages/                  # Razor Pages — admin panel sayfaları
│   └── Shared/_Layout.cshtml # Sidebar + header layout
├── Program.cs              # DI, auth, CORS, middleware, pipeline
└── wwwroot/                # landing.css, docs.css, landing.js, docs.html

Mimari

Clean Architecture

Robert C. Martin'in Clean Architecture prensipleri uygulanmıştır. Temel kural: iç katmanlar dış katmanları bilmez. Domain katmanı ne Flutter'ı ne Dio'yu ne Hive'ı bilir.

Presentation Katmanı

Kullanıcının gördüğü her şey burada: sayfalar, widget'lar ve BLoC. Bu katman sadece BLoC üzerinden domain'e erişir. API'nin varlığından haberi yoktur — sadece "state" okur ve "event" gönderir.

İçerir: BLoC (bloc.dart + event.dart + state.dart), Page widget'ları, ortak Widget bileşenleri

Domain Katmanı

İş kuralları burada yaşar. Hiçbir dış pakete bağımlı değildir — pure Dart. Bu katmanı alıp başka bir framework'e taşısanız çalışmaya devam eder.

İçerir: Entity sınıfları (saf veri modelleri), Repository interface'leri (abstract sınıflar), Use Case'ler (tek iş yapan fonksiyonlar), Failure sınıfları

Örnek: UseCase abstract sınıfı
// Her use case bu sınıfı extend eder.
// T: dönüş tipi, Params: parametre tipi

abstract class UseCase<T, Params> {
  Future<Either<Failure, T>> call(Params params);
}

// Parametresiz use case'ler için:
class NoParams {
  const NoParams();
}
Örnek: GetNearbyStationsUseCase
class GetNearbyStationsUseCase extends UseCase<List<StationEntity>, NoParams> {
  final StationRepository repository;

  GetNearbyStationsUseCase(this.repository);

  @override
  Future<Either<Failure, List<StationEntity>>> call(NoParams params) {
    return repository.getNearbyStations();
  }
}

Data Katmanı

API çağrıları, JSON parse, local cache ve repository implementasyonları bu katmanda gerçekleşir. Dio HTTP client ve Hive local storage bu katmanda kullanılır. Domain katmanının tanımladığı interface'leri implemente eder.

Akış: DataSource (Dio ile API çağır) → Model (JSON → Dart objesi) → Repository (try-catch → Either) → Domain'e teslim et

Örnek: Repository implementasyonu
class StationRepositoryImpl implements StationRepository {
  final StationRemoteDataSource remote;

  Future<Either<Failure, List<StationEntity>>> getNearbyStations() async {
    try {
      final models = await remote.getStations();
      return Right(models);  // başarılı → sağ taraf
    } on ServerException catch (e) {
      return Left(ServerFailure(e.message));  // hata → sol taraf
    }
  }
}

BLoC Pattern

State management için BLoC (Business Logic Component) kullanılır. Cubit kullanılmaz — Event/State ayrımı debug kolaylığı ve event geçmişi takibi sağlar.

Neden 3 Ayrı Dosya?

Her BLoC modülü _bloc.dart, _event.dart ve _state.dart olmak üzere üç dosyadan oluşur. Tek dosyada tutmak 200+ satırda okunmaz hale gelir. Ayrıca bloc.dart içinde export statement'ları ile geriye dönük uyumluluk sağlanır.

Event Sınıfları

Her kullanıcı eylemi bir event'tir: sayfa açıldı, arama yapıldı, favori eklendi, filtre değişti.

map_event.dart
abstract class MapEvent {}

class MapLoadStations extends MapEvent {}

class MapSearchChanged extends MapEvent {
  final String query;
  MapSearchChanged({required this.query});
}

class MapToggleFavorite extends MapEvent {
  final String stationId;
  MapToggleFavorite({required this.stationId});
}

class MapFilterChanged extends MapEvent {
  final String? statusFilter;
  final String? chargeTypeFilter;
  MapFilterChanged({this.statusFilter, this.chargeTypeFilter});
}

BLoC Sınıfı

map_bloc.dart
export 'map_event.dart';
export 'map_state.dart';

class MapBloc extends Bloc<MapEvent, MapState> {
  final GetNearbyStationsUseCase getStationsUseCase;

  MapBloc({required this.getStationsUseCase})
    : super(const MapState()) {
    on<MapLoadStations>(_onLoadStations);
    on<MapSearchChanged>(_onSearch);
    on<MapToggleFavorite>(_onToggleFavorite);
    on<MapFilterChanged>(_onFilter);
  }

  Future<void> _onLoadStations(event, emit) async {
    if (state.stations.isNotEmpty) return; // tab switch'te yeniden yükleme
    emit(state.copyWith(isLoading: true));
    final result = await getStationsUseCase(const NoParams());
    result.fold(
      (f) => emit(state.copyWith(isLoading: false, error: () => f.message)),
      (s) => emit(state.copyWith(isLoading: false, stations: s)),
    );
  }
}

Projede Bulunan BLoC Modülleri

BLoCFeatureÖnemli Event'ler
AuthBlocauthLogin, Register, VerifyOtp, Logout, GetCurrentUser
MapBlocmapLoadStations, Search, ToggleFavorite, Filter
StationDetailBlocstationsLoadDetail, LoadConnectors
ChargingBlocchargingGetActiveSession, StopCharging
VehicleBlocvehiclesLoadBrands, LoadModels, AddVehicle, BrandSearch
PaymentBlocpaymentLoadMethods, AddMethod
ProfileBlocprofileLoadProfile
FavoritesBlocfavoritesLoadFavorites
HistoryBlochistoryLoadHistory

Dependency Injection

get_it paketi ile service locator pattern uygulanır. Tüm bağımlılıklar main.dart'ta uygulama başlamadan önce kaydedilir. Kayıt sırası önemlidir — en alt katmandan (network) başlayıp en üst katmana (BLoC) çıkılır.

injection_container.dart — kısaltılmış
final sl = GetIt.instance;

void initDependencies() {
  // ── Network ──
  sl.registerLazySingleton(() => DioClient());

  // ── Data Sources ──
  sl.registerLazySingleton(() => StationRemoteDataSource(sl()));
  sl.registerLazySingleton(() => AuthRemoteDataSource(sl()));
  // ... diğer data source'lar

  // ── Repositories ──
  sl.registerLazySingleton<StationRepository>(
    () => StationRepositoryImpl(remote: sl()),
  );

  // ── Use Cases ──
  sl.registerLazySingleton(() => GetNearbyStationsUseCase(sl()));

  // ── BLoCs (factory — her seferinde yeni instance) ──
  sl.registerFactory(() => MapBloc(getStationsUseCase: sl()));
}

registerLazySingleton tek instance oluşturur ve her çağrıda aynısını döndürür — DioClient, DataSource, Repository gibi paylaşılan nesneler için. registerFactory her çağrıda yeni instance üretir — BLoC'lar factory olmalıdır çünkü her sayfa açıldığında temiz state ile başlamalıdır.

Hata Yönetimi

dartz paketinin Either<L, R> tipi kullanılır. Sol taraf (Left) hata, sağ taraf (Right) başarıdır. Bu yaklaşımın avantajı: hata kontrolünü atlamak yapısal olarak imkansızdırfold yapmadan veriye erişemezsiniz.

Failure Hiyerarşisi

core/error/failures.dart
abstract class Failure {
  final String message;
  const Failure(this.message);
}

class ServerFailure extends Failure { ... }  // API hataları
class CacheFailure extends Failure { ... }   // Hive hataları

BLoC'ta Kullanımı

BLoC handler içinde
final result = await useCase(params);
result.fold(
  (failure) => emit(state.copyWith(error: () => failure.message)),
  (data)    => emit(state.copyWith(stations: data)),
);

Routing & Guard

go_router paketi ile declarative routing kullanılır. Auth guard şu mantıkla çalışır: gezinme serbesttir, şarj için giriş gerekir. Kullanıcı haritada dolaşabilir, istasyonlara bakabilir — sadece şarj başlatmak istediğinde ChargingFlow giriş kontrolü yapar.

Giriş yapmış kullanıcı /login sayfasına gitmeye çalışırsa otomatik olarak /home'a yönlendirilir.

Token Yönetimi

JWT token'lar Hive local storage'da saklanır. TokenStorage sınıfı okuma/yazma işlemlerini yönetir. TokenInterceptor ise Dio'nun interceptor mekanizması ile her HTTP isteğine otomatik olarak token ekler.

Token Refresh Akışı

Access token süresi dolduğunda (401 yanıtı) interceptor otomatik olarak refresh token ile yenileme dener. Başarılı olursa orijinal istek tekrar gönderilir. Başarısız olursa kullanıcı logout edilir ve token'lar Hive'dan silinir.

API

Genel Yapı

Tüm endpoint'ler /api/v1/ prefix'i altındadır. Korumalı endpoint'ler Authorization: Bearer {token} header'ı gerektirir. Tüm yanıtlar aynı format ile döner:

Standart API yanıt formatı
{
  "success": true,
  "data": { /* asıl veri */ },
  "error": null
}

// Hata durumunda:
{
  "success": false,
  "data": null,
  "error": { "message": "Hata açıklaması" }
}

Flutter tarafında bu format ApiResponse<T> wrapper sınıfı ile parse edilir.

Kimlik Doğrulama

JWT tabanlı auth sistemi. Telefon + şifre ile giriş yapılır, access token (1 saat) + refresh token döner. OTP doğrulama 2 dakika geçerlidir.

MetodEndpointAçıklamaAuth
POST/auth/loginTelefon + şifre ile giriş, JWT token döner-
POST/auth/registerYeni hesap oluşturma, OTP gönderir-
POST/auth/verify-otp6 haneli SMS kodunu doğrular (2dk süre)-
POST/auth/refreshRefresh token ile yeni access token al-
GET/auth/meMevcut kullanıcı bilgisini dönerGerekli
POST/auth/logoutRefresh token'ı geçersiz kılarGerekli

Login İstek/Yanıt Örneği

POST /api/v1/auth/login
// İstek:
{ "phone": "+905551234567", "password": "******" }

// Yanıt:
{
  "success": true,
  "data": {
    "accessToken": "eyJhbGciOiJIUzI1NiIs...",
    "refreshToken": "d4f8a2b1-...",
    "expiresIn": 3600
  }
}

İstasyonlar

İstasyon endpoint'leri genel erişime açıktır — giriş yapmadan kullanılabilir. Çünkü uygulama tasarım gereği giriş olmadan da istasyonları gösterir.

MetodEndpointAçıklama
GET/stationsTüm istasyonları listeler (çevrimdışı dahil)
GET/stations/{id}Tek istasyonun detay bilgisi + konnektörleri
GET/stations/nearby?lat=&lng=Koordinata göre en yakın istasyonlar
GET/stations/{id}/connectorsİstasyondaki konnektör listesi (tip, güç, fiyat, durum)

Flutter'da Kullanım

station_remote_datasource.dart
class StationRemoteDataSource {
  final DioClient client;

  Future<List<StationModel>> getStations() async {
    final response = await client.dio.get('/stations');
    final list = response.data['data'] as List;
    return list.map((e) => StationModel.fromJson(e)).toList();
  }
}

// StationModel.fromJson içinde status parsing:
// API "Available" (büyük A) döner → toLowerCase() ile normalize edilir

Şarj İşlemleri

Tüm şarj endpoint'leri JWT korumalıdır. Şarj başlatmak için kullanıcının en az bir kayıtlı aracı ve ödeme yöntemi olmalıdır — bu kontrol Flutter tarafında ChargingFlow sınıfı tarafından yapılır.

MetodEndpointAçıklama
POST/charging/startconnectorId + vehicleId + paymentMethodId ile şarj başlat
POST/charging/stopAktif oturumu durdur, enerji ve maliyet hesapla
GET/charging/activeKullanıcının aktif şarj oturumu varsa döner
GET/charging/historyTamamlanmış şarj oturumları listesi

Araç Servisleri

Marka ve model endpoint'leri herkese açıktır. Araç CRUD işlemleri JWT gerektirir. Seed data'da 17 Elektrikli araç markası (Tesla, BYD, BMW, Mercedes, Audi, Porsche, Volvo, Hyundai, Kia, VW, Nissan, Renault, Peugeot, Fiat, MG, Cupra, TOGG) ve modelleri bulunur.

MetodEndpointAçıklama
GET/vehicles/brandsTüm Elektrikli araç markaları (id, name, logoUrl)
GET/vehicles/brands/{id}/modelsSeçilen markanın modelleri
GET/vehiclesKullanıcının kayıtlı araçları
POST/vehiclesbrandId, modelId, licensePlate, year ile araç ekle

Ödeme, Profil, Favoriler

MetodEndpointAçıklama
GET/payment/methodsKullanıcının kayıtlı ödeme kartları
POST/payment/methodsYeni kart ekle (cardNumber, holderName, expiry, cvv)
GET/profileKullanıcı profil bilgileri
PUT/profileProfil güncelleme
GET/favoritesFavori istasyon listesi
POST/favorites/togglestationId ile favori ekle/çıkar (toggle)
GET/campaignsAktif kampanyalar (title, discount, tarih aralığı)

Flutter

Network Katmanı

HTTP iletişimi Dio istemcisi üzerinden gerçekleşir. core/network/ klasöründe üç dosya bulunur:

DioClient

Base URL, timeout (30sn), content-type header ve TokenInterceptor burada yapılandırılır. Singleton olarak GetIt'e kaydedilir.

TokenInterceptor

Her outgoing request'e otomatik Bearer token ekler. 401 yanıtında refresh dener, başarısızsa logout yapar.

TokenInterceptor — temel mantık
// İstek öncesi:
void onRequest(options, handler) {
  final token = TokenStorage.getAccessToken();
  if (token != null)
    options.headers['Authorization'] = 'Bearer $token';
  handler.next(options);
}

// 401 hatası gelirse:
void onError(err, handler) async {
  if (err.response?.statusCode == 401) {
    final refreshed = await _tryRefresh();
    if (refreshed) { /* orijinal isteği tekrar gönder */ }
    else { /* logout + token'ları sil */ }
  }
}

ApiResponse

API'nin standart {success, data, error} formatını parse eden generic wrapper. DataSource'lar bu sınıfı kullanarak response'u ayrıştırır.

Giriş Akışı

Uygulama giriş yapmadan kullanılabilir şekilde tasarlanmıştır. Kullanıcı haritada gezinebilir, istasyonları inceleyebilir, filtreleyebilir. Giriş yalnızca şarj başlatma anında zorunlu hale gelir.

1
Uygulama açılırSplash ekranında Hive'dan token kontrol edilir
2
Ana ekran yüklenirToken olsun olmasın harita ve istasyonlar gösterilir
3
"Şarjı Başlat" butonuna basılırChargingFlow sınıfı devreye girer
4
Ön koşul kontrolüSırasıyla: giriş → araç → ödeme yöntemi kontrol edilir

Şarj Başlatma Akışı

ChargingFlow sınıfı (core/utils/charging_flow.dart) şarj öncesi tüm ön koşulları sıralı kontrol eder. Eksik olan koşul tespit edildiğinde kullanıcı ilgili sayfaya yönlendirilir. Kullanıcı eksik adımı tamamlayıp geri döndüğünde kaldığı yerden devam edebilir.

ChargingFlow kontrol sırası
1. Oturum kontrolü     Giriş yapılmamışsa  Login sayfasına yönlendir
2. Araç kontrolü       Kayıtlı araç yoksa  Araç ekleme sayfasına yönlendir
3. Ödeme kontrolü      Kayıtlı kart yoksa  Ödeme yöntemi sayfasına yönlendir
4. Şarj başlatma       API'ye POST /charging/start isteği gönder
5. Yönlendirme         Aktif şarj takip sayfasına geçiş

Harita Entegrasyonu

flutter_map paketi ile OpenStreetMap tabanlı harita kullanılır (Google Maps API key gerektirmez). İstasyonlar custom marker olarak gösterilir.

Zoom Animasyonu

Bir istasyon kartına tıklandığında harita önce zoom-out yapar, sonra yeni konuma zoom-in yapar. AnimationController ile MapController senkronize edilir. Birden fazla animasyonun çakışmasını engellemek için _animId mekanizması kullanılır — her yeni animasyon önceki animasyonu iptal eder.

Arama

Arama sayfasında kullanıcı istasyon adı veya adresine göre arama yapar. Sonuçlarda eşleşen kısım highlight edilir. Bir sonuca tıklandığında haritada o istasyona animasyonlu geçiş yapılır.

Filtreleme

İstasyon durumu (Müsait, Dolu, Çevrimdışı, Arızalı) ve konnektör tipi (AC Type2, DC CCS, DC CHAdeMO) filtreleri vardır. Filtre sayfası static değişkenler kullanır — tab switch'te seçimler korunur.

Arayüz & Animasyonlar

Uygulamada CustomPaint ile oluşturulmuş özel animasyonlar kullanılır. Tüm görseller kod ile üretilir — harici asset dosyası (PNG, SVG) yoktur. Bu sayede asset boyutu sıfırdır ve her şey dinamik olarak parametre ile kontrol edilebilir.

BileşenDetayTeknoloji
Splash EkranıYukarıdan aşağı kırılarak inen yıldırım bolt animasyonuCustomPaint
Şarj EkranıTesla bobini (titreşim + ark), şehir silüeti (binalar, ağaçlar, yol, bulutlar, uçak)CustomPaint
Ödeme KartıGerçekçi kredi kartı yüzü, EMV chip altın rengi detaylarCustomPaint
Harita GeçişiZoom-out → zoom-in kamera animasyonu, çakışma engellemeAnimationController
QR TarayıcıKamera üzerinde yukarı-aşağı hareket eden scan lineCustomPaint
Filtre Chip'leriHer filtre tipi farklı gradient renk (teal, amber, rose, indigo)Widget
Onboarding"Nasıl şarj edilir" adım adım rehberLottie
Nav Bar4 tab (her biri farklı renk) + merkez QR butonu (navy gradient)Widget

Backend

Veritabanı Şeması

Entity Framework Core Code First yaklaşımı ile yönetilen 14 tablo ve 4 enum tipi. Pomelo MySQL provider (v8.0.2) kullanılır.

TabloAçıklamaÖnemli Alanlarİlişkiler
UserKullanıcı hesabıPhone, Name, Surname, Email, PasswordHash, IsVerifiedVehicle, PaymentMethod, Favorite
StationŞarj istasyonuName, Address, Lat, Lng, Status, Rating, ImageUrl, IsActiveConnector, Favorite
ConnectorKonnektörType (CCS/CHAdeMO/Type2), ChargeType (AC/DC), PowerKw, PricePerKwhStation, ChargingSession
ChargingSessionŞarj oturumuStartedAt, EndedAt, EnergyKwh, CostTl, DurationSeconds, StatusUser, Connector
VehicleKullanıcı aracıLicensePlate, YearUser, VehicleBrand, VehicleModel
VehicleBrandAraç markasıName, LogoUrlVehicleModel
VehicleModelAraç modeliNameVehicleBrand
PaymentMethodÖdeme kartıLast4, CardHolderName, ExpiryDate, IsPrimaryUser
FavoriteFavori istasyon-User, Station
OtpCodeSMS doğrulamaCode, ExpiresAt (2dk), IsUsedUser
RefreshTokenToken yenilemeToken, ExpiresAt, IsRevokedUser
CampaignKampanyaTitle, Description, DiscountPercent, StartDate, EndDate, IsActive-
AdminUserYönetici (ayrı tablo)Email, PasswordHash-
BillingInfoFatura adresiCompanyName, TaxNumber, AddressUser

Enum Tipleri

EnumDeğerlerKullanıldığı Yer
StationStatusAvailable, Occupied, Offline, FaultStation.Status
ConnectorTypeType2, CCS, CHAdeMO, TeslaConnector.Type
ChargeTypeAC, DCConnector.ChargeType
ChargingStatusActive, Completed, Stopped, FailedChargingSession.Status

Servis Katmanı

İş mantığı Controller'lardan ayrılmıştır. Her servis bir interface ve bir implementasyondan oluşur. Servisler Program.cs'te DI container'a AddScoped ile kaydedilir.

ServisSorumlulukÖnemli Metotlar
AuthServiceLogin, register, OTP, JWT üretimi, token refreshLogin(), Register(), VerifyOtp(), RefreshToken()
StationServiceİstasyon CRUD, yakındaki istasyonlar hesaplamaGetAll(), GetById(), GetNearby(), Create(), Update()
ChargingServiceŞarj başlat/durdur, geçmiş, aktif oturumStart(), Stop(), GetActive(), GetHistory()
VehicleServiceMarka/model listeleme, araç CRUDGetBrands(), GetModels(), AddVehicle()
PaymentServiceÖdeme yöntemi CRUDGetMethods(), AddMethod()
IyzicoPaymentServiceiyzico POS entegrasyonu, şarj sonrası otomatik tahsilatCreatePayment()

Yönetim Paneli

Razor Pages ile server-side render. Mobil API'nin JWT auth'undan ayrı olarak Cookie authentication kullanır (AdminUser tablosu). Tüm CSS sıfırdan yazılmıştır — Bootstrap veya herhangi bir CSS framework kullanılmamıştır.

SayfaAdresİşlevler
Dashboard/admin/dashboardToplam istasyon, aktif şarj, günlük gelir, kullanıcı sayısı, son oturumlar
İstasyonlar/admin/stationsListeleme, ekleme, düzenleme, silme (CRUD)
Kullanıcılar/admin/usersKullanıcı listesi, doğrulama durumu, kayıt tarihi
Şarj Oturumları/admin/chargingAktif/tamamlanan oturumlar, enerji, maliyet, süre
Araçlar/admin/vehicles17+ marka ve modelleri, model sayıları
Kampanyalar/admin/campaignsKampanya oluşturma, aktif/pasif durumu
Bildirimler/admin/notificationsBildirim gönderme (tek/tüm kullanıcı), tip seçimi, son bildirimler

Seed Data

DbSeeder.cs ilk çalıştırmada aşağıdaki verileri oluşturur:

VeriMiktarDetay
Admin hesabı1Yönetim paneli erişimi için
Şarj istasyonu6Gerçekçi koordinatlar, farklı durumlar
Konnektör~15Her istasyonda 2-3 adet (AC Type2, DC CCS, DC CHAdeMO)
Elektrikli araç markası17Tesla, BYD, BMW, Mercedes, Audi, Porsche, Volvo, Hyundai, Kia, VW, Nissan, Renault, Peugeot, Fiat, MG, Cupra, TOGG
Araç modeli~40Her markaya ait popüler EV modelleri

Entegrasyonlar

iyzico Ödeme Entegrasyonu

Şarj oturumu tamamlandığında kullanıcının kayıtlı kartından otomatik ödeme çekilir. iyzico Türkiye'nin en yaygın online ödeme altyapısıdır — PCI DSS uyumlu, 3D Secure destekli sanal pos hizmeti sunar.

Sandbox Yapılandırması

ParametreDeğer
OrtamSandbox (Test)
Base URLhttps://sandbox-api.iyzipay.com
Test Kart No5528790000000008
SKT12/2030
CVV123

Ödeme Akışı

Ödeme ChargingService.StopChargingAsync() içinde tetiklenir:

Akış
1. Kullanıcı "Şarjı Bitir" butonuna basar
2. Enerji, maliyet ve süre hesaplanır
3. Kullanıcının kayıtlı kartı PaymentMethods tablosundan çekilir
4. iyzico sandbox API'ye ödeme isteği gönderilir
5. Payment response: status + paymentId
6. ChargingSession'a paymentStatus kaydedilir
7. "Şarj Tamamlandı" bildirimi oluşturulur

CreatePaymentAsync Parametreleri

ParametreTipAçıklama
userIdstringBuyer kimliği
userEmailstringBuyer e-posta
userNamestringBuyer ad soyad
cardNumberstringKart numarası (sandbox: test kart)
amountdecimalTutar (TL)
descriptionstring"Volti Şarj - {istasyon adı}"

Production'a Geçiş: Gerçek iyzico merchant hesabı açılmalı, API key/secret güncellenmeli, BaseUrl https://api.iyzipay.com yapılmalı, 3D Secure zorunlu hale getirilmelidir.

Bildirim Sistemi

Bildirimler veritabanında Notifications tablosunda saklanır. Otomatik ve manuel olmak üzere iki kaynaktan oluşturulur.

Otomatik Bildirimler

ChargingService şarj başlatma ve durdurma sırasında otomatik bildirim oluşturur:

TetikleyiciTipBaşlık
Şarj başlatıldıcharging_startedŞarj Başladı
Şarj durdurulducharging_completeŞarj Tamamlandı
İlk bildirim açılışısystem, campaign, station_available, price_changeHoşgeldin bildirimleri (auto-seed)

Admin Panel ile Manuel Gönderim

/admin/notifications sayfasından tüm kullanıcılara veya tek kullanıcıya bildirim gönderilebilir. 4 tip desteklenir: Sistem, Kampanya, İstasyon, Fiyat.

API Endpoint'leri

MethodEndpointAçıklama
GET/api/v1/notificationsKullanıcının bildirimleri (ilk açılışta auto-seed)
PUT/api/v1/notifications/{id}/readTek bildirim okundu işaretle
PUT/api/v1/notifications/read-allTümünü okundu yap

Bildirim Tipleri

TipAçıklamaRenk
charging_completeŞarj tamamlandıYeşil
charging_startedŞarj başladıTeal
campaignKampanya/promosyonPembe
station_availableİstasyon müsait olduMavi
price_changeFiyat değişikliğiAmber
systemSistem bildirimiİndigo

Favori Sistemi

Favoriler veritabanında Favorites tablosunda saklanır (UserId + StationId composite unique). MapBloc üzerinden yönetilir.

Optimistic Update Akışı

MapBloc._onToggleFavorite
1. UI anında güncellenir (favoriteIds Set'e ekle/çıkar)
2. API'ye POST /favorites veya DELETE /favorites/{stationId} gönderilir
3. API başarısız olursa UI geri alınır (rollback)
4. Profil sayfası MapBloc'u dinler — favori sayısı değişince stats güncellenir

API Endpoint'leri

MethodEndpointAçıklama
GET/api/v1/favoritesFavori istasyonlar (station detaylarıyla)
POST/api/v1/favoritesFavoriye ekle (body: stationId)
DELETE/api/v1/favorites/{id}Favoriden çıkar (stationId veya favoriteId)

DELETE endpoint hem Favorite.Id hem StationId ile çalışır — Flutter stationId gönderir, backend her iki durumu da arar.

Profil Sayfaları

Profil modülü 8 alt sayfadan oluşur. Tüm sayfalar premium navy gradient header ve Jost tipografi ile tutarlı tasarıma sahiptir.

SayfaRouteİşlev
Profil Düzenle/profile/editAd, soyad, telefon, e-posta güncelleme
Fatura Bilgileri/profile/billingBireysel/kurumsal toggle, TC/vergi no validation, adres
Bildirimler/profile/notificationsAPI'den gelen bildirimler, okundu işaretleme, tümünü oku
Araçlarım/profile/vehiclesKayıtlı araç listesi + araç ekleme
Ödeme Yöntemleri/profile/paymentKayıtlı kartlar, kart ekleme, birincil kart
Şarj Geçmişi/profile/historyTarihe göre gruplanmış oturumlar, detay bottom sheet
Yardım & Destek/profile/helpFAQ accordion, iletişim kartları
Gizlilik Politikası/profile/privacyKVKK uyumlu 7 bölüm
Hakkında/profile/aboutUygulama bilgisi, teknolojiler, geliştirici
Nasıl Şarj Edilir/profile/how-to-charge4 adımlı onboarding (Lottie animasyonları)

Profil İstatistikleri

Stats verisi ProfileRemoteDatasource.getProfileStats() ile hesaplanır — /charging/history ve /favorites endpoint'lerinden toplam şarj sayısı, toplam enerji (kWh) ve favori sayısı çekilir. MapBloc'taki favori değişiklikleri dinlenerek anlık güncellenir.

Fatura Bilgileri Validation

AlanBireyselKurumsal
TC / Vergi No11 hane zorunlu10 hane zorunlu
Firma AdıZorunlu
Vergi DairesiZorunlu
AdresZorunluZorunlu
ŞehirZorunluZorunlu

Hakkında

Geliştirici

Bu platform — mobil uygulama, backend API, veritabanı tasarımı, yönetim paneli, tanıtım web sayfası ve bu döküman dahil tüm bileşenler — Mehmet Doğukan Sevinç tarafından sıfırdan tasarlanmış ve geliştirilmiştir.

FlutterDartBLoC .NET 8C#MySQL
Volti Documentation Mehmet Doğukan Sevinç