first commit
This commit is contained in:
48
accounts/models/account.go
Normal file
48
accounts/models/account.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
gorm.Model
|
||||
UserName string `json:"username" gorm:"type:varchar(255)"`
|
||||
Email string `gorm:"uniqueIndex;not null;type:varchar(255)" json:"email"`
|
||||
Password string `json:"-" gorm:"type:varchar(255)"` // Password shouldn't be returned in JSON
|
||||
EmailVerified *bool `gorm:"default:false" json:"email_verified"` // default false for email/password registration
|
||||
EmailVerifyToken string `gorm:"index;type:varchar(255)" json:"-"`
|
||||
EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty"`
|
||||
IsAdmin *bool `gorm:"default:false" json:"is_admin"`
|
||||
SocialAccounts []SocialAccount `gorm:"foreignKey:UserID" json:"social_accounts,omitempty"`
|
||||
Profile []Profile `gorm:"foreignKey:UserID" json:"profiles,omitempty"`
|
||||
}
|
||||
|
||||
// IsEmailVerified Email Veriyf i False Döndürüyor
|
||||
func (u *User) IsEmailVerified() bool {
|
||||
if u.EmailVerified == nil {
|
||||
return false
|
||||
}
|
||||
return *u.EmailVerified
|
||||
}
|
||||
|
||||
// SocialAccount model structure
|
||||
type SocialAccount struct {
|
||||
gorm.Model
|
||||
UserID uint64 `gorm:"type:bigint unsigned;not null;index" json:"user_id"`
|
||||
Provider string `gorm:"type:varchar(32);not null;uniqueIndex:idx_social_provider_identity" json:"provider"` // google, github
|
||||
ProviderID string `gorm:"type:varchar(191);not null;uniqueIndex:idx_social_provider_identity" json:"provider_id"`
|
||||
Email string `json:"email" gorm:"type:varchar(255)"`
|
||||
Name string `json:"name,omitempty" gorm:"type:varchar(255)"` // Full name from provider
|
||||
AvatarURL string `json:"avatar_url,omitempty" gorm:"type:varchar(255)"` // Avatar URL from provider
|
||||
|
||||
}
|
||||
type Profile struct {
|
||||
gorm.Model
|
||||
UserID uint64 `gorm:"type:bigint unsigned;not null;index" json:"user_id"`
|
||||
AvatarURL string `json:"avatar_url,omitempty" gorm:"type:varchar(255)"` // Avatar URL from provider
|
||||
FirstName string `json:"first_name" gorm:"type:varchar(255)"` // Full name from provider
|
||||
LastName string `json:"last_name" gorm:"type:varchar(255)"` // Full name from provider
|
||||
|
||||
}
|
||||
65
accounts/models/models_test.go
Normal file
65
accounts/models/models_test.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// ─── User.IsEmailVerified ────────────────────────────────────────────────────
|
||||
|
||||
func TestIsEmailVerified_NilPointer(t *testing.T) {
|
||||
u := &User{}
|
||||
if u.IsEmailVerified() {
|
||||
t.Fatal("expected false when EmailVerified is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmailVerified_False(t *testing.T) {
|
||||
v := false
|
||||
u := &User{EmailVerified: &v}
|
||||
if u.IsEmailVerified() {
|
||||
t.Fatal("expected false when EmailVerified is &false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsEmailVerified_True(t *testing.T) {
|
||||
v := true
|
||||
u := &User{EmailVerified: &v}
|
||||
if !u.IsEmailVerified() {
|
||||
t.Fatal("expected true when EmailVerified is &true")
|
||||
}
|
||||
}
|
||||
|
||||
// ─── User struct tag doğrulamaları ──────────────────────────────────────────
|
||||
|
||||
func TestUser_PasswordHiddenFromJSON(t *testing.T) {
|
||||
f, ok := reflect.TypeOf(User{}).FieldByName("Password")
|
||||
if !ok {
|
||||
t.Fatal("User has no Password field")
|
||||
}
|
||||
if tag := f.Tag.Get("json"); tag != "-" {
|
||||
t.Fatalf("expected Password json tag to be \"-\", got %q", tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUser_EmailUniqueIndexTagSet(t *testing.T) {
|
||||
f, ok := reflect.TypeOf(User{}).FieldByName("Email")
|
||||
if !ok {
|
||||
t.Fatal("User has no Email field")
|
||||
}
|
||||
if gormTag := f.Tag.Get("gorm"); gormTag == "" {
|
||||
t.Fatal("Email field has no gorm tag")
|
||||
}
|
||||
}
|
||||
|
||||
// ─── RefreshToken alanları ───────────────────────────────────────────────────
|
||||
|
||||
func TestRefreshToken_HasRequiredFields(t *testing.T) {
|
||||
typ := reflect.TypeOf(RefreshToken{})
|
||||
required := []string{"UserID", "TokenID", "TokenHash", "TokenFingerprint", "ExpiresAt", "Revoked"}
|
||||
for _, name := range required {
|
||||
if _, ok := typ.FieldByName(name); !ok {
|
||||
t.Errorf("RefreshToken missing required field: %s", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
26
accounts/models/token.go
Normal file
26
accounts/models/token.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// RefreshToken represents a server-side record of issued refresh tokens
|
||||
// to support rotation, revocation and reuse detection.
|
||||
type RefreshToken struct {
|
||||
gorm.Model
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
TokenID string `gorm:"type:varchar(128);not null;uniqueIndex" json:"token_id"`
|
||||
// TokenHash is SHA-256 hex of the refresh token string (64 chars).
|
||||
// Stored instead of the raw token for security, while still allowing debug/lookup.
|
||||
TokenHash string `gorm:"type:char(64);index" json:"token_hash"`
|
||||
// TokenFingerprint is a masked representation (e.g. first6...last4) to help operators
|
||||
// visually correlate DB rows with logs without storing full token.
|
||||
TokenFingerprint string `gorm:"type:varchar(32);index" json:"token_fingerprint"`
|
||||
ExpiresAt time.Time `gorm:"index" json:"expires_at"`
|
||||
Revoked bool `gorm:"index" json:"revoked"`
|
||||
ReplacedByTokenID string `gorm:"type:varchar(128)" json:"replaced_by_token_id"`
|
||||
UserAgent string `gorm:"type:varchar(255)" json:"user_agent"`
|
||||
IP string `gorm:"type:varchar(64)" json:"ip"`
|
||||
}
|
||||
Reference in New Issue
Block a user