ITU (İstanbul Teknik Üniversitesi) öğrencileri için geliştirilmiş Secret Santa (Gizli Hediye) uygulamasının backend servisi.
- Genel Bakış
- Teknolojiler
- Kurulum
- Yapılandırma
- Proje Yapısı
- Uygulama Fazları
- API Dokümantasyonu
- Veritabanı Şeması
- Eşleştirme Algoritması
- Güvenlik
- Script'ler
Bu uygulama, ITÜ öğrencilerinin birbirleriyle eşleştirilerek Secret Santa etkinliği düzenlemesini sağlar. Sistem şu özelliklere sahiptir:
- Faz Bazlı Yönetim: Uygulama 5 farklı fazda çalışır ve her fazda farklı işlemler yapılabilir
- Akıllı Eşleştirme: Katılımcıların
matchSignaturedeğerlerine göre benzer katılımcıları birbiriyle eşleştirir - Keşif Soruları: Eşleştirilen katılımcılar birbirlerini tanımak için sorulara cevap verir
- Karşılıklılık Prensibi: Partner'in cevaplarını görmek için kendi cevaplarını vermiş olmak gerekir
- Gerçek Zamanlı Chat: Eşleştirilen katılımcılar zamanı geldiğinde Socket.IO üzerinden birbirleriyle mesajlaşabilir
- Güvenli Kimlik Doğrulama: JWT tabanlı authentication ve 2FA desteği
- Runtime: Node.js
- Framework: Express.js
- Dil: TypeScript
- Veritabanı: PostgreSQL
- ORM: Prisma
- Şifreleme: Argon2
- Email: AWS SES
- Template Engine: Handlebars
- Validasyon: Zod
- WebSocket: Socket.IO
- Queue: BullMQ (Redis)
- Cache/Queue: Redis (ioredis)
- Node.js (v18 veya üzeri)
- PostgreSQL
- npm veya yarn
-
Projeyi klonlayın
git clone <repository-url> cd itu-secretsanta-backend
-
Bağımlılıkları yükleyin
npm install
-
Prisma Client'ı oluşturun
npm run prisma:gen
-
Veritabanı migration'larını çalıştırın
npm run prisma:mig
-
Environment değişkenlerini ayarlayın
.envdosyası oluşturun ve gerekli değişkenleri ekleyin (bkz. Yapılandırma) -
Seed verilerini yükleyin (opsiyonel)
npm run seed npm run seed:discovery-questions
-
Uygulamayı başlatın
# Development npm run dev # Production npm run build npm start
.env dosyasında aşağıdaki değişkenler tanımlanmalıdır:
# Server
PORT=3000
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/itu_secretsanta"
# JWT
JWT_SECRET=your-secret-key
JWT_ACCESS_EXPIRES_MIN=15
JWT_TWO_FACTOR_EXPIRES_MIN=60
JWT_ADMIN_TOKEN_EXPIRES_MIN=1440
# Refresh Token
REFRESH_EXPIRES_DAYS=30
REFRESH_COOKIE_NAME=refresh_token
REFRESH_COOKIE_SECURE=true
# Frontend ve backend farklı origin'de ise (CORS kullanıyorsanız): "none" (HTTPS gerekli)
# Frontend ve backend aynı origin'de ise: "strict"
# Development (HTTP) için: "lax" ve REFRESH_COOKIE_SECURE=false
REFRESH_COOKIE_SAME_SITE=none
REFRESH_COOKIE_PATH=/
# Device Cookie
DEVICE_COOKIE_NAME=device_id
DEVICE_COOKIE_SECURE=true
# Frontend ve backend farklı origin'de ise (CORS kullanıyorsanız): "none" (HTTPS gerekli)
# Development (HTTP) için: "lax" ve DEVICE_COOKIE_SECURE=false
DEVICE_COOKIE_SAME_SITE=none
# Email
EMAIL_SUFFIX=@itu.edu.tr
# AWS SES
AWS_REGION=us-east-1
AWS_ACCESS_KEY_ID=your-access-key
AWS_SECRET_ACCESS_KEY=your-secret-key
SES_SENDER_EMAIL=[email protected]
# Redis (Queue için)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password
REDIS_USE_TLS=false
# Frontend (Socket.IO CORS için)
FRONTEND_URL=http://localhost:3000
# Admin
ADMIN_EMAIL=[email protected]
ADMIN_PASSWORD=your-admin-passworditu-secretsanta-backend/
├── prisma/
│ ├── schema.prisma # Prisma şema tanımları
│ ├── migrations/ # Veritabanı migration'ları
│ ├── seed.ts # Ana seed dosyası
│ └── discovery_questions_seed.ts # Keşif soruları seed
├── src/
│ ├── config/
│ │ ├── db.ts # Prisma client
│ │ └── env.ts # Environment değişkenleri
│ ├── modules/
│ │ ├── auth/ # Kimlik doğrulama modülü
│ │ │ ├── auth.controller.ts
│ │ │ ├── auth.routes.ts
│ │ │ ├── auth.validators.ts
│ │ │ ├── jwt.ts # JWT token işlemleri
│ │ │ ├── refresh.ts # Refresh token yönetimi
│ │ │ ├── users.service.ts
│ │ │ └── meRoutes/ # Kullanıcı bilgileri
│ │ ├── questions/ # Keşif soruları modülü
│ │ │ ├── questions.controller.ts
│ │ │ ├── questions.router.ts
│ │ │ ├── questions.service.ts
│ │ │ └── questions.validators.ts
│ │ ├── match/ # Eşleştirme modülü
│ │ │ ├── match.controller.ts
│ │ │ ├── match.router.ts
│ │ │ └── match.service.ts
│ │ ├── status/ # Uygulama durumu modülü
│ │ │ ├── status.controller.ts
│ │ │ ├── status.router.ts
│ │ │ └── status.service.ts
│ │ ├── chat/ # Chat modülü (Socket.IO + REST)
│ │ │ ├── chat.server.ts # Socket.IO server yapılandırması
│ │ │ ├── chat.manager.ts # Connection yönetimi ve authentication
│ │ │ ├── chat.events.ts # Chat event handlers
│ │ │ ├── chat.service.ts # Chat business logic
│ │ │ ├── chat.controller.ts # Chat REST controller
│ │ │ ├── chat.router.ts # Chat REST routes
│ │ │ ├── chat.validators.ts # Chat validators (Zod schemas)
│ │ │ └── chat.types.ts # TypeScript type definitions
│ │ └── common/ # Ortak yardımcılar
│ │ ├── asyncHandler.ts
│ │ ├── authGuard.ts
│ │ ├── phaseGuard.ts
│ │ ├── errorHandlers.ts
│ │ ├── errors.ts
│ │ └── validate.ts
│ ├── services/
│ │ └── mail-service/ # Email servisi
│ │ ├── aws.ses.ts
│ │ ├── mailSender.ts
│ │ ├── bulkemail.service.ts # Bulk email servisi
│ │ ├── bulkmail.worker.ts # Email worker (BullMQ)
│ │ └── templates/ # Email şablonları
│ ├── scripts/
│ │ ├── run_matching.ts # Eşleştirme script'i
│ │ ├── change_state_to_delivery.ts
│ │ ├── bulk-email/ # Bulk email script'leri
│ │ │ ├── matching-notification.ts
│ │ │ ├── reminder.ts
│ │ │ ├── announcement.ts
│ │ │ └── trigger-bulk-emails.ts
│ │ └── test/ # Test script'leri
│ │ ├── test_bulk_email.ts
│ │ └── create_test_users.ts
│ ├── utils/
│ │ └── scoreCalculator.ts # Eşleştirme skor hesaplama
│ ├── index.ts # Uygulama giriş noktası
│ └── server.ts # Express server yapılandırması
├── package.json
├── tsconfig.json
└── README.md
Uygulama 5 farklı fazda çalışır. Her faz, belirli işlemlerin yapılabilmesini sağlar:
- ✅ Kullanıcı kaydı açık
- ✅ Keşif soruları görüntülenebilir (sadece okuma)
- ❌ Keşif sorularına cevap verilemez
- ❌ Eşleştirme bilgileri görüntülenemez
- ❌ Kullanıcı kaydı kapalı
- ✅ Keşif soruları görüntülenebilir (sadece okuma)
- ❌ Keşif sorularına cevap verilemez
- ❌ Eşleştirme bilgileri görüntülenemez
⚠️ Eşleştirme script'i bu fazda çalıştırılmalı
- ✅ Eşleştirme tamamlandı
- ✅ Keşif sorularına cevap verilebilir
- ✅ Kendi cevaplarını görüntüleyebilir
- ✅ Partner'in cevaplarını görüntüleyebilir (karşılıklılık prensibi)
- ✅ Partner'in match signature'ını görebilir
- ✅ Hediyeler teslim ediliyor
- ✅ Chat özelliği aktif - Eşleştirilen katılımcılar birbirleriyle gerçek zamanlı mesajlaşabilir
- ✅ Keşif sorularına cevap verilebilir
- ✅ Kendi cevaplarını görüntüleyebilir
- ✅ Partner'in cevaplarını görüntüleyebilir
- ✅ Partner'in match signature'ını görebilir
- ❌ Tüm işlemler kapalı
- ✅ Sadece okuma işlemleri (geçmiş veriler)
http://localhost:3000
Çoğu endpoint JWT token gerektirir. Token'ı Authorization header'ında gönderin:
Authorization: Bearer <access_token>
GET /
Sunucunun çalışıp çalışmadığını kontrol eder.
Response:
{
"ok": true,
"ts": "2024-01-01T00:00:00.000Z"
}POST /auth/register
Faz: REGISTRATION_OPEN
Body:
{
"email": "[email protected]",
"password": "securePassword123",
"firstName": "Ahmet",
"lastName": "Yılmaz",
"gender": "MALE",
"department": "BILGISAYAR_MUHENDISLIGI",
"matchSignature": "ABCDEFGHIJK"
}Not: gender alanı zorunludur ve şu değerlerden biri olmalıdır: MALE, FEMALE, OTHER
Response:
{
"newUser": {
"id": "uuid",
"email": "[email protected]",
"emailVerified": false,
"participant": { ... }
},
"access": "jwt_access_token"
}POST /auth/login
Body:
{
"email": "[email protected]",
"password": "securePassword123"
}Response:
{
"access": "jwt_access_token"
}Not: Refresh token cookie olarak gönderilir.
POST /auth/refresh
Refresh token cookie'den okunur.
Response:
{
"access": "new_jwt_access_token"
}POST /auth/logout
Refresh token cookie'den okunur ve iptal edilir.
POST /auth/logout-all
Auth: Gerekli
POST /auth/2fa
Auth: Gerekli
Body:
{
"scope": "verify-email" | "reset-password"
}Response:
{
"msg": "Verification email sent to [email protected]"
}POST /auth/verify-email?token=<2fa_token>
Auth: 2FA Token (query parameter)
POST /auth/reset-password?token=<2fa_token>
Auth: 2FA Token (query parameter)
Body:
{
"newPassword": "newSecurePassword123"
}GET /me
Auth: Gerekli
Response:
{
"user": {
"id": "uuid",
"email": "[email protected]",
"emailVerified": true,
"lastLoginAt": "2024-01-01T00:00:00.000Z",
"participant": {
"firstName": "Ahmet",
"lastName": "Yılmaz",
"gender": "MALE",
"department": "BILGISAYAR_MUHENDISLIGI",
"matchSignature": "ABCDEFGHIJK"
}
},
"sessions": [
{
"userAgent": "Mozilla/5.0...",
"ip": "192.168.1.1",
"deviceId": "device-uuid",
"createdAt": "2024-01-01T00:00:00.000Z",
"expiresAt": "2024-02-01T00:00:00.000Z"
}
]
}GET /status
Response:
{
"status": {
"currentState": "MATCHED_PHASE",
"registrationDeadline": "2024-01-15T00:00:00.000Z",
"matchingDate": "2024-01-20T00:00:00.000Z",
"revealDate": "2024-01-25T00:00:00.000Z"
}
}GET /match
Auth: Gerekli
Faz: MATCHED_PHASE, DELIVERY_AND_CHAT_PHASE
Response:
{
"matchSignature": "XYZ12345678"
}Not: Bu, partner'inizin matchSignature değeridir. Bu değerle eşleştiğiniz katılımcının kayıt olurken sorulara verdiği cevaplara erişebilirsiniz.
GET /questions
Response:
{
"questions": [
{
"id": "uuid",
"text": "Kampüste takılmayı en sevdiğin gizli/sakin yer neresi?",
"createdAt": "2024-01-01T00:00:00.000Z"
},
...
]
}POST /questions/answer
Auth: Gerekli
Faz: MATCHED_PHASE, DELIVERY_AND_CHAT_PHASE
Body:
{
"questionId": "uuid",
"answer": "Kütüphane 3. kat, sessiz çalışma odası"
}Response:
{
"success": true,
"message": "Question answered successfully"
}Not: Her soruya sadece bir kez cevap verilebilir.
GET /questions/my-answers
Auth: Gerekli
Faz: MATCHED_PHASE, DELIVERY_AND_CHAT_PHASE
Response:
{
"answers": [
{
"id": "uuid",
"participantId": "uuid",
"questionId": "uuid",
"answerText": "Kütüphane 3. kat, sessiz çalışma odası",
"createdAt": "2024-01-01T00:00:00.000Z"
},
...
]
}GET /questions/partner-answers
Auth: Gerekli
Faz: MATCHED_PHASE, DELIVERY_AND_CHAT_PHASE
Response:
{
"answers": [
{
"id": "uuid",
"participantId": "partner-uuid",
"questionId": "uuid",
"answerText": "Partner'in cevabı" | null,
"createdAt": "2024-01-01T00:00:00.000Z"
},
...
]
}Önemli: Karşılıklılık Prensibi
- Partner'in bir soruya verdiği cevabı görmek için, siz de o soruya cevap vermiş olmalısınız
- Eğer bir soruya cevap vermediyseniz, partner'in o soruya verdiği cevap
nullolarak döner
POST /match/report
Auth: Gerekli
Faz: MATCHED_PHASE, DELIVERY_AND_CHAT_PHASE
Body:
{
"scope": "CHAT" | "QUESTIONS",
"description": "Şikayet açıklaması (opsiyonel)"
}Response:
{
"success": true,
"message": "Report submitted successfully",
"report": {
"id": "uuid",
"scope": "CHAT",
"createdAt": "2024-01-01T00:00:00.000Z"
}
}Not: Kullanıcılar eşleştirildikleri partner hakkında şikayet oluşturabilir. Şikayet kapsamı (scope) CHAT veya QUESTIONS olabilir.
Chat özelliği Socket.IO kullanarak gerçek zamanlı mesajlaşma sağlar ve REST API ile geçmiş mesajları çekmeyi destekler.
Faz: DELIVERY_AND_CHAT_PHASE
GET /chat/messages
Auth: Gerekli
Faz: DELIVERY_AND_CHAT_PHASE
Query Parameters:
limit(optional): Sayfa başına mesaj sayısı (1-100, default: 50)offset(optional): Atlanacak mesaj sayısı (default: 0)
Response:
{
"messages": [
{
"id": "message-uuid",
"content": "Merhaba! Hediyeni beğendin mi?",
"senderId": "participant-uuid",
"createdAt": "2024-01-01T00:00:00.000Z"
},
...
],
"pagination": {
"total": 150,
"limit": 50,
"offset": 0,
"hasMore": true
}
}Not: Mesajlar en eskiden en yeniye doğru sıralanır (chronological order).
Gerçek zamanlı mesajlaşma için Socket.IO kullanılır.
Bağlantı:
Socket.IO client ile bağlanın:
import { io } from "socket.io-client";
const socket = io("http://localhost:3000", {
auth: {
accessToken: "your_jwt_access_token",
},
});Authentication:
- Access token
socket.handshake.auth.accessTokenile gönderilmelidir - Token geçerli olmalı ve kullanıcı bir match'e sahip olmalıdır
- Sistem otomatik olarak kullanıcının match'ini bulur ve ilgili room'a bağlar
Events:
Client → Server:
socket.emit("sendMessage", {
content: "Merhaba! Hediyeni beğendin mi?",
});Validasyon:
content: String, minimum 1 karakter, maksimum 5000 karakter- Boşluk karakterleri otomatik olarak trim edilir
Server → Client (newMessage):
Tüm room'daki kullanıcılara (gönderen dahil) mesaj gönderilir:
{
"id": "message-uuid",
"content": "Merhaba! Hediyeni beğendin mi?",
"senderId": "participant-uuid",
"createdAt": "2024-01-01T00:00:00.000Z"
}Hata Eventleri:
{
"code": "INVALID_MESSAGE_DATA",
"message": "Mesaj içeriği eksik veya geçersiz."
}{
"code": "DB_SAVE_ERROR",
"message": "Message not saved."
}Bağlantı Hataları:
{
"code": "PHASE_NOT_OPEN",
"message": "DELIVERY_AND_CHAT_PHASE phase is not open"
}{
"code": "MATCH_NOT_FOUND",
"message": "No match found for this user. No chat room available."
}Özellikler:
- ✅ Gerçek zamanlı mesajlaşma
- ✅ Sadece eşleştirilen katılımcılar birbirleriyle mesajlaşabilir
- ✅ Mesajlar veritabanında kalıcı olarak saklanır
- ✅ Otomatik room yönetimi (her match için ayrı room)
- ✅ JWT tabanlı authentication
- ✅ Phase kontrolü (sadece DELIVERY_AND_CHAT_PHASE'de aktif)
- ✅ Geçmiş mesajları çekme REST API endpoint'i (
GET /chat/messages)
Admin paneli için özel endpoint'ler. Tüm admin endpoint'leri admin token gerektirir.
POST /admin/login
Body:
{
"email": "[email protected]",
"password": "admin-password"
}Response:
{
"access": "admin_jwt_token"
}Not: Admin token'ı Authorization: Bearer <admin_token> header'ı ile gönderilmelidir.
GET /admin/users
Auth: Admin token gerekli
Query Parameters:
email(optional): Email ile filtrelemeemailVerified(optional): Boolean - Email doğrulama durumuisSuspended(optional): Boolean - Hesap askıya alınma durumulimit(optional): Sayfa başına kayıt sayısı (default: 50, max: 100)offset(optional): Atlanacak kayıt sayısı (default: 0)
Response:
{
"users": [
{
"id": "uuid",
"email": "[email protected]",
"emailVerified": true,
"isSuspended": false,
"lastLoginAt": "2024-01-01T00:00:00.000Z",
"participant": { ... },
"createdAt": "2024-01-01T00:00:00.000Z"
},
...
]
}GET /admin/users/:id
Auth: Admin token gerekli
Response:
{
"user": {
"id": "uuid",
"email": "[email protected]",
"emailVerified": true,
"participant": { ... },
"reports": [ ... ]
}
}GET /admin/matches
Auth: Admin token gerekli
Query Parameters:
limit(optional): Sayfa başına kayıt sayısı (default: 50, max: 100)offset(optional): Atlanacak kayıt sayısı (default: 0)
Response:
{
"matches": [
{
"id": "uuid",
"participantA": { ... },
"participantB": { ... },
"messages": [ ... ],
"reports": [ ... ]
},
...
]
}GET /admin/matches/:id
Auth: Admin token gerekli
Response:
{
"match": {
"id": "uuid",
"participantA": { ... },
"participantB": { ... },
"messages": [ ... ],
"reports": [ ... ]
}
}GET /admin/statistics
Auth: Admin token gerekli
Query Parameters:
limit(optional): Sayfa başına kayıt sayısı (default: 50, max: 100)offset(optional): Atlanacak kayıt sayısı (default: 0)
Response:
{
"statistics": {
"totalParticipants": 100,
"genderDistribution": {
"MALE": 50,
"FEMALE": 45,
"OTHER": 5
},
"departmentDistribution": {
"BILGISAYAR_MUHENDISLIGI": 30,
...
}
}
}Tüm hatalar standart format ile döner:
{
"error": {
"code": "ERROR_CODE",
"message": "Human readable error message"
}
}Yaygın Hata Kodları:
EMAIL_IN_USE: Email zaten kullanılıyorINVALID_CREDENTIALS: Email veya şifre hatalıPHASE_NOT_OPEN: İstenen işlem mevcut fazda yapılamazMATCH_NOT_FOUND: Kullanıcı için eşleştirme bulunamadıQUESTION_NOT_FOUND: Soru bulunamadıANSWER_ALREADY_EXISTS: Bu soruya zaten cevap verilmişGLOBAL_SETTINGS_NOT_SET: Global ayarlar henüz yapılandırılmamış
Kullanıcı hesap bilgileri.
model User {
id String @id @default(uuid())
email String @unique
passwordHash String
emailVerified Boolean @default(false)
isSuspended Boolean @default(false)
failedLoginCount Int @default(0)
lockUntil DateTime?
lastLoginAt DateTime?
passwordChangedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
refreshTokens RefreshToken[]
expiredTwoFactorTokens ExpiredTwoFactorToken[]
participant Participant?
}Secret Santa katılımcısı bilgileri.
model Participant {
userId String @id
firstName String @db.VarChar(100)
lastName String @db.VarChar(100)
gender Gender
department Department
matchSignature String @db.VarChar(20)
answers QuestionAnswer[]
matchA Match[] @relation("MatchSideA")
matchB Match[] @relation("MatchSideB")
messages ChatMessage[]
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Eşleştirme kayıtları.
model Match {
id String @id @default(uuid())
participantAId String
participantBId String
participantA Participant @relation("MatchSideA", fields: [participantAId], references: [userId])
participantB Participant @relation("MatchSideB", fields: [participantBId], references: [userId])
messages ChatMessage[]
@@unique([participantAId, participantBId])
}Chat mesajları. Her mesaj bir match'e ve bir sender'a (participant) aittir.
model ChatMessage {
id String @id @default(uuid())
matchId String
senderId String
content String @db.Text
createdAt DateTime @default(now())
match Match @relation(fields: [matchId], references: [id])
sender Participant @relation(fields: [senderId], references: [userId])
@@index([matchId])
@@index([senderId])
}Eşleştirme şikayetleri. Kullanıcılar partner'leri hakkında şikayet oluşturabilir.
model MatchReport {
id String @id @default(uuid())
matchId String
reporterId String
scope ReportScope
description String? @db.Text
createdAt DateTime @default(now())
match Match @relation(fields: [matchId], references: [id], onDelete: Cascade)
reporter Participant @relation(fields: [reporterId], references: [userId], onDelete: Cascade)
@@index([matchId])
@@index([reporterId])
@@index([scope])
}Keşif soruları.
model DiscoveryQuestion {
id String @id @default(uuid())
text String @db.VarChar(255)
answers QuestionAnswer[]
createdAt DateTime @default(now())
}Kullanıcıların sorulara verdiği cevaplar.
model QuestionAnswer {
id String @id @default(uuid())
participantId String
questionId String
answerText String @db.Text
createdAt DateTime @default(now())
participant Participant @relation(fields: [participantId], references: [userId], onDelete: Cascade)
question DiscoveryQuestion @relation(fields: [questionId], references: [id])
@@unique([participantId, questionId])
@@index([participantId])
}Uygulama durumu ve tarihler.
model GlobalSetting {
id Int @id @default(1)
currentState AppState @default(REGISTRATION_OPEN)
registrationDeadline DateTime
matchingDate DateTime
revealDate DateTime
updatedAt DateTime @updatedAt
}enum AppState {
REGISTRATION_OPEN
REGISTRATION_CLOSED
MATCHED_PHASE
DELIVERY_AND_CHAT_PHASE
OVER
}enum Gender {
MALE
FEMALE
OTHER
}50+ ITÜ bölümü (BILGISAYAR_MUHENDISLIGI, YAPAY_ZEKA_VE_VERI_MUHENDISLIGI, vb.)
enum ReportScope {
CHAT
QUESTIONS
}Eşleştirme algoritması src/scripts/run_matching.ts dosyasında bulunur.
- Durum Kontrolü: Sistem
REGISTRATION_CLOSEDfazında olmalı - Katılımcı Toplama: Tüm participant'ların
matchSignaturedeğerleri alınır - Uyumsuzluk Skoru Hesaplama: Her çift için uyumsuzluk (dissimilarity) skoru hesaplanır
- Sıralama: Skorlar küçükten büyüğe sıralanır (en düşük skor = en benzer = en iyi eşleşme)
- Eşleştirme: Greedy algoritma ile en benzer çiftler öncelikli olarak eşleştirilir
- Durum Güncelleme: Sistem
MATCHED_PHASEfazına geçer
calculateDissimilarityScore fonksiyonu iki matchSignature arasındaki uyumsuzluğu (farklılığı) hesaplar:
- Her karakter pozisyonu için kontrol yapılır
- Farklı karakterler için ağırlık eklenir
- Ağırlıklar:
[3, 3, 3, 1, 1, 1, 1, 1, 1, 1, 1] - İlk 3 pozisyon daha önemli (ağırlık 3)
- Daha düşük skor = daha benzer = daha iyi eşleşme
- Sistem benzer katılımcıları birbiriyle eşleştirmeyi amaçlar
Örnek:
Signature A: "ABCDEFGHIJK"
Signature B: "ABXDEFGHIJK"
↑
Pozisyon 2 farklı → +3 uyumsuzluk skoru
Signature A: "ABCDEFGHIJK"
Signature B: "ABCDEFGHIJK"
Tümü aynı → 0 uyumsuzluk skoru (en benzer, en iyi eşleşme)
npm run script:matchingNot: Bu script transaction içinde çalışır. Hata durumunda rollback yapılır.
- JWT Access Token: Kısa ömürlü (15 dakika), API istekleri için
- Refresh Token: Uzun ömürlü (30 gün), cookie'de saklanır
- Token Rotation: Her refresh işleminde yeni token üretilir
Belirli işlemler için email tabanlı 2FA:
- Email doğrulama
- Şifre sıfırlama
2FA token'ları:
- Tek kullanımlık
- Kısa ömürlü (60 dakika)
- Kullanıldıktan sonra veritabanına kaydedilir
Her endpoint belirli fazlarda çalışabilir. phaseGuard middleware'i bu kontrolü yapar.
Başarısız giriş denemeleri takip edilir ve hesap kilitlenebilir.
Argon2 algoritması ile şifreler hash'lenir.
npm run script:matchingGereksinimler:
- Sistem
REGISTRATION_CLOSEDfazında olmalı - En az 2 katılımcı olmalı
Not: Bu script transaction içinde çalışır. Hata durumunda rollback yapılır.
Fazları manuel olarak değiştirmek için script'ler kullanılabilir:
# REGISTRATION_CLOSED fazına geçiş
npm run script:close-registration
# MATCHED_PHASE'e geçiş
tsx src/scripts/change_state_to_matched.ts
# DELIVERY_AND_CHAT_PHASE'e geçiş
npm run script:change-state-deliveryNot: change_state_to_matched.ts script'i mevcuttur ancak package.json'da script olarak tanımlı değildir. Doğrudan tsx ile çalıştırılabilir veya package.json'a eklenebilir.
# Ana seed
npm run seed
# Keşif soruları seed
npm run seed:discovery-questionsNot: Keşif soruları seed'i sadece REGISTRATION_OPEN veya REGISTRATION_CLOSED fazlarında çalışır.
Toplu email gönderme için kullanılan script'ler:
# Eşleştirme bildirimi email'leri
npm run script:bulk-email:matching
# Hatırlatma email'leri (varsayılan mesaj)
npm run script:bulk-email:reminder
# Hatırlatma email'leri (özel mesaj)
npm run script:bulk-email:reminder -- "Özel hatırlatma mesajı"
# Duyuru email'leri
npm run script:bulk-email:announcement -- "Başlık" "İçerik..."
# Master script (tüm senaryoları tetikler)
npm run script:bulk-email:trigger -- matching
npm run script:bulk-email:trigger -- reminder "Özel mesaj"
npm run script:bulk-email:trigger -- announcement "Başlık" "İçerik"Not: Tüm bulk email script'leri email'leri Redis queue'ya ekler. Email'ler worker tarafından asenkron olarak işlenir ve gönderilir. Worker'ın çalıştığından emin olun.
# Bulk email test
npm run script:test-bulk-email
# Test kullanıcıları oluştur
npm run script:create-test-users
# Socket.IO bağlantı testi
npm run script:test-socket-connectionSistem, 20 dakika içinde email doğrulaması yapmayan kullanıcıları otomatik olarak siler.
# Cron job'u çalıştır
npm run cron:prune-unverified-usersNot: Bu script production ortamında cron job olarak çalıştırılmalıdır. Örnek cron yapılandırması:
# Her 30 dakikada bir çalıştır
*/30 * * * * cd /path/to/project && npm run cron:prune-unverified-usersÖzellikler:
- 20 dakika içinde email doğrulaması yapmayan kullanıcıları siler
- Silinen kullanıcılar hakkında admin'e email bildirimi gönderir
- Hata durumunda admin'e hata bildirimi gönderir
Proje Docker ve Docker Compose ile çalıştırılabilir.
# Container'ı build et ve başlat
docker-compose up -d --build
# Logları görüntüle
docker-compose logs -f
# Container'ı durdur
docker-compose downNot: Docker Compose yapılandırması docker-compose.yml dosyasında bulunur. Container, port 4009'da (host) çalışır ve 3000 (container) portuna map edilir.
Production için optimize edilmiş multi-stage build kullanılır:
- Builder stage: Dependencies yüklenir, Prisma client oluşturulur, TypeScript derlenir
- Production stage: Sadece production dependencies ve derlenmiş dosyalar kopyalanır
Not: Dockerfile'da EXPOSE 8080 tanımlıdır, ancak uygulama PORT environment variable'ından port'u okur. Container içinde doğru port'un ayarlandığından emin olun.
npm run devTypeScript dosyaları otomatik olarak derlenir ve değişikliklerde yeniden başlatılır.
Not: Development modunda worker otomatik olarak başlatılır. Worker'ın çalıştığından emin olmak için src/services/mail-service/bulkmail.worker.ts dosyasının import edildiğinden emin olun.
npm run buildTypeScript dosyaları dist/ klasörüne derlenir. Handlebars template dosyaları da kopyalanır.
npm startGereksinimler:
- PostgreSQL veritabanı çalışıyor olmalı
- Redis çalışıyor olmalı (email queue için)
- Environment değişkenleri ayarlanmış olmalı
- Prisma migrations çalıştırılmış olmalı
Not: Production ortamında worker'ın çalıştığından emin olun. Worker'ı başlatmak için src/services/mail-service/bulkmail.worker.ts dosyasını import edin.
Sistem, toplu email gönderme için Redis queue ve BullMQ worker kullanır.
┌─────────────┐
│ Scripts │ → İş mantığı, veri hazırlama
└──────┬──────┘
│
↓
┌─────────────┐
│ Services │ → Sadece queue'ya ekleme
└──────┬──────┘
│
↓
┌─────────────┐
│ Queue │ → Redis queue
└──────┬──────┘
│
↓
┌─────────────┐
│ Worker │ → Asenkron işleme (rate limited)
└─────────────┘
sendBulkEmail()- Genel bulk email göndermesendMatchingNotificationEmail()- Eşleştirme bildirimisendReminderEmail()- Hatırlatma email'isendAnnouncementEmail()- Genel duyuru
Email worker (bulkmail.worker.ts) otomatik olarak başlatılır ve:
- Redis queue'dan email işlerini alır
- AWS SES üzerinden gönderir
- Rate limiting uygular (12 email/saniye)
- Retry mekanizması (3 deneme, exponential backoff)
Not: Worker, bulkmail.worker.ts dosyası import edildiğinde otomatik olarak başlatılır. Production ortamında worker'ın çalıştığından emin olun. Worker'ı ayrı bir process olarak çalıştırmak için:
// worker.ts
import "./services/mail-service/bulkmail.worker.js";veya worker'ı server başlatılırken import edin:
// index.ts veya server.ts
import "./services/mail-service/bulkmail.worker.js";Script'ler servis fonksiyonlarını çağırarak email'leri queue'ya ekler. Worker arka planda çalışarak email'leri işler.
Örnek:
import { sendMatchingNotificationEmail } from "./services/mail-service/bulkemail.service.js";
const emails = ["[email protected]", "[email protected]"];
await sendMatchingNotificationEmail(emails);
// Email'ler queue'ya eklendi, worker işleyecekKeşif soruları sisteminde önemli bir prensip vardır:
- Partner'in bir soruya verdiği cevabı görmek için, siz de o soruya cevap vermiş olmalısınız
- Bu, karşılıklılığı teşvik eder ve daha iyi bir deneyim sağlar
matchSignature 15 karakterlik bir string'dir (veritabanında VarChar(15)). Bu değer:
- Kayıt sırasında kullanıcı tarafından sağlanır
- Eşleştirme algoritmasında kullanılır
- Eşleştirme sonrası partner'in signature'ı görülebilir
- Bu signature ile partner'i bulabilirsiniz
Not: Eşleştirme algoritması 11 karakterlik signature'lar için tasarlanmıştır, ancak veritabanı şeması 15 karaktere kadar destekler.
Eğer katılımcı sayısı tek ise, son katılımcı eşleştirilemez. Sistem bu durumda uyarı verir ve admin müdahalesi gerekir.
Email worker, bulk email gönderme işlemlerini asenkron olarak işler. Worker'ın çalışması için:
- Redis bağlantısının aktif olması gerekir
- Worker'ın import edilmesi gerekir (otomatik olarak başlatılır)
- AWS SES yapılandırmasının doğru olması gerekir
Worker, src/services/mail-service/bulkmail.worker.ts dosyasında tanımlıdır ve export edildiğinde otomatik olarak başlatılır.
Admin endpoint'leri özel bir JWT token gerektirir. Admin token'ı:
JWT_ADMIN_TOKEN_EXPIRES_MINenvironment variable'ı ile yapılandırılır (varsayılan: 1440 dakika = 24 saat)- Admin email ve password ile
/admin/loginendpoint'inden alınır ADMIN_EMAILveADMIN_PASSWORDenvironment variable'larında tanımlı olmalıdır
Güvenlik Notu: Production ortamında admin credentials'ları güvenli bir şekilde saklanmalı ve düzenli olarak değiştirilmelidir.
- Fork yapın
- Feature branch oluşturun (
git checkout -b feature/amazing-feature) - Commit yapın (
git commit -m 'Add some amazing feature') - Push yapın (
git push origin feature/amazing-feature) - Pull Request açın
Bu proje ITU ACM Student Chapter için İsmet Arca Erdoğar tarafından geliştirilmiştir.
Sorularınız için issue açabilir veya proje yöneticileri ile iletişime geçebilirsiniz.