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.
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ı.
cd VoltiAdmin dotnet run
Başarılı çalıştırmada aşağıdaki adresler erişilebilir olur:
| Servis | Adres |
|---|---|
| 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.
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/ ├── 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:
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/ ├── 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ı
// 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(); }
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
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.
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ı
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
| BLoC | Feature | Önemli Event'ler |
|---|---|---|
| AuthBloc | auth | Login, Register, VerifyOtp, Logout, GetCurrentUser |
| MapBloc | map | LoadStations, Search, ToggleFavorite, Filter |
| StationDetailBloc | stations | LoadDetail, LoadConnectors |
| ChargingBloc | charging | GetActiveSession, StopCharging |
| VehicleBloc | vehicles | LoadBrands, LoadModels, AddVehicle, BrandSearch |
| PaymentBloc | payment | LoadMethods, AddMethod |
| ProfileBloc | profile | LoadProfile |
| FavoritesBloc | favorites | LoadFavorites |
| HistoryBloc | history | LoadHistory |
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.
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ır — fold yapmadan veriye erişemezsiniz.
Failure Hiyerarşisi
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ı
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:
{
"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.
| Metod | Endpoint | Açıklama | Auth |
|---|---|---|---|
| POST | /auth/login | Telefon + şifre ile giriş, JWT token döner | - |
| POST | /auth/register | Yeni hesap oluşturma, OTP gönderir | - |
| POST | /auth/verify-otp | 6 haneli SMS kodunu doğrular (2dk süre) | - |
| POST | /auth/refresh | Refresh token ile yeni access token al | - |
| GET | /auth/me | Mevcut kullanıcı bilgisini döner | Gerekli |
| POST | /auth/logout | Refresh token'ı geçersiz kılar | Gerekli |
Login İstek/Yanıt Örneği
// İ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.
| Metod | Endpoint | Açıklama |
|---|---|---|
| GET | /stations | Tü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
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.
| Metod | Endpoint | Açıklama |
|---|---|---|
| POST | /charging/start | connectorId + vehicleId + paymentMethodId ile şarj başlat |
| POST | /charging/stop | Aktif oturumu durdur, enerji ve maliyet hesapla |
| GET | /charging/active | Kullanıcının aktif şarj oturumu varsa döner |
| GET | /charging/history | Tamamlanmış ş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.
| Metod | Endpoint | Açıklama |
|---|---|---|
| GET | /vehicles/brands | Tüm Elektrikli araç markaları (id, name, logoUrl) |
| GET | /vehicles/brands/{id}/models | Seçilen markanın modelleri |
| GET | /vehicles | Kullanıcının kayıtlı araçları |
| POST | /vehicles | brandId, modelId, licensePlate, year ile araç ekle |
Ödeme, Profil, Favoriler
| Metod | Endpoint | Açıklama |
|---|---|---|
| GET | /payment/methods | Kullanıcının kayıtlı ödeme kartları |
| POST | /payment/methods | Yeni kart ekle (cardNumber, holderName, expiry, cvv) |
| GET | /profile | Kullanıcı profil bilgileri |
| PUT | /profile | Profil güncelleme |
| GET | /favorites | Favori istasyon listesi |
| POST | /favorites/toggle | stationId ile favori ekle/çıkar (toggle) |
| GET | /campaigns | Aktif 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.
// İ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.
Ş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.
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şen | Detay | Teknoloji |
|---|---|---|
| Splash Ekranı | Yukarıdan aşağı kırılarak inen yıldırım bolt animasyonu | CustomPaint |
| Ş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 detaylar | CustomPaint |
| Harita Geçişi | Zoom-out → zoom-in kamera animasyonu, çakışma engelleme | AnimationController |
| QR Tarayıcı | Kamera üzerinde yukarı-aşağı hareket eden scan line | CustomPaint |
| Filtre Chip'leri | Her filtre tipi farklı gradient renk (teal, amber, rose, indigo) | Widget |
| Onboarding | "Nasıl şarj edilir" adım adım rehber | Lottie |
| Nav Bar | 4 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.
| Tablo | Açıklama | Önemli Alanlar | İlişkiler |
|---|---|---|---|
| User | Kullanıcı hesabı | Phone, Name, Surname, Email, PasswordHash, IsVerified | Vehicle, PaymentMethod, Favorite |
| Station | Şarj istasyonu | Name, Address, Lat, Lng, Status, Rating, ImageUrl, IsActive | Connector, Favorite |
| Connector | Konnektör | Type (CCS/CHAdeMO/Type2), ChargeType (AC/DC), PowerKw, PricePerKwh | Station, ChargingSession |
| ChargingSession | Şarj oturumu | StartedAt, EndedAt, EnergyKwh, CostTl, DurationSeconds, Status | User, Connector |
| Vehicle | Kullanıcı aracı | LicensePlate, Year | User, VehicleBrand, VehicleModel |
| VehicleBrand | Araç markası | Name, LogoUrl | VehicleModel |
| VehicleModel | Araç modeli | Name | VehicleBrand |
| PaymentMethod | Ödeme kartı | Last4, CardHolderName, ExpiryDate, IsPrimary | User |
| Favorite | Favori istasyon | - | User, Station |
| OtpCode | SMS doğrulama | Code, ExpiresAt (2dk), IsUsed | User |
| RefreshToken | Token yenileme | Token, ExpiresAt, IsRevoked | User |
| Campaign | Kampanya | Title, Description, DiscountPercent, StartDate, EndDate, IsActive | - |
| AdminUser | Yönetici (ayrı tablo) | Email, PasswordHash | - |
| BillingInfo | Fatura adresi | CompanyName, TaxNumber, Address | User |
Enum Tipleri
| Enum | Değerler | Kullanıldığı Yer |
|---|---|---|
| StationStatus | Available, Occupied, Offline, Fault | Station.Status |
| ConnectorType | Type2, CCS, CHAdeMO, Tesla | Connector.Type |
| ChargeType | AC, DC | Connector.ChargeType |
| ChargingStatus | Active, Completed, Stopped, Failed | ChargingSession.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.
| Servis | Sorumluluk | Önemli Metotlar |
|---|---|---|
| AuthService | Login, register, OTP, JWT üretimi, token refresh | Login(), Register(), VerifyOtp(), RefreshToken() |
| StationService | İstasyon CRUD, yakındaki istasyonlar hesaplama | GetAll(), GetById(), GetNearby(), Create(), Update() |
| ChargingService | Şarj başlat/durdur, geçmiş, aktif oturum | Start(), Stop(), GetActive(), GetHistory() |
| VehicleService | Marka/model listeleme, araç CRUD | GetBrands(), GetModels(), AddVehicle() |
| PaymentService | Ödeme yöntemi CRUD | GetMethods(), AddMethod() |
| IyzicoPaymentService | iyzico POS entegrasyonu, şarj sonrası otomatik tahsilat | CreatePayment() |
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.
| Sayfa | Adres | İşlevler |
|---|---|---|
| Dashboard | /admin/dashboard | Toplam istasyon, aktif şarj, günlük gelir, kullanıcı sayısı, son oturumlar |
| İstasyonlar | /admin/stations | Listeleme, ekleme, düzenleme, silme (CRUD) |
| Kullanıcılar | /admin/users | Kullanıcı listesi, doğrulama durumu, kayıt tarihi |
| Şarj Oturumları | /admin/charging | Aktif/tamamlanan oturumlar, enerji, maliyet, süre |
| Araçlar | /admin/vehicles | 17+ marka ve modelleri, model sayıları |
| Kampanyalar | /admin/campaigns | Kampanya oluşturma, aktif/pasif durumu |
| Bildirimler | /admin/notifications | Bildirim 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:
| Veri | Miktar | Detay |
|---|---|---|
| Admin hesabı | 1 | Yönetim paneli erişimi için |
| Şarj istasyonu | 6 | Gerçekçi koordinatlar, farklı durumlar |
| Konnektör | ~15 | Her istasyonda 2-3 adet (AC Type2, DC CCS, DC CHAdeMO) |
| Elektrikli araç markası | 17 | Tesla, BYD, BMW, Mercedes, Audi, Porsche, Volvo, Hyundai, Kia, VW, Nissan, Renault, Peugeot, Fiat, MG, Cupra, TOGG |
| Araç modeli | ~40 | Her 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ı
| Parametre | Değer |
|---|---|
| Ortam | Sandbox (Test) |
| Base URL | https://sandbox-api.iyzipay.com |
| Test Kart No | 5528790000000008 |
| SKT | 12/2030 |
| CVV | 123 |
Ödeme Akışı
Ödeme ChargingService.StopChargingAsync() içinde tetiklenir:
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
| Parametre | Tip | Açıklama |
|---|---|---|
| userId | string | Buyer kimliği |
| userEmail | string | Buyer e-posta |
| userName | string | Buyer ad soyad |
| cardNumber | string | Kart numarası (sandbox: test kart) |
| amount | decimal | Tutar (TL) |
| description | string | "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:
| Tetikleyici | Tip | Başlık |
|---|---|---|
| Şarj başlatıldı | charging_started | Şarj Başladı |
| Şarj durduruldu | charging_complete | Şarj Tamamlandı |
| İlk bildirim açılışı | system, campaign, station_available, price_change | Hoş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
| Method | Endpoint | Açıklama |
|---|---|---|
| GET | /api/v1/notifications | Kullanıcının bildirimleri (ilk açılışta auto-seed) |
| PUT | /api/v1/notifications/{id}/read | Tek bildirim okundu işaretle |
| PUT | /api/v1/notifications/read-all | Tümünü okundu yap |
Bildirim Tipleri
| Tip | Açıklama | Renk |
|---|---|---|
| charging_complete | Şarj tamamlandı | Yeşil |
| charging_started | Şarj başladı | Teal |
| campaign | Kampanya/promosyon | Pembe |
| station_available | İstasyon müsait oldu | Mavi |
| price_change | Fiyat değişikliği | Amber |
| system | Sistem bildirimi | İndigo |
Favori Sistemi
Favoriler veritabanında Favorites tablosunda saklanır (UserId + StationId composite unique). MapBloc üzerinden yönetilir.
Optimistic Update Akışı
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
| Method | Endpoint | Açıklama |
|---|---|---|
| GET | /api/v1/favorites | Favori istasyonlar (station detaylarıyla) |
| POST | /api/v1/favorites | Favoriye 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.
| Sayfa | Route | İşlev |
|---|---|---|
| Profil Düzenle | /profile/edit | Ad, soyad, telefon, e-posta güncelleme |
| Fatura Bilgileri | /profile/billing | Bireysel/kurumsal toggle, TC/vergi no validation, adres |
| Bildirimler | /profile/notifications | API'den gelen bildirimler, okundu işaretleme, tümünü oku |
| Araçlarım | /profile/vehicles | Kayıtlı araç listesi + araç ekleme |
| Ödeme Yöntemleri | /profile/payment | Kayıtlı kartlar, kart ekleme, birincil kart |
| Şarj Geçmişi | /profile/history | Tarihe göre gruplanmış oturumlar, detay bottom sheet |
| Yardım & Destek | /profile/help | FAQ accordion, iletişim kartları |
| Gizlilik Politikası | /profile/privacy | KVKK uyumlu 7 bölüm |
| Hakkında | /profile/about | Uygulama bilgisi, teknolojiler, geliştirici |
| Nasıl Şarj Edilir | /profile/how-to-charge | 4 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
| Alan | Bireysel | Kurumsal |
|---|---|---|
| TC / Vergi No | 11 hane zorunlu | 10 hane zorunlu |
| Firma Adı | — | Zorunlu |
| Vergi Dairesi | — | Zorunlu |
| Adres | Zorunlu | Zorunlu |
| Şehir | Zorunlu | Zorunlu |
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.