Registration¶
Durum: Adım 3 tamamlandı. CreateUser endpoint (backend + Flutter) tamamlandı.
register_details_screen.darttam işlevsel hale getirildi. Şifre hash'leme (Argon2id), username/password validasyonu, transaction'lı kullanıcı oluşturma ve başarı ekranı tamamlandı.IsEmailRegisteredInUserskontrolü eklendi — kayıtlı emailRegisterEmailaşamasındaErrEmailAlreadyRegisteredile reddedilir, doğrulama koduna ulaşılmaz. Son güncelleme: 6 Mart 2026
User Flow¶
Bölüm 1: Kullanıcı adımları, ekranlar, senaryo. Teknik detay yok.
Ekran 1: Email Input¶
- Kullanıcı üniversite e-posta adresini girer
- "Devam Et" butonuna basar
- E-posta doğrulanır ve kod gönderilir
- Başarı: Ekran 2'ye yönlendir (10 dakika timer başlar)
- Hata: Jenerik mesaj göster ve tekrar dene
Ekran 2: Email Verification Code¶
- 10 dakikalık timer gösterilir
- Kullanıcı email'den aldığı 6 haneli kodu girer
- "Doğrula" butonuna basar
- Başarı: Ekran 3'e yönlendir (önceki kodlar silinir)
- Hata (yanlış kod): "Kod yanlış. Tekrar deneyin." (max 5 deneme)
- Max deneme aşıldıysa: "Çok fazla deneme. Yeni kod isteyin."
- "Yeni Kod Al" butonu: 10 dakika timer sıfırlanır, yeni kod gönderilir
- Resend max 3 kez / 10 dakika (spam koruması)
- "Geri" butonu: Ekran 1'e dönüş (yeni kod gönderilir, oturum iptal)
Ekran 3: Username & Password¶
- Kullanıcı adı (3-20 karakter, harf/rakam/alt çizgi)
- Şifre (8+ karakter, tüm karakterler)
- Şifre baştan yazı (confirm)
- "Kullanım Koşulları" checkbox ✓ (zorunlu)
- "Gizlilik Politikası" checkbox ✓ (zorunlu)
- Her iki checkbox işaretlenince "Kayıt Ol" butonu aktif olur
- Başarı: Ekran 4'e yönlendir
- Hata: SnackBar ile hata mesajı göster
- Geri tuşu: Engellendi (
PopScope(canPop: false)) — geri dönüş yok
Ekran 4: Kayıt Tamamlandı¶
- "Kayıt başarılı! Giriş yap ekranına yönlendiriliyorsun..."
- 2 saniye sonra automatic olarak Login ekranına yönlendir
- NOT: Auto-login yapılmaz (güvenlik nedeniyle)
Edge Cases¶
Bölüm 2: Tüm "ya şöyle olursa" durumları. Normal akış dışındaki senaryolar.
| Senaryo | Davranış |
|---|---|
| Email Ekranında: | |
Email zaten users tablosunda kayıtlı (tam kayıt tamamlanmış) |
codes.AlreadyExists — ErrEmailAlreadyRegistered — RegisterEmail aşamasında erken yakalanır, doğrulama koduna ulaşılmaz |
Üniversite emaili değil (domain veya subdomaini universities tablosunda yok) |
Hata: codes.InvalidArgument — "universite domaini degil: |
| Boş email | Client-side: "Bu alan zorunludur" |
| Doğrulama Kodu Ekranında: | |
| Kod yanlış girilirse (1-4. deneme) | "Kod yanlış. Kalan deneme: X" |
| Max 5 deneme aşılırsa | Kod iptal: "Çok fazla deneme. 'Yeni Kod Al' butonuna basın" |
| Doğrulama kodu süresi dolduysa (10 dakika) | Otom. "Kodunuz süresi doldu. Yeni kod isteyin" |
| "Yeni Kod Al" 3 kez çıkartıldıktan sonra | Bekleme süresi: "10 dakika içinde tekrar deneyin" |
| Uygulama kapanıp tekrar açılırsa | Oturum devam: Aynı kod ekranında (kalan süre korunur) |
| Geri tuşuna basıp email girerse | Yeni kod gönderilir, oturum sıfırlanır, timer sıfırlanır |
| Username/Password Ekranında: | |
| Username zaten alınmışsa | codes.AlreadyExists — "Bu kullanıcı adı kullanılıyor. Başka bir tane deneyin" |
| Username kuralları (3-20 harf/rakam/alt çizgi) ihlal | codes.InvalidArgument — "Kullanıcı adı 3-20 karakter olmalı, sadece harf/rakam/alt çizgi" |
| Şifre confirm eşleşmezse | Client-side SnackBar — "Şifreler eşleşmiyor" |
| Şifre kuralları (min 8 karakter) ihlal | codes.InvalidArgument — "Şifre en az 8 karakter olmalı" |
| KVKK checkbox işaretlemezse | Kayıt butonu disabled kalır |
| Ekran 3'te sistem geri tuşuna basılırsa | PopScope(canPop: false) — geri git engellendi, kullanıcı Ekran 3'te kalır |
| Email doğrulanmamışken CreateUser çağrılırsa | codes.FailedPrecondition — ErrEmailNotVerified |
Email email_verification tablosunda yoksa |
codes.NotFound — ErrEmailNotFound |
Email zaten users tablosundaysa |
codes.AlreadyExists — ErrEmailAlreadyRegistered |
| Üniversite domain eşleşmezse | codes.NotFound — ErrUniversityNotFound |
| Network/System: | |
| Internet kesilirse (herhangi bir adımda) | Hata: "Bağlantı hatası. Lütfen tekrar deneyin" |
| Backend timeout | Hata: "İstek zaman aşımına uğradı. Tekrar deneyin" |
| Uygulama crash/kill olursa | Tekrar açılınca: En son seçilen email adımından başla |
| 2 cihazdan aynı emaille aynı anda kayıt | Son gelen kod geçerli, önceki iptal |
Implementation¶
Durum: Adım 3 tamamlandı. Adım 1 tamamlandı: Email kayıt (domain doğrulama + subdomain desteği).
ON CONFLICT DO UPDATE: aynı email tekrar gelirse kayıt üstüne yazılır, yeni kod gönderilir. Adım 2 tamamlandı: VerifyCode proto, backend ve Flutter tarafı tamamen çalışıyor.remaining_attemptsFlutter'a doğru aktarılıyor.code_validation.goeklendi (backend girdi validasyonu). Frontend validasyonu geçici olarak kaldırıldı (backend testi için); yayına alınmadan önce tekrar eklenecek. Countdown Timer tamamlandı:RegisterEmailResponse'aint64 expires_at_unixeklendi. Backend herRegisterEmailçağrısındaexpiresAt = time.Now() + verificationCodeTTLhesaplar, Flutter'a döndürür. FlutterDateTime.fromMillisecondsSinceEpoch(expiresAtUnix * 1000)ileTimer.periodic(1s)countdown başlatır. Yeni Kod Al (Resend) tamamlandı: Süre dolunca "Yeni kod almak için geri dönün." text'i kaldırıldı, "Yeni Kod Al" butonu eklendi.ResendCodeLoading/Success/Failurestate'leri veresendCode()cubit metodu eklendi. BackendRegisterEmailendpoint'iON CONFLICT DO UPDATEile kod/süre/attempt'i sıfırlar — ayrı bir RPC gerekmez. Adım 3 tamamlandı:CreateUserRPC eklendi (proto + backend + Flutter).register_details_screen.darttam işlevsel hale getirildi — form validasyonu, gRPC çağrısı, başarı ekranına yönlendirme tamamlandı. Şifre Argon2id ile hash'leniyor (password_hash.go).username_validation.govepassword_validation.goeklendi.repository.go'yaGetUniversityIDByDomain+ transaction'lıCreateUsermetodları eklendi.email_verificationkaydı kullanıcı oluşturulduktan sonra transaction içinde siliniyor.IsEmailRegisteredInUserseklendi:repository.go'yaSELECT EXISTS(...FROM users...)sorgusu,service.go'daRegisterEmailakışına domain kontrolünden sonra users tablosu kontrolü eklendi. Kayıtlı email artıkErrEmailAlreadyRegistered→codes.AlreadyExistsdöndürür; silent success kaldırıldı. Son güncelleme: 6 Mart 2026Önemli mimari not — gRPC nil error kuralı: Backend, yanlış kod için
error != nildöndürmüşse gRPC framework response body'yi silerdi; Flutterremaining_attempts'ı hiç alamazdı. Bu nedenle yanlış kod durumundaservice.gonilerror ile{success:false, remaining_attempts:N}döndürür. Gerçek hatalar (süre doldu, kilitlendi vb.) halaerror != nilolarak döner.Önemli mimari not — sentinel error + wrap pattern: Backend'de her hata durumu için sentinel error tanımlı (
ErrInvalidUsername,ErrInvalidPassword, vb.). Validation hataları özel struct'lara (usernameFormatErr,passwordFormatErr) wrap'leniyor. Bu struct'lar.Error()ile kullanıcıya gösterilecek mesajı,.Unwrap()ile sentinel'i döndürür. Handlererrors.Is()ile sentinel'i kontrol eder, gRPC status kodu belirler; mesajı iseerr.Error()ile direkt alır.
Akış¶
Ekran 1: RegisterEmail¶
"Devam Et" butonu
→ EmailInputScreen
→ RegisterCubit.submitEmail(email)
→ RegisterRepository.registerEmail(email)
→ DefaultRegisterGrpcClient → GrpcClient.stub
─── gRPC / TCP :50051 ───
→ EmailHandler.RegisterEmail()
→ EmailService.RegisterEmail()
→ validation.ValidateEmailFormat(req.Email) ← pure format kontrolü (RFC 5322)
→ domain = strings.Split(email, "@")[1] ← @ sonrası domain
→ EmailRepository.IsUniversityDomain(domain) ← DB sorgusu
SELECT EXISTS(
SELECT 1 FROM universities
WHERE ($1 = domain OR $1 LIKE '%.' || domain)
AND is_active = true
)
→ EmailRepository.IsEmailRegisteredInUsers(email) ← DB sorgusu
SELECT EXISTS(SELECT 1 FROM users WHERE email = $1)
→ kayıtlıysa: ErrEmailAlreadyRegistered (codes.AlreadyExists)
→ EmailRepository.SaveEmail()
→ INSERT INTO email_verification (email, verification_code, expires_at)
VALUES ($1, $2, $3)
ON CONFLICT (email) DO UPDATE SET
verification_code = EXCLUDED.verification_code,
expires_at = EXCLUDED.expires_at,
attempt_count = 0,
is_verified = FALSE
Başarı: {success: true, expires_at_unix: <unix_timestamp>} → RegisterSuccess(expiresAtUnix: expiresAtUnix) emit edilir → Navigator.push → VerificationCodeScreen(email: email, expiresAtUnix: expiresAtUnix)
Hata (geçersiz format): codes.InvalidArgument → SnackBar hata mesajı
Hata (geçersiz domain): codes.InvalidArgument → SnackBar "universite domaini degil: codes.AlreadyExists → SnackBar hata mesajı (doğrulama koduna erişilmez)
Hata (DB/sistem hatası): codes.Internal → SnackBar hata mesajı
Not:
DO UPDATEkullanıldığı için aynı email tekrar gönderilirseAlreadyExistshatası dönmez; kayıt sessizce güncellenir ve yeni kod gönderilir. Bu davranış Resend akışında da kullanılır.
Ekran 2: VerifyCode¶
"Doğrula" butonu
→ VerificationCodeScreen
→ RegisterCubit.submitCode(email, code)
→ RegisterRepository.verifyCode(email, code) ← Future<int?> döndürür
→ DefaultRegisterGrpcClient → GrpcClient.stub
─── gRPC / TCP :50051 ───
→ EmailHandler.VerifyCode()
→ EmailService.VerifyCode()
→ validation.ValidateCodeFormat(req.Code) ← 6 hane, sadece rakam (pure function)
→ Hata: fmt.Errorf("%w", ErrInvalidCodeFormat) ← handler: codes.InvalidArgument
→ EmailRepository.GetVerificationRecord(email) ← DB'den kaydı oku
→ is_verified == true? → ErrAlreadyVerified (codes.AlreadyExists)
→ expires_at geçti? → ErrCodeExpired (codes.DeadlineExceeded)
→ attempt_count >= 5? → ErrMaxAttemptsExceeded (codes.ResourceExhausted)
→ code eşleşmedi?
→ IncrementAttemptCount()
→ remaining = 5 - (attemptCount + 1)
→ return {success:false, remaining_attempts:N}, nil ← nil error! (gRPC kuralı)
→ code eşleşti?
→ MarkEmailVerified()
→ return {success:true, remaining_attempts:0}, nil
Flutter tarafında verifyCode dönüş değerine göre cubit davranışı:
| Dönen Değer | Durum | Cubit Davranışı |
|---|---|---|
null |
Başarılı | CodeVerificationSuccess emit et |
int > 0 (ör. 3) |
Yanlış kod | CodeVerificationFailure('Yanlış kod. 3 deneme hakkınız kaldı.') |
int == 0 |
Son hak da tukendi | CodeVerificationFailure('Maksimum deneme sayısına ulaştınız.') |
| exception | Gerçek hata | CodeVerificationFailure(mesaj) — remainingAttempts null |
Başarı: CodeVerificationSuccess → Navigator.push → RegisterDetailsScreen(email: email)
Hata (yanlış kod): CodeVerificationFailure → SnackBar "Yanlış kod. N deneme hakkınız kaldı."
Hata (0 hak): CodeVerificationFailure → SnackBar "Maksimum deneme sayısına ulaştınız."
Hata (süre doldu): CodeVerificationFailure → SnackBar backend mesajı
Hata (format): codes.InvalidArgument → SnackBar backend mesajı (attempt_count artmaz)
Ekran 2: Countdown Timer¶
Backend RegisterEmail her çağrıldığında (ilk kayıt veya resend) expiresAt = time.Now().Add(verificationCodeTTL) hesaplar ve RegisterEmailResponse.expires_at_unix (Unix saniye) olarak Flutter'a döndürür.
Flutter VerificationCodeScreen._startTimer():
expiresAt = DateTime.fromMillisecondsSinceEpoch(_expiresAtUnix * 1000)
Timer.periodic(1s) → diff = expiresAt.difference(DateTime.now())
diff > 0 → setState(_remaining = diff) → MM:SS göster
diff ≤ 0 → setState(_expired = true) → timer cancel, TextField disabled, verifyButton disabled
_expiresAtUnix neden late int field'a taşındı:
- widget.expiresAtUnix final — widget parametresi değiştirilemiyor
- Resend gelince BlocListener state.expiresAtUnix'i _expiresAtUnix'e yazar ve _startTimer() yeniden çağrılır
Ekran 2: Yeni Kod Al (Resend)¶
Süre dolunca _expired = true → TextField ve verifyButton devre dışı kalır → resendButton ekranda belirir.
"Yeni Kod Al" butonuna basılır
→ RegisterCubit.resendCode(email)
→ emit(ResendCodeLoading) → resendButton alanına CircularProgressIndicator
→ RegisterRepository.registerEmail(email) ← submitEmail ile AYNI metod
→ DefaultRegisterGrpcClient → GrpcClient.stub
─── gRPC / TCP :50051 ───
→ EmailHandler.RegisterEmail()
→ EmailService.RegisterEmail() ← yeni crypto/rand kodu + yeni expiresAt
→ SaveEmail() → ON CONFLICT DO UPDATE:
verification_code = yeni kod
expires_at = şimdi + verificationCodeTTL
attempt_count = 0
is_verified = FALSE
← {success:true, expires_at_unix: yeni_timestamp}
→ emit(ResendCodeSuccess(expiresAtUnix: yeniDeger))
→ BlocListener:
_timer?.cancel()
setState: _expiresAtUnix = state.expiresAtUnix, _expired = false
_startTimer() ← yeni süre başlar
_codeController.clear()
Neden ResendCodeSuccess ayrı state (neden RegisterSuccess emit edilmez):
- email_input_screen.dart'daki BlocListener RegisterSuccess dinler → Navigator.push tetiklenir → Ekran 2 üstüne tekrar Ekran 2 açılırdı (hatalı navigasyon)
- ResendCodeSuccess yalnızca VerificationCodeScreen'in kendi BlocListener'ı tarafından işlenir
Ekran 3: CreateUser¶
"Kayıt Ol" butonu
→ RegisterDetailsScreen
→ (client-side) şifre eşleşmiyorsa → SnackBar "Şifreler eşleşmiyor", backend'e gidilmez
→ RegisterCubit.submitDetails(email, username, password, termsAccepted, privacyAccepted)
→ emit(CreateUserLoading)
→ RegisterRepository.createUser(email, username, password, termsAccepted, privacyAccepted)
→ DefaultRegisterGrpcClient.createUser() → GrpcClient.stub
─── gRPC / TCP :50051 ───
→ EmailHandler.CreateUser()
→ EmailService.CreateUser()
→ validation.ValidateUsernameFormat(req.Username) ← 3-20 karakter, harf/rakam/alt çizgi
→ Hata: usernameFormatErr{} wraps ErrInvalidUsername → handler: codes.InvalidArgument
→ validation.ValidatePasswordFormat(req.Password) ← min 8 karakter, boşluk yasak
→ Hata: passwordFormatErr{} wraps ErrInvalidPassword → handler: codes.InvalidArgument
→ req.TermsAccepted == false? → ErrTermsNotAccepted (codes.InvalidArgument)
→ req.PrivacyAccepted == false? → ErrPrivacyNotAccepted (codes.InvalidArgument)
→ EmailRepository.GetUniversityIDByDomain(domain)
SELECT id FROM universities
WHERE ($1 = domain OR $1 LIKE '%.' || domain)
AND is_active = true
→ bulunamazsa: ErrUniversityNotFound (codes.NotFound)
→ hashPassword(req.Password) ← Argon2id (m=19456, t=2, p=1)
→ EmailRepository.CreateUser(ctx, email, username, hashedPassword, universityID)
BEGIN TRANSACTION
SELECT id, is_verified FROM email_verification WHERE email = $1 FOR UPDATE
→ kayıt yoksa: ErrEmailNotFound (codes.NotFound)
→ is_verified=false: ErrEmailNotVerified (codes.FailedPrecondition)
INSERT INTO users (email, username, password_hash, university_id)
VALUES ($1, $2, $3, $4)
→ email çakışması (23505): ErrEmailAlreadyRegistered (codes.AlreadyExists)
→ username çakışması (23505): ErrUsernameAlreadyExists (codes.AlreadyExists)
DELETE FROM email_verification WHERE email = $1
COMMIT
← {success:true, message:"Kayit basarili"}
→ emit(CreateUserSuccess)
Başarı: CreateUserSuccess → Navigator.pushReplacement → RegisterSuccessScreen
Hata: CreateUserFailure(message) → SnackBar hata mesajı
Handler error mapping (CreateUser):
| Sentinel Error | gRPC Code |
|---|---|
ErrInvalidUsername |
codes.InvalidArgument |
ErrInvalidPassword |
codes.InvalidArgument |
ErrTermsNotAccepted |
codes.InvalidArgument |
ErrPrivacyNotAccepted |
codes.InvalidArgument |
ErrEmailNotVerified |
codes.FailedPrecondition |
ErrUniversityNotFound |
codes.NotFound |
ErrEmailNotFound |
codes.NotFound |
ErrEmailAlreadyRegistered |
codes.AlreadyExists |
ErrUsernameAlreadyExists |
codes.AlreadyExists |
| diğer | codes.Internal |
Dosyalar¶
Proto:
- proto/yackr/v1/yackr.proto — RegisterEmail, VerifyCode, CreateUser RPC tanımları
- RegisterEmailResponse: success, int64 expires_at_unix
- VerifyCodeRequest: email, code
- VerifyCodeResponse: success, remaining_attempts
- CreateUserRequest: email, username, password, terms_accepted, privacy_accepted (YENİ)
- CreateUserResponse: success, message (YENİ)
- Generated: backend/gen/go/yackr/v1/ (Go), app/lib/gen/yackr/v1/ (Dart)
Backend:
- backend/cmd/server/main.go — sunucuyu başlatır
- backend/internal/server/server.go — handler'ı oluşturur ve kaydeder
- backend/internal/auth/register/handler.go — gRPC isteğini karşılar; RegisterEmail + VerifyCode + CreateUser handler'ları; sentinel error → gRPC status code eşlemesi; errors.Is() ile unwrap zinciri kontrol edilir; RegisterEmail'de ErrEmailAlreadyRegistered → codes.AlreadyExists eşlemesi eklendi
- backend/internal/auth/register/service.go — servis katmanı; RegisterEmail, VerifyCode, CreateUser metodları; CreateUser username/password validasyonu + terms/privacy kontrolü + hash + repository çağrısı; RegisterEmail'de domain kontrolünden sonra IsEmailRegisteredInUsers kontrolü eklendi; EmailRepositoryInterface'e IsEmailRegisteredInUsers metodu eklendi
- backend/internal/auth/register/repository.go — veritabanı CRUD; GetVerificationRecord, IncrementAttemptCount, MarkEmailVerified, GetUniversityIDByDomain, CreateUser (transaction), IsEmailRegisteredInUsers metodları; son metod users tablosunda email varlığını kontrol eder
- backend/internal/auth/register/password_hash.go — YENİ — hashPassword(): Argon2id ile şifre hash'leme (m=19456, t=2, p=1, saltLen=16); PHC string formatı ($argon2id$v=19$...)
- backend/internal/auth/register/verification_code.go — generateVerificationCode() (crypto/rand, 100000-999999) ve verificationCodeTTL
- backend/internal/db/db.go — veritabanı bağlantısı
- backend/internal/db/migrations/create_email_verification_table.sql — email_verification tablosu
- backend/internal/db/migrations/create_universities_table.sql — universities tablosu
- backend/internal/db/seeds/universities_tr.json — Türkiye üniversiteleri seed verisi
- backend/internal/db/seeds/seed_universities.py — seed yükleme scripti
- backend/internal/validation/email_validation.go — RFC 5322 email format validator (pure function)
- backend/internal/validation/code_validation.go — doğrulama kodu format validator; boş, 6 haneli olmayan, rakam dışı karakter kontrolü
- backend/internal/validation/username_validation.go — YENİ — ValidateUsernameFormat(): 3-20 karakter, harf/rakam/alt çizgi kuralı; usernameFormatErr struct ile wrap (sentinel: ErrInvalidUsername)
- backend/internal/validation/password_validation.go — YENİ — ValidatePasswordFormat(): min 8 karakter, boşluk yasak; passwordFormatErr struct ile wrap (sentinel: ErrInvalidPassword)
Backend testler:
- backend/internal/validation/email_validation_test.go — email format unit testler (table-driven)
- backend/internal/validation/code_validation_test.go — ValidateCodeFormat unit testler
- backend/internal/validation/username_validation_test.go — YENİ — ValidateUsernameFormat unit testler (table-driven); geçerli/geçersiz senaryolar, hata mesajı doğrulama; 100% coverage
- backend/internal/validation/password_validation_test.go — YENİ — ValidatePasswordFormat unit testler (table-driven); geçerli/geçersiz senaryolar, hata mesajı doğrulama; 100% coverage
- backend/internal/auth/register/handler_test.go — handler unit; TestHandler_CreateUser_Success, TestHandler_CreateUser_ErrorMapping (9 sentinel → gRPC code tablosu), TestHandler_CreateUser_WrappedUsernameError, TestHandler_CreateUser_WrappedPasswordError eklendi
- backend/internal/auth/register/service_test.go — service unit; 11 CreateUser senaryosu eklendi: başarı, geçersiz email/username/password, terms/privacy kabul edilmemesi, üniversite bulunamadı, repository hatası, validation sıralama, wrapped hata mesajları
- backend/internal/auth/register/password_hash_test.go — YENİ — hashPassword() unit testler: başarı, PHC format doğrulama, unique salt, farklı şifreler farklı hash, boş şifre, uzun şifre
- backend/internal/auth/register/verification_code_test.go — kod üretimi unit (6 hane, 100000–999999 aralığı, sadece rakam, TTL pozitif)
- backend/internal/auth/register/repository_integration_test.go — DB integration; TestRepo_CreateUser_Success, TestRepo_CreateUser_EmailNotVerified, TestRepo_CreateUser_EmailNotFound, TestRepo_CreateUser_DuplicateEmail, TestRepo_CreateUser_DuplicateUsername, TestRepo_GetUniversityIDByDomain_Exists, TestRepo_GetUniversityIDByDomain_NotExists, TestRepo_GetUniversityIDByDomain_Subdomain (tüm fonksiyon isimleri TestRepo_ prefix'i aldı — aynı paketteki service_test.go ile isim çakışmasını önlemek için)
Flutter:
- app/lib/main.dart — entry point, BlocProvider kurulumu
- app/lib/core/config.dart — gRPC bağlantı ayarları
- app/lib/core/grpc_client.dart — gRPC client
- app/lib/features/auth/register/register_repository.dart — gRPC çağrısı ve hata yönetimi; registerEmail Future<int> döndürür (expiresAtUnix); verifyCode Future<int?> döndürür; createUser YENİ — Future<void> döndürür, success=false gelirse exception fırlatır
- app/lib/features/auth/register/cubit/register_cubit.dart — state yönetimi; submitEmail, submitCode, resendCode metodları; submitDetails(email, username, password, termsAccepted, privacyAccepted) YENİ — CreateUserLoading/Success/Failure emit eder
- app/lib/features/auth/register/cubit/register_state.dart — state tanımları; RegisterSuccess(expiresAtUnix), CodeVerificationFailure(remainingAttempts), ResendCodeLoading, ResendCodeSuccess(expiresAtUnix), ResendCodeFailure(message); CreateUserLoading, CreateUserSuccess, CreateUserFailure(message) YENİ
- app/lib/features/auth/register/ui/email_input_screen.dart — email giriş ekranı
- app/lib/features/auth/register/ui/verification_code_screen.dart — doğrulama kodu ekranı (Ekran 2); resend sistemi dahil
- app/lib/features/auth/register/ui/register_details_screen.dart — TAM İŞLEVSEL HALE GETİRİLDİ — kullanıcı adı + şifre + şifre tekrar + 2 checkbox; BlocListener ile CreateUserSuccess → LoginScreen'e Navigator.pushReplacement; CreateUserFailure → SnackBar; şifre uyuşmazlığı client-side kontrol; PopScope(canPop: false) + AppBar(automaticallyImplyLeading: false)
- app/lib/features/auth/register/ui/register_success_screen.dart — Ekran 4 (artık kullanılmıyor); register_details_screen.dart başarı durumunda doğrudan LoginScreen'e yönlendirir
Flutter testler:
- app/test/features/auth/register/email_input_screen_test.dart — widget testleri
- app/test/features/auth/register/verification_code_screen_test.dart — widget testleri; resend dahil
- app/test/features/auth/register/register_repository_test.dart — repository unit; createUser başarılı, createUser hata testleri eklendi
- app/test/features/auth/register/register_cubit_test.dart — cubit unit; submitDetails başarılı: CreateUserLoading → CreateUserSuccess, submitDetails hata: CreateUserLoading → CreateUserFailure testleri eklendi
- app/test/features/auth/register/register_details_screen_test.dart — widget; mock shouldSucceed parametreli hale getirildi; Şifre eşleşmiyorsa SnackBar gösterir, Başarılı kayıt RegisterSuccessScreen'e yönlendirir, Backend hatası SnackBar gösterir testleri eklendi