Skip to content

ituacm/yilbasikovani-backend

Repository files navigation

ITU Secret Santa Backend

ITU (İstanbul Teknik Üniversitesi) öğrencileri için geliştirilmiş Secret Santa (Gizli Hediye) uygulamasının backend servisi.

📋 İçindekiler

🎯 Genel Bakış

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 matchSignature değ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

🛠 Teknolojiler

  • 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)

📦 Kurulum

Gereksinimler

  • Node.js (v18 veya üzeri)
  • PostgreSQL
  • npm veya yarn

Adımlar

  1. Projeyi klonlayın

    git clone <repository-url>
    cd itu-secretsanta-backend
  2. Bağımlılıkları yükleyin

    npm install
  3. Prisma Client'ı oluşturun

    npm run prisma:gen
  4. Veritabanı migration'larını çalıştırın

    npm run prisma:mig
  5. Environment değişkenlerini ayarlayın .env dosyası oluşturun ve gerekli değişkenleri ekleyin (bkz. Yapılandırma)

  6. Seed verilerini yükleyin (opsiyonel)

    npm run seed
    npm run seed:discovery-questions
  7. Uygulamayı başlatın

    # Development
    npm run dev
    
    # Production
    npm run build
    npm start

⚙️ Yapılandırma

Environment Değişkenleri

.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-password

📁 Proje Yapısı

itu-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 Fazları

Uygulama 5 farklı fazda çalışır. Her faz, belirli işlemlerin yapılabilmesini sağlar:

1. REGISTRATION_OPEN

  • ✅ 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

2. REGISTRATION_CLOSED

  • ❌ 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ı

3. MATCHED_PHASE

  • ✅ 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

4. DELIVERY_AND_CHAT_PHASE

  • ✅ 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

5. OVER

  • ❌ Tüm işlemler kapalı
  • ✅ Sadece okuma işlemleri (geçmiş veriler)

📡 API Dokümantasyonu

Base URL

http://localhost:3000

Authentication

Çoğu endpoint JWT token gerektirir. Token'ı Authorization header'ında gönderin:

Authorization: Bearer <access_token>

Endpoints

Health Check

GET /

Sunucunun çalışıp çalışmadığını kontrol eder.

Response:

{
  "ok": true,
  "ts": "2024-01-01T00:00:00.000Z"
}

🔐 Authentication Endpoints

Kayıt
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"
}
Giriş
POST /auth/login

Body:

{
  "email": "[email protected]",
  "password": "securePassword123"
}

Response:

{
  "access": "jwt_access_token"
}

Not: Refresh token cookie olarak gönderilir.

Token Yenileme
POST /auth/refresh

Refresh token cookie'den okunur.

Response:

{
  "access": "new_jwt_access_token"
}
Çıkış
POST /auth/logout

Refresh token cookie'den okunur ve iptal edilir.

Tüm Cihazlardan Çıkış
POST /auth/logout-all

Auth: Gerekli

2FA Token İsteği
POST /auth/2fa

Auth: Gerekli

Body:

{
  "scope": "verify-email" | "reset-password"
}

Response:

{
  "msg": "Verification email sent to [email protected]"
}
Email Doğrulama
POST /auth/verify-email?token=<2fa_token>

Auth: 2FA Token (query parameter)

Şifre Sıfırlama
POST /auth/reset-password?token=<2fa_token>

Auth: 2FA Token (query parameter)

Body:

{
  "newPassword": "newSecurePassword123"
}

👤 Kullanıcı Bilgileri

Kendi Bilgilerimi Getir
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"
    }
  ]
}

📊 Uygulama Durumu

Durum Bilgisi
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"
  }
}

🎁 Eşleştirme

Eşleştirme Bilgileri
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.


❓ Keşif Soruları

Soruları Listele
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"
    },
    ...
  ]
}
Soruya Cevap Ver
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.

Kendi Cevaplarımı Getir
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"
    },
    ...
  ]
}
Partner'in Cevaplarını Getir
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 null olarak döner

🎁 Eşleştirme (Devam)

Eşleştirme Şikayeti
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

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

Geçmiş Mesajları Getir
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).


💬 Chat (WebSocket)

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.accessToken ile 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:

Mesaj Gönderme

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 Endpoints

Admin paneli için özel endpoint'ler. Tüm admin endpoint'leri admin token gerektirir.

Admin Girişi
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.

Kullanıcıları Listele
GET /admin/users

Auth: Admin token gerekli

Query Parameters:

  • email (optional): Email ile filtreleme
  • emailVerified (optional): Boolean - Email doğrulama durumu
  • isSuspended (optional): Boolean - Hesap askıya alınma durumu
  • limit (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"
    },
    ...
  ]
}
Kullanıcı Detayı
GET /admin/users/:id

Auth: Admin token gerekli

Response:

{
  "user": {
    "id": "uuid",
    "email": "[email protected]",
    "emailVerified": true,
    "participant": { ... },
    "reports": [ ... ]
  }
}
Eşleştirmeleri Listele
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": [ ... ]
    },
    ...
  ]
}
Eşleştirme Detayı
GET /admin/matches/:id

Auth: Admin token gerekli

Response:

{
  "match": {
    "id": "uuid",
    "participantA": { ... },
    "participantB": { ... },
    "messages": [ ... ],
    "reports": [ ... ]
  }
}
İstatistikler
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,
      ...
    }
  }
}

Hata Yönetimi

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ıyor
  • INVALID_CREDENTIALS: Email veya şifre hatalı
  • PHASE_NOT_OPEN: İstenen işlem mevcut fazda yapılamaz
  • MATCH_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ış

🗄️ Veritabanı Şeması

Modeller

User

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?
}

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
}

Match

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])
}

ChatMessage

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])
}

MatchReport

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])
}

DiscoveryQuestion

Keşif soruları.

model DiscoveryQuestion {
  id        String  @id @default(uuid())
  text      String  @db.VarChar(255)
  answers   QuestionAnswer[]
  createdAt DateTime @default(now())
}

QuestionAnswer

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])
}

GlobalSetting

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
}

AppState Enum

enum AppState {
  REGISTRATION_OPEN
  REGISTRATION_CLOSED
  MATCHED_PHASE
  DELIVERY_AND_CHAT_PHASE
  OVER
}

Gender Enum

enum Gender {
  MALE
  FEMALE
  OTHER
}

Department Enum

50+ ITÜ bölümü (BILGISAYAR_MUHENDISLIGI, YAPAY_ZEKA_VE_VERI_MUHENDISLIGI, vb.)

ReportScope Enum

enum ReportScope {
  CHAT
  QUESTIONS
}

🎲 Eşleştirme Algoritması

Eşleştirme algoritması src/scripts/run_matching.ts dosyasında bulunur.

Algoritma Adımları

  1. Durum Kontrolü: Sistem REGISTRATION_CLOSED fazında olmalı
  2. Katılımcı Toplama: Tüm participant'ların matchSignature değerleri alınır
  3. Uyumsuzluk Skoru Hesaplama: Her çift için uyumsuzluk (dissimilarity) skoru hesaplanır
  4. Sıralama: Skorlar küçükten büyüğe sıralanır (en düşük skor = en benzer = en iyi eşleşme)
  5. Eşleştirme: Greedy algoritma ile en benzer çiftler öncelikli olarak eşleştirilir
  6. Durum Güncelleme: Sistem MATCHED_PHASE fazına geçer

Skor Hesaplama

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)

Eşleştirme Çalıştırma

npm run script:matching

Not: Bu script transaction içinde çalışır. Hata durumunda rollback yapılır.


🔒 Güvenlik

Authentication

  • 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

2FA (Two-Factor Authentication)

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

Phase Guard

Her endpoint belirli fazlarda çalışabilir. phaseGuard middleware'i bu kontrolü yapar.

Rate Limiting

Başarısız giriş denemeleri takip edilir ve hesap kilitlenebilir.

Password Hashing

Argon2 algoritması ile şifreler hash'lenir.


📜 Script'ler

Eşleştirme Çalıştırma

npm run script:matching

Gereksinimler:

  • Sistem REGISTRATION_CLOSED fazında olmalı
  • En az 2 katılımcı olmalı

Not: Bu script transaction içinde çalışır. Hata durumunda rollback yapılır.

Faz Değiştirme

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-delivery

Not: 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.

Seed Verileri

# Ana seed
npm run seed

# Keşif soruları seed
npm run seed:discovery-questions

Not: Keşif soruları seed'i sadece REGISTRATION_OPEN veya REGISTRATION_CLOSED fazlarında çalışır.

Bulk Email Script'leri

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.

Test Script'leri

# 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-connection

Cron Job'lar

Onaylanmamış Kullanıcıları Temizleme

Sistem, 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-users

Not: 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

🐳 Docker

Proje Docker ve Docker Compose ile çalıştırılabilir.

Docker Compose ile Çalıştırma

# Container'ı build et ve başlat
docker-compose up -d --build

# Logları görüntüle
docker-compose logs -f

# Container'ı durdur
docker-compose down

Not: Docker Compose yapılandırması docker-compose.yml dosyasında bulunur. Container, port 4009'da (host) çalışır ve 3000 (container) portuna map edilir.

Dockerfile

Production için optimize edilmiş multi-stage build kullanılır:

  1. Builder stage: Dependencies yüklenir, Prisma client oluşturulur, TypeScript derlenir
  2. 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.


🧪 Development

Development Mode

npm run dev

TypeScript 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.

Build

npm run build

TypeScript dosyaları dist/ klasörüne derlenir. Handlebars template dosyaları da kopyalanır.

Production

npm start

Gereksinimler:

  • 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.


📧 Bulk Email Sistemi

Sistem, toplu email gönderme için Redis queue ve BullMQ worker kullanır.

Mimari

┌─────────────┐
│   Scripts   │  → İş mantığı, veri hazırlama
└──────┬──────┘
       │
       ↓
┌─────────────┐
│  Services    │  → Sadece queue'ya ekleme
└──────┬──────┘
       │
       ↓
┌─────────────┐
│    Queue    │  → Redis queue
└──────┬──────┘
       │
       ↓
┌─────────────┐
│   Worker    │  → Asenkron işleme (rate limited)
└─────────────┘

Servis Fonksiyonları

  • sendBulkEmail() - Genel bulk email gönderme
  • sendMatchingNotificationEmail() - Eşleştirme bildirimi
  • sendReminderEmail() - Hatırlatma email'i
  • sendAnnouncementEmail() - Genel duyuru

Worker

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";

Kullanım

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şleyecek

📝 Notlar

Karşılıklılık Prensibi

Keş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

Match Signature

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.

Tek Kalan Katılımcı

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

Email worker, bulk email gönderme işlemlerini asenkron olarak işler. Worker'ın çalışması için:

  1. Redis bağlantısının aktif olması gerekir
  2. Worker'ın import edilmesi gerekir (otomatik olarak başlatılır)
  3. 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 Authentication

Admin endpoint'leri özel bir JWT token gerektirir. Admin token'ı:

  • JWT_ADMIN_TOKEN_EXPIRES_MIN environment variable'ı ile yapılandırılır (varsayılan: 1440 dakika = 24 saat)
  • Admin email ve password ile /admin/login endpoint'inden alınır
  • ADMIN_EMAIL ve ADMIN_PASSWORD environment 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.


🤝 Katkıda Bulunma

  1. Fork yapın
  2. Feature branch oluşturun (git checkout -b feature/amazing-feature)
  3. Commit yapın (git commit -m 'Add some amazing feature')
  4. Push yapın (git push origin feature/amazing-feature)
  5. Pull Request açın

📄 Lisans

Bu proje ITU ACM Student Chapter için İsmet Arca Erdoğar tarafından geliştirilmiştir.


📞 İletişim

Sorularınız için issue açabilir veya proje yöneticileri ile iletişime geçebilirsiniz.

About

Backend Repo of ITUACM Yılbaşı Kovanı Event Application

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors