commit f34e54c5a52241f4fde7c13d9a84de1872f2ad27 Author: Beyhan Oğur Date: Sun Apr 26 21:43:40 2026 +0300 first commit diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..ed479c5 --- /dev/null +++ b/.air.toml @@ -0,0 +1,58 @@ +#:schema https://json.schemastore.org/any.json + +env_files = [] +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "./tmp/main" + cmd = "go build -o ./tmp/main ." + delay = 1000 + entrypoint = ["./tmp/main"] + exclude_dir = ["assets", "tmp", "vendor", "testdata"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + ignore_dangerous_root_dir = false + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + silent = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + app_start_timeout = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.env b/.env new file mode 100644 index 0000000..2313dac --- /dev/null +++ b/.env @@ -0,0 +1,98 @@ +# Application Port Configuration +PORT=8080 +######################### +# MySql Configuration +DB_URL="beyhango:gg7678290@tcp(10.80.80.70:3306)/beyhango?charset=utf8mb4&parseTime=True&loc=Local" +#DB_URL=mariadb://beyhango:gg7678290@10.80.80.70:3306/beyhango + +DB_USER=cloud +DB_PASSWORD=gg7678290 +DB_NAME=atahan_go +DB_PORT=5432 +DB_HOST=10.80.80.70 +########################## +# Redis Configuration +REDIS_HOST=10.80.80.70 +REDIS_PORT=6379 +REDIS_USER=default +REDIS_PASSWORD=gg7678290 +REDIS_URL=redis://default:gg7678290@10.80.80.70:6379/0 +########################### +# JWT Secret +JWT_SECRET=eCT286MautyK9TWfz8SPSIWJTXV83mwLRwriLvSbpcMZuDLxywaHWP9Ju7xRoTS2 +# Session Secret (for OAuth state management) +SESSION_SECRET=kL8mN2pQ5rS9tV1wX4yZ7aB0cD3eF6gH9jK2mN5pQ8rS1tV4wX7yZ0aB3cD6eF9g +########################### +# Application URL +APP_URL=http://localhost:8080 +########################### +# OAuth - Google +GOOGLE_CLIENT_ID=915364976256-691m0s87as2r5vdbqr96f6humblseobt.apps.googleusercontent.com +GOOGLE_CLIENT_SECRET=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 +################################ +# OAuth - GitHub +GITHUB_CLIENT_ID=Ov23liUt9B61O46Mdfm4 +GITHUB_CLIENT_SECRET=c7fc8dcb1b2c8f22120608425d07d5efd995baaf +################################ +# OAuth Callback URL (Backend OAuth callback endpoint) +CLIENT_CALLBACK_URL=http://localhost:8080/v1/auth +# OAuth Redirect URL (Frontend callback page where user will be redirected with tokens) +OAUTH_REDIRECT_URL=http://localhost:3000/auth/callback +################################ +# AVATANE IMAGES +AVATAR_H=150 +AVATAR_W=150 +AVATAR_Q=90 +AVATAR_B=cover +AVATAR_F=webp +####################### +# Home IMAGES +HOME_IMAGE_H=400 +HOME_IMAGE_W=400 +HOME_IMAGE_Q=90 +HOME_IMAGE_B=cover +HOME_IMAGE_F=webp +####################### +# Aboutme IMAGES +ABOUTME_IMAGE_H=400 +ABOUTME_IMAGE_W=400 +ABOUTME_IMAGE_Q=90 +ABOUTME_IMAGE_B=cover +ABOUTME_IMAGE_F=webp +####################### +# MyService IMAGES +SERVICE_IMAGE_H=256 +SERVICE_IMAGE_W=256 +SERVICE_IMAGE_Q=90 +SERVICE_IMAGE_B=cover +SERVICE_IMAGE_F=webp +####################### +# BANNER IMAGES +BANNER_IMAGE_H=700 +BANNER_IMAGE_W=1920 +BANNER_IMAGE_Q=85 +BANNER_IMAGE_B=cover +BANNER_IMAGE_F=webp +################################ +# Email Settings (Mailpit) +EMAIL_HOST=212.64.215.243 +EMAIL_PORT=1025 +EMAIL_HOST_USER="" +EMAIL_HOST_PASSWORD="" +EMAIL_USE_TLS=false +EMAIL_USE_SSL=false +EMAIL_FROM=noreply@gauth.local +################################ +CORS_DEBUG=true +VITE_API_BASE_URL=http://localhost:8080 +FRONTEND_URL=http://localhost:3000 +# SESSION_SECRET=mTFY2jAOMWWxadVIWjRoPG9aOM3z9srCVoU35Gs1VZaRKgXet26cztUE8LLpwok9 +# JWT Settings +ACCESS_TOKEN_EXPIRE_MINUTES=120 +REFRESH_TOKEN_EXPIRE_DAYS=30 +### TRUSTED PROXIES +TRUSTED_PROXIES=127.0.0.1,10.0.0.0/8 + +CLOUD_FLARE_SITE_KEY='0x4AAAAAACHzHKvlEwMamxCM' +CLOUD_FLARE_SECRET='0x4AAAAAACHzHHisTSFzGw15HvwXF3yXRIg' +TURNSTILE_SECRET_KEY=0x4AAAAAACHzHHisTSFzGw15HvwXF3yXRIg diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e113cfe --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +### Go template +# If you prefer the allow list template instead of the deny list, see community template: +# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore +# +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +frontend.tar.gz +tmp +gobeyhango +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work +go.work.sum + +bin +gobeyhan \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md new file mode 100644 index 0000000..b7bc63f --- /dev/null +++ b/GEMINI.md @@ -0,0 +1,213 @@ +# Admin Panel — Giriş (Login) + Refresh Token Mantığı Prompt / İş Tanımı + +Amaç: +- Var olan `admin-panel` Vue projesinde (Tailwind, reka-ui, Pinia, vue-auth3, lucide-vue-next, zod, vb.) Go backend API ile sadece "login" işlevi sunan admin arayüzü oluşturmak. +- Backend JWT access token + refresh token mekanizması destekliyor (refresh token mantığı kurulmalı). +- Backend base: `http://localhost:8080/api/v1` — login endpoint: `POST /auth/login` (dönen: access token + refresh token + user) — refresh endpoint olarak `POST /auth/refresh` (veya backend ile uyumlu başka path) bekleniyor. +- Hedef: güvenli, responsive login + refresh token tabanlı token yenileme, token saklama ve otomatik yenileme akışı ile korunan admin dashboard iskeleti. + +Genel Tasarım Kararları (kritik) +- Access token (kısa ömürlü, örneğin 5-15 dk): uygulamada bellek/Pinia state veya memory-only saklama (localStorage'ta saklanması güvenlik riski taşıdığından önerilmez). +- Refresh token (uzun ömürlü, örneğin 7-30 gün): production için HttpOnly, Secure cookie ile saklanmalı — bu, XSS riskini azaltır. Eğer backend cookie ile set ediyorsa client sadece server çağrısıyla refresh yapar (cookie otomatik gönderilir). +- Eğer backend refresh token'ı client tarafında bekliyorsa (ör. JSON response ile gönderip client saklıyorsa), yalnızca sessionStorage (veya yine tercihen HttpOnly cookie) kullanılmalı; localStorage önerilmez. +- Refresh token rotation ve revocation: backend destekliyorsa rotasyonu kullan; logout veya refresh hatasında token'ı serverda iptal et. + +Beklenen Endpoint'ler (uygulama tarafının kullanacağı) +- POST /api/v1/auth/login + - Body: { email, password } + - Response (örnek): { access_token, refresh_token (opsiyonel if cookie), user, expires_in } +- POST /api/v1/auth/refresh + - Use-case: access token yenileme. Eğer refresh token cookie ile saklanıyorsa body boş olabilir; aksi halde { refresh_token } gönderilebilir. + - Response: { access_token, refresh_token (rotated), expires_in } +- POST /api/v1/auth/logout + - Use-case: refresh token'ı server tarafında invalidasyon + client temizleme. + +Refresh Token Akışı — Kabul Kriterleri ve Davranış +1. Login akışı + - Kullanıcı /login formunu doldurur. + - POST /auth/login çağrısı yapılır. + - Başarılıysa: + - Access token uygulama state'ine (Pinia memory) veya güvenli saklama yerine kısa süreli tutulacak şekilde konur. + - Refresh token: + - Tercih A (öncelikli, tavsiye): backend Set-Cookie ile HttpOnly, Secure cookie olarak gönderir. Client hiçbir şekilde refresh token'ı JS üzerinden okumaz veya yazmaz. + - Tercih B (fallback): backend refresh token'ı JSON içinde dönerse client token'ı sessionStorage'e veya (remember me seçeneği varsa) localStorage'e koyar ve riskler README'de belirtilir. + - Kullanıcı /admin'e yönlendirilir. + +2. Otomatik yenileme (interceptor + single-refresh lock) + - Tüm korumalı isteklerde Authorization: Bearer header kullanılır. + - Bir istek 401 dönerse ve hata sebebi token expiration ise: + - HTTP client (axios/fetch wrapper) bir *refresh attempt* başlatır: + - Eğer başka bir refresh devam ediyorsa yeni istek bekletilir (queue) — bu, aynı anda birden çok refresh çağrısının yapılmasını engeller. + - Refresh başarılı ise yeni access token (ve varsa yeni refresh token) saklanır; bekleyen istekler yeniden denenir (replay). + - Refresh başarısız ise (refresh expired veya invalid) -> authStore.logout() çağrılır ve kullanıcı /login'e yönlendirilir. + - İstek interceptor'ı 401'lerde doğrudan logout yerine önce refresh denemeli; refresh'in başarısız olması durumunda logout yapılmalı. + +3. Proaktif yenileme (optional, önerilen UX) + - Access token expiry (exp) okunarak (token JWT ise), expiry zamanından örn. 60-120s önce otomatik yenileme tetiklenebilir. + - Bu, kullanıcı etkileşimi sırasında beklenmedik logout'ları azaltır. + - Proaktif yenileme, aynı queue/lock mekanizmasını kullanarak aynı anda birden fazla refresh'i engeller. + +4. Logout / revocation + - logout() çağrıldığında client: + - Eğer refresh cookie ise: POST /auth/logout çağrısı yaparak server tarafında refresh token iptal edilir ve server cookie'yi temizler. + - Client state'teki access token ve user bilgileri temizlenir; local/session storage temizlenir. + - Kullanıcı /login'e yönlendirilir. + +5. Refresh token rotation & güvenlik + - Eğer backend rotation destekliyorsa (refresh token her yenilemede değişiyorsa), client her refresh response'unda yeni refresh token'ı saklamalı (ve/veya server Set-Cookie ile güncelleyip JS erişim izni vermemeli). + - Tek kullanımlık (one-time) refresh token kullanımı varsa backend başarısız veya yeniden kullanım durumunda tüm oturumları iptal etmeli. + - HTTP cookie attributeleri (production): + - HttpOnly + - Secure + - SameSite=Lax (veya Strict, ihtiyaçlara göre) + - Path=/api/v1/auth/refresh (gerekirse) veya Path=/ + - Domain ihtiyaca göre ayarlanmalı + +Client tarafında uygulama tasarımı (yapılacaklar — geliştiriciye talimat) +- Auth store (Pinia) içinde: + - state: accessToken (memory), user, loading, refreshPromise / refreshLock (internal) + - getters: isAuthenticated + - actions: login(), logout(), setAccessToken(), tryRefresh() + - tryRefresh(): refresh endpoint'ine istek atar; başarılıysa accessToken güncellenir; başarısızsa reject -> logout. +- HTTP client wrapper: + - baseURL: VITE_API_BASE_URL + - request interceptor: access token mevcutsa Authorization header ekle + - response interceptor: + - 401 alındığında: + - Eğer hata tipi token expired ise tryRefresh() çağrılır. + - tryRefresh() başarılı ise orijinal istek yeniden gönderilir. + - Eğer tryRefresh() başarısızsa logout() ve /login yönlendirme. + - Her isteğin retry mekanizması maksimum 1 refresh attempt ile sınırlı olmalı (sonsuz döngü olmaması için). + - Concurrent refresh yönetimi: single refresh promise pattern (ilk refresh çağrısı bir Promise döndürür; diğer istekler bu promise'i await eder). +- Proaktif yenileme (opsiyonel ama önerilen): + - Access token decode edilerek exp timestamp alınır. + - exp - now <= threshold (örn. 60s) olduğunda tryRefresh() çağrılır. + - Bu mekanizma kullanıcı etkileşiminde veya route değişimlerinde tetiklenebilir. +- Storage politikası: + - access token: bellek (Pinia) veya short-lived storage + - refresh token: HttpOnly cookie (tercih) veya sessionStorage/secure storage fallback + - README'de hangi seçeneğin neden seçildiği açıkça dokümante edilecek. + +Hata Yönetimi & UX +- Refresh başarısız olursa kullanıcıya basit, net bir mesaj göster: "Oturum süresi doldu, lütfen tekrar giriş yapın." +- Arka arkaya çalışan isteklerde refresh sırasında spinner veya global loading state kullanılabilir. +- Eğer backend 429 (rate limit) veya 5xx dönerse kullanıcı uygun uyarı almalı (tekrar dene vs). + +Test Senaryoları (kritik) +1. Başarılı login: + - login -> access token alındı, refresh token cookie ile set edildi (veya sessionStorage'e kondu) -> /admin'e yönlendirme. +2. Access token expire iken istek: + - İlk korumalı istek 401 döner -> tryRefresh() tetiklenir -> refresh başarılı -> orijinal istek tekrar çalışır -> kullanıcı etkileşimi kesintisiz devam eder. +3. Refresh expired veya invalid: + - Refresh attempt başarısız -> logout -> /login ve uygun hata mesajı. +4. Concurrent istekler token expired durumda: + - Birden fazla istek gönderildiğinde yalnızca bir refresh çağrısı yapılmalı, diğerleri bekletilip ardından yeniden denenmeli. +5. Logout: + - logout çağrıldığında server /auth/logout çağrılır (varsa) ve client state & storageler temizlenir. +6. Proaktif yenileme: + - Token exp < threshold iken uygulama otomatik yenileme yapıyor ve kullanıcıyı kesintiye uğratmıyor. + +Dokümantasyon ve README Notları +- .env örneği: VITE_API_BASE_URL=http://localhost:8080/api/v1 +- Token saklama tercihleri ve güvenlik nedenleri (HttpOnly cookie vs localStorage) açıkça yazılmalı. +- Backend ile cookie tabanlı refresh kullanılıyorsa CORS ve credentials ayarları açıklanmalı (axios: withCredentials=true; server: Access-Control-Allow-Credentials: true). +- Güvenlik önerileri: refresh token'ların server-side revocation tablosunda saklanması, rotation kullanımı ve refresh token reuse detection. + +Teslim Edilecekler (geliştiriciye verilecek) +- Güncellenmiş prompt (bu dosya) — refresh token mantığı, beklenen endpoint'ler, client davranışı ve test senaryoları açık. +- Uygulamada olması gerekenler: + - login sayfası + - Pinia auth store (refresh logic ile) + - HTTP client wrapper (interceptor + refresh queue/lock) + - Router guard (protected rotalar) + - Dashboard iskeleti ve en az bir örnek protected endpoint kullanımı + - README: environment, çalışma, güvenlik ve token saklama tercihleri + +Notlar / Varsayımlar +- Backend, refresh endpoint ve/veya Set-Cookie davranışını destekliyor olmalıdır. Eğer backend cookie yerine JSON refresh token dönerse prompt'ta belirtilen fallback yöntem uygulanacak. +- Exact refresh endpoint path'ı backend ile netleştirilmeli (`/auth/refresh`, `/auth/refresh-token` veya benzeri). +- Backend CORS + cookie kullanımında `Access-Control-Allow-Credentials: true` ve client çağrılarında `withCredentials: true` gerekecektir. + +--- + +## ✅ İMPLEMENTASYON TAMAMLANDI (12 Şubat 2026) + +Yukarıdaki tüm gereksinimler başarıyla implement edildi. İşte teslim edilen bileşenler: + +### Oluşturulan Dosyalar + +#### Environment & Yapılandırma +- `.env` - Environment variables +- `.env.example` - Environment template +- `tsconfig.app.json` - TypeScript path alias yapılandırması + +#### Core Infrastructure +- `src/lib/http-client.ts` - Axios wrapper + interceptors (401 handling, refresh logic, retry) +- `src/services/auth.service.ts` - Auth API endpoints (login, refresh, logout) +- `src/types/auth.types.ts` - TypeScript interfaces + +#### State Management +- `src/stores/auth.ts` - Pinia auth store + - Login/logout actions + - Refresh token logic (single promise lock) + - Proaktif token yenileme (exp check) + - Concurrent refresh prevention + +#### UI Components +- `src/components/ui/Button.vue` - Reusable button (variants, loading, sizes) +- `src/components/ui/Input.vue` - Reusable input (validation, error states) + +#### Views +- `src/views/LoginView.vue` - Modern login sayfası (Zod validation, responsive, dark mode) +- `src/views/DashboardView.vue` - Admin dashboard (user info, stats, protected content) + +#### Router & Navigation +- `src/router/index.ts` - Router configuration + navigation guards + - `/login` (public) + - `/admin` (protected, requires auth) + +#### App Structure +- `src/App.vue` - Minimal layout (sadece RouterView) + +#### Dokümantasyon +- `README.md` - Detaylı dokümantasyon + - Token saklama stratejisi + - CORS yapılandırması + - Auth akışı diagramları + - Backend endpoint gereksinimleri + - Güvenlik best practices + +### Teknolojiler +- Vue 3 (Composition API) +- TypeScript +- Pinia (state management) +- Vue Router +- Axios +- Zod (validation) +- Tailwind CSS +- Lucide Icons +- SweetAlert2 +- jwt-decode + +### Güvenlik Özellikleri +✅ Access token bellek (Pinia) depolaması +✅ HttpOnly cookie desteği (refresh token) +✅ withCredentials: true (CORS) +✅ Single refresh promise lock (concurrent prevention) +✅ Proaktif token yenileme (60s threshold) +✅ Router guards (protected routes) +✅ Form validation (Zod) +✅ XSS koruması + +### Test Senaryoları (Hazır) +1. ✅ Başarılı login akışı +2. ✅ Access token expiration & refresh +3. ✅ Refresh token invalid/expired handling +4. ✅ Concurrent requests (single refresh) +5. ✅ Logout flow +6. ✅ Protected route guards +7. ✅ Proaktif yenileme + +**Backend Hazırlığı**: Frontend tamamlandı, backend endpoint'leri (`/auth/login`, `/auth/refresh`, `/auth/logout`) hazır olduğunda test edilebilir. + +**Çalıştırma**: `npm run dev` komutu ile başlatılabilir. \ No newline at end of file diff --git a/api_documentation.md b/api_documentation.md new file mode 100644 index 0000000..900d652 --- /dev/null +++ b/api_documentation.md @@ -0,0 +1,794 @@ +# 🚀 Backend API Dokümantasyonu + +## 📋 İçindekiler + +- [Genel Bilgiler](#genel-bilgiler) +- [Swagger UI](#swagger-ui) +- [Authentication](#authentication) +- [Response Formatı](#response-formatı) +- [Hata Kodları](#hata-kodları) +- [Modüler Yapı](#modüler-yapı) +- [Endpoint'ler](#endpointler) + - [Blog App](#blog-app) + - [Account App](#account-app) + - [Settings App](#settings-app) + +--- + +## Genel Bilgiler + +### Base URL +``` +http://localhost:8080/api/v1 +``` + +### Content Type +``` +Content-Type: application/json +``` + +### Modüler Yapı +API, **app-based modüler yapı** kullanır: + +| App | Sorumluluk | Endpoint Prefix | +|-----|------------|-----------------| +| **Blog** | Kategori, Tag, Post, Yorum | `/api/v1/categories`, `/api/v1/posts` | +| **Account** | Kullanıcı, Rol, İzin | `/api/v1/admin/users`, `/api/v1/admin/roles` | +| **Settings** | CORS, Rate Limit | `/api/v1/admin/cors`, `/api/v1/admin/rate-limits` | + +--- + +## Swagger UI + +### Swagger Dokümantasyonu + +API'nin interaktif dokümantasyonuna Swagger UI üzerinden erişebilirsiniz: + +``` +http://localhost:8080/swagger/index.html +``` + +### Swagger Generate + +Swagger dokümantasyonunu güncellemek için: + +```bash +# Swagger docs oluştur +swag init + +# Veya make komutu ile (varsa) +make swagger +``` + +### Swagger Annotation Örneği + +Handler'larda godoc annotation'lar kullanılır: + +```go +// GetAllCategories godoc +// @Summary Get all active categories +// @Description Get list of all active categories +// @Tags blog,categories +// @Accept json +// @Produce json +// @Success 200 {array} models.Category +// @Router /api/v1/categories [get] +func (h *CategoryHandler) GetAllCategories(c *gin.Context) { + // ... +} +``` + +--- + +## Authentication + +### Kimlik Doğrulama + +Admin ve korumalı endpoint'ler için JWT token: + +```http +Authorization: Bearer +``` + +### Erişim Seviyeleri + +| Seviye | Açıklama | Örnek | +|--------|----------|-------| +| 🌍 **Public** | Kimlik doğrulama gerektirmez | `GET /api/v1/posts` | +| 🔓 **Auth** | Login olmuş kullanıcı | `POST /api/v1/user/posts/:id/comments` | +| 🔐 **Admin** | Admin rolü gerekli | `POST /api/v1/admin/categories` | + +--- + +## Response Formatı + +### Başarılı Response + +#### Tekil Kayıt +```json +{ + "data": { + "id": 1, + "title": "Örnek" + } +} +``` + +#### Liste (Pagination ile) +```json +{ + "data": [...], + "total": 100, + "page": 1, + "limit": 10 +} +``` + +### Hata Response + +```json +{ + "error": "Resource not found" +} +``` + +--- + +## Hata Kodları + +| HTTP Status | Açıklama | +|-------------|----------| +| **200** | Başarılı | +| **201** | Oluşturuldu | +| **400** | Hatalı Request | +| **401** | Yetkisiz | +| **403** | Yasak | +| **404** | Bulunamadı | +| **500** | Sunucu Hatası | + +--- + +## Modüler Yapı + +### Kod Organizasyonu + +``` +app/ +├── blog/ → Blog işlevleri +│ ├── handlers/ +│ └── services/ +├── account/ → Kullanıcı yönetimi +│ ├── handlers/ +│ └── services/ +└── settings/ → Sistem ayarları + ├── handlers/ + └── services/ +``` + +### Import Path'leri + +```go +import ( + blogHandlers "gobeyhan/app/blog/handlers" + blogServices "gobeyhan/app/blog/services" + accountHandlers "gobeyhan/app/account/handlers" + accountServices "gobeyhan/app/account/services" +) +``` + +--- + +## Endpoint'ler + +## Blog App + +Blog uygulaması 5 ana kaynak içerir: Categories, Tags, Posts, Comments, CategoryViews + +### 📌 Categories + +#### Tüm Kategoriler (Public) + +```http +GET /api/v1/categories +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "title": "Teknoloji", + "slug": "teknoloji", + "parent": null, + "children": [...] + } + ] +} +``` + +#### Kategori Detay (Public) + +```http +GET /api/v1/categories/:slug +``` + +#### Kategori Görüntülenme (Public) + +```http +POST /api/v1/categories/:id/view +``` + +#### Admin: Kategori CRUD + +```http +GET /api/v1/admin/categories +GET /api/v1/admin/categories/:id +POST /api/v1/admin/categories +PUT /api/v1/admin/categories/:id +DELETE /api/v1/admin/categories/:id +GET /api/v1/admin/categories/:id/views +``` + +**Create Request:** +```json +{ + "title": "Yeni Kategori", + "slug": "yeni-kategori", + "description": "Açıklama", + "is_active": true, + "order": 1, + "parent_id": null +} +``` + +--- + +### 📌 Tags + +#### Tüm Etiketler (Public) + +```http +GET /api/v1/tags +``` + +#### Etiket Detay (Public) + +```http +GET /api/v1/tags/:slug +``` + +#### Admin: Etiket CRUD + +```http +GET /api/v1/admin/tags +GET /api/v1/admin/tags/:id +POST /api/v1/admin/tags +PUT /api/v1/admin/tags/:id +DELETE /api/v1/admin/tags/:id +``` + +**Create Request:** +```json +{ + "tag": "golang", + "slug": "golang", + "is_active": true +} +``` + +--- + +### 📌 Posts + +#### Tüm Yazılar (Public, Paginated) + +```http +GET /api/v1/posts?page=1&limit=10 +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "title": "Blog Yazısı", + "slug": "blog-yazisi", + "user": {...}, + "categories": [...], + "tags": [...] + } + ], + "total": 100, + "page": 1, + "limit": 10 +} +``` + +#### Yazı Detay (Public) + +```http +GET /api/v1/posts/:slug +``` + +#### Admin: Yazı CRUD + +```http +GET /api/v1/admin/posts +GET /api/v1/admin/posts/:id +POST /api/v1/admin/posts +PUT /api/v1/admin/posts/:id +DELETE /api/v1/admin/posts/:id +``` + +**Create Request:** +```json +{ + "title": "Yeni Yazı", + "content": "İçerik...", + "categories": [1, 2], + "tags": [1, 3, 5], + "is_active": true +} +``` + +--- + +### 📌 Comments + +#### Yazı Yorumları (Public) + +```http +GET /api/v1/posts/:id/comments +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "user_id": 2, + "body": "Yorum içeriği", + "parent_id": null, + "children": [...] + } + ] +} +``` + +#### Yorum Yaz (Auth) + +```http +POST /api/v1/user/posts/:id/comments +Authorization: Bearer +``` + +**Request:** +```json +{ + "body": "Yorum içeriği", + "parent_id": null +} +``` + +#### Admin: Yorum Yönetimi + +```http +GET /api/v1/admin/comments +GET /api/v1/admin/comments/:id +PUT /api/v1/admin/comments/:id +DELETE /api/v1/admin/comments/:id +``` + +--- + +## Account App + +Kullanıcı, rol ve izin yönetimi + +### 📌 Users + +#### Admin: Tüm Kullanıcılar + +```http +GET /api/v1/admin/users?page=1&limit=10&include_deleted=false +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "username": "admin", + "email": "admin@example.com", + "roles": [...], + "social_accounts": [...], + "deleted_at": null + } + ], + "total": 50 +} +``` + +#### Admin: Kullanıcı CRUD + +```http +GET /api/v1/admin/users/:id +POST /api/v1/admin/users +PUT /api/v1/admin/users/:id +DELETE /api/v1/admin/users/:id # Soft delete +POST /api/v1/admin/users/:id/restore +``` + +**Create Request:** +```json +{ + "email": "user@example.com", + "password": "securePass123", + "username": "newuser" +} +``` + +> **Not**: Password bcrypt ile hashlenip saklanır + +#### Admin: Rol Yönetimi + +```http +POST /api/v1/admin/users/:id/roles +DELETE /api/v1/admin/users/:id/roles/:role_id +``` + +**Assign Role:** +```json +{ + "role_id": 2 +} +``` + +--- + +### 📌 Roles + +#### Admin: Roller + +```http +GET /api/v1/admin/roles +GET /api/v1/admin/roles/:id +POST /api/v1/admin/roles +PUT /api/v1/admin/roles/:id +DELETE /api/v1/admin/roles/:id +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "name": "admin", + "description": "Administrator", + "permissions": [...] + } + ] +} +``` + +**Create Request:** +```json +{ + "name": "moderator", + "description": "Moderator role" +} +``` + +--- + +### 📌 Permissions + +#### Admin: İzinler + +```http +GET /api/v1/admin/permissions +POST /api/v1/admin/permissions +``` + +**Create Request:** +```json +{ + "name": "edit_comment", + "description": "Can edit comments" +} +``` + +--- + +### 📌 Social Accounts + +#### Auth: Kullanıcının Social Hesapları + +```http +GET /api/v1/user/social-accounts +DELETE /api/v1/user/social-accounts/:id +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "provider": "google", + "provider_user_id": "123456" + } + ] +} +``` + +--- + +## Settings App + +Sistem ayarları ve yapılandırma + +### 📌 CORS Whitelist + +#### Admin: CORS Whitelist + +```http +GET /api/v1/admin/cors/whitelist +POST /api/v1/admin/cors/whitelist +PUT /api/v1/admin/cors/whitelist/:id +DELETE /api/v1/admin/cors/whitelist/:id +``` + +**Create Request:** +```json +{ + "origin": "https://example.com", + "description": "Main website" +} +``` + +--- + +### 📌 CORS Blacklist + +```http +GET /api/v1/admin/cors/blacklist +POST /api/v1/admin/cors/blacklist +PUT /api/v1/admin/cors/blacklist/:id +DELETE /api/v1/admin/cors/blacklist/:id +``` + +--- + +### 📌 Rate Limits + +#### Admin: Rate Limit Ayarları + +```http +GET /api/v1/admin/rate-limits +PUT /api/v1/admin/rate-limits/:id +``` + +**Response:** +```json +{ + "data": [ + { + "id": 1, + "path": "/api/v1/posts", + "max_requests": 100, + "window_seconds": 60 + } + ] +} +``` + +**Update Request:** +```json +{ + "max_requests": 200, + "window_seconds": 120 +} +``` + +--- + +### 📌 CORS Cache + +#### Admin: Cache Temizle + +```http +POST /api/v1/admin/cors/cache/invalidate +``` + +--- + +## Endpoint Özeti + +### Blog App (29 endpoint) + +| Kaynak | Public | Auth | Admin | +|--------|--------|------|-------| +| Categories | 3 | 0 | 6 | +| Tags | 2 | 0 | 5 | +| Posts | 2 | 0 | 5 | +| Comments | 1 | 1 | 4 | +| CategoryViews | 1 | 0 | 1 | + +### Account App (17 endpoint) + +| Kaynak | Public | Auth | Admin | +|--------|--------|------|-------| +| Users | 0 | 0 | 8 | +| Roles | 0 | 0 | 5 | +| Permissions | 0 | 0 | 2 | +| Social Accounts | 0 | 2 | 0 | + +### Settings App (11 endpoint) + +| Kaynak | Public | Auth | Admin | +|--------|--------|------|-------| +| CORS Whitelist | 0 | 0 | 4 | +| CORS Blacklist | 0 | 0 | 4 | +| Rate Limits | 0 | 0 | 2 | +| Cache | 0 | 0 | 1 | + +**TOPLAM: 57 endpoint** + +--- + +## cURL Örnekleri + +### Public Endpoint +```bash +curl http://localhost:8080/api/v1/posts?page=1&limit=5 +``` + +### Admin Endpoint +```bash +curl -X POST http://localhost:8080/api/v1/admin/categories \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"title": "Test"}' +``` + +### Authentication Endpoints + +### Register +**POST** `/api/v1/auth/register` +Create a new user account. + +**Request Body:** +```json +{ + "email": "user@example.com", + "password": "securePass123", + "username": "johndoe" +} +``` + +**Response:** +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": 1, + "email": "user@example.com", + "username": "johndoe", + "avatar": "", + "created_at": "2024-03-20T10:00:00Z" + } +} +``` + +### Login +**POST** `/api/v1/auth/login` +Authenticate user and get JWT token. + +**Request Body:** +```json +{ + "email": "user@example.com", + "password": "securePass123" +} +``` + +**Response:** +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { ... } +} +``` + +### OAuth Login (Google) +**GET** `/api/v1/auth/google` +Redirects to Google OAuth login page. + +**Callback:** `/api/v1/auth/google/callback` +Exchanges code for token and returns JWT + User. + +### OAuth Login (GitHub) +**GET** `/api/v1/auth/github` +Redirects to GitHub OAuth login page. + +**Callback:** `/api/v1/auth/github/callback` +Exchanges code for token and returns JWT + User. + +### Logout +**POST** `/api/v1/auth/logout` +Logout user (client-side token removal recommended). + +--- + +## Blog Endpoints +```bash +curl -X POST http://localhost:8080/api/v1/user/posts/1/comments \ + -H "Authorization: Bearer YOUR_TOKEN" \ + -d '{"body": "Great post!"}' +``` + +--- + +## Postman Collection + +Environment variables: +```json +{ + "base_url": "http://localhost:8080/api/v1", + "admin_token": "eyJhbGc...", + "user_token": "eyJhbGc..." +} +``` + +--- + +## Middleware Notları + +Auth ve Admin middleware'ler şu anda yorumda: + +**Aktif etmek için** [routes.go](file:///Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/routes/routes.go): + +```go +// Auth routes +user := api.Group("/user") +user.Use(AuthMiddleware()) + +// Admin routes +admin := api.Group("/admin") +admin.Use(AuthMiddleware(), AdminMiddleware()) +``` + +--- + +## Swagger Entegrasyonu + +### Kurulum + +```bash +go get -u github.com/swaggo/swag/cmd/swag +go get -u github.com/swaggo/gin-swagger +go get -u github.com/swaggo/files +``` + +### Generate + +```bash +swag init +``` + +### Erişim + +``` +http://localhost:8080/swagger/index.html +``` + +--- + +**Last Updated:** 2026-02-11 +**Version:** 2.0 (Modular Structure) diff --git a/app/account/handlers/auth_handler.go b/app/account/handlers/auth_handler.go new file mode 100644 index 0000000..b5ee7a5 --- /dev/null +++ b/app/account/handlers/auth_handler.go @@ -0,0 +1,361 @@ +package handlers + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + + "gobeyhan/app/account/services" + settingsServices "gobeyhan/app/settings/services" + "gobeyhan/config" + "gobeyhan/database/models" + + "github.com/gin-gonic/gin" +) + +type AuthHandler struct { + userService *services.UserService + jwtService *settingsServices.JWTService +} + +func NewAuthHandler(userService *services.UserService, jwtService *settingsServices.JWTService) *AuthHandler { + return &AuthHandler{ + userService: userService, + jwtService: jwtService, + } +} + +// Register godoc +// @Summary Register a new user +// @Description Create a new user account with email and password +// @Tags auth +// @Accept json +// @Produce json +// @Param request body object{email=string,password=string,username=string} true "Registration data" +// @Success 201 {object} object{token=string,user=models.User} +// @Failure 400 {object} object{error=string} +// @Router /api/v1/auth/register [post] +func (h *AuthHandler) Register(c *gin.Context) { + var input struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required,min=6"` + Username string `json:"username" binding:"required,min=3"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Create user (password will be hashed by UserService) + user := &models.User{ + Email: input.Email, + UserName: input.Username, + } + + // Password is passed separately to be hashed + if err := h.userService.CreateUser(user, input.Password); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Assign default role + if err := h.userService.AssignDefaultRole(user.ID); err != nil { + // Log error but don't fail registration + // log.Printf("Failed to assign default role: %v", err) + } + + // Generate JWT tokens + accessToken, refreshToken, err := h.jwtService.GenerateTokenPair(*user) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate tokens"}) + return + } + + // Return tokens and user (without password) + user.Password = "" + c.JSON(http.StatusCreated, gin.H{ + "access_token": accessToken, + "refresh_token": refreshToken, + "user": user, + }) +} + +// Login godoc +// @Summary Login user +// @Description Login with email and password +// @Tags auth +// @Accept json +// @Produce json +// @Param request body object{email=string,password=string} true "Login credentials" +// @Success 200 {object} object{token=string,user=models.User} +// @Failure 400 {object} object{error=string} +// @Failure 401 {object} object{error=string} +// @Router /api/v1/auth/login [post] +func (h *AuthHandler) Login(c *gin.Context) { + var input struct { + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + TurnstileToken string `json:"turnstile_token"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Verify Turnstile + if !verifyTurnstile(input.TurnstileToken) { + c.JSON(http.StatusBadRequest, gin.H{"error": "Turnstile doğrulaması başarısız"}) + return + } + + // Get user by email + user, err := h.userService.GetUserByEmail(input.Email) + if err != nil || user == nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + // Verify password + if !h.userService.VerifyPassword(user.Password, input.Password) { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid credentials"}) + return + } + + // Generate JWT tokens + accessToken, refreshToken, err := h.jwtService.GenerateTokenPair(*user) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate tokens"}) + return + } + + // Set refresh token as HttpOnly cookie (secure, XSS-safe) + cookie := &http.Cookie{ + Name: "refresh_token", + Value: refreshToken, + Path: "/", + Domain: "localhost", // Explicitly set for local dev + MaxAge: 7 * 24 * 60 * 60, // 7 days + Secure: false, // Set true for HTTPS in production + HttpOnly: true, // Cannot be accessed by JavaScript + SameSite: http.SameSiteLaxMode, // Lax is better for local dev + } + http.SetCookie(c.Writer, cookie) + fmt.Printf("[DEBUG] Login - Set-Cookie Raw: %s\n", cookie.String()) + + // Return tokens and user (without password) + user.Password = "" + c.JSON(http.StatusOK, gin.H{ + "access_token": accessToken, + "refresh_token": refreshToken, // Also in response for fallback + "user": user, + }) +} + +// GetCurrentUser godoc +// @Summary Get current user +// @Description Get current authenticated user information +// @Tags auth +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} models.User +// @Failure 401 {object} object{error=string} +// @Router /api/v1/auth/me [get] +func (h *AuthHandler) GetCurrentUser(c *gin.Context) { + // Get user ID from context (set by auth middleware) + userIDStr, exists := c.Get("user_id") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + return + } + + // Convert to uint64 + var userID uint64 + switch v := userIDStr.(type) { + case string: + parsed, _ := strconv.ParseUint(v, 10, 64) + userID = parsed + case uint64: + userID = v + case int: + userID = uint64(v) + case float64: + userID = uint64(v) + } + + // Get user from database + user, err := h.userService.GetUserByID(userID) + if err != nil || user == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + // Return user (without password) + user.Password = "" + c.JSON(http.StatusOK, user) +} + +// Logout godoc +// @Summary Logout user +// @Description Logout (client-side token removal) +// @Tags auth +// @Accept json +// @Produce json +// @Success 200 {object} object{message=string} +// @Router /api/v1/auth/logout [post] +func (h *AuthHandler) Logout(c *gin.Context) { + // Clear refresh token cookie + cookie := &http.Cookie{ + Name: "refresh_token", + Value: "", + Path: "/", + Domain: "localhost", + MaxAge: -1, // Delete cookie + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + } + http.SetCookie(c.Writer, cookie) + fmt.Printf("[DEBUG] Logout - Set-Cookie Raw: %s\n", cookie.String()) + + // For JWT, logout is typically handled client-side + // Server can implement token blacklisting if needed + c.JSON(http.StatusOK, gin.H{"message": "Logged out successfully"}) +} + +// RefreshToken godoc +// @Summary Refresh access token +// @Description Get a new access token using refresh token from HttpOnly cookie +// @Tags auth +// @Accept json +// @Produce json +// @Success 200 {object} object{access_token=string,refresh_token=string} +// @Failure 401 {object} object{error=string} +// @Router /api/v1/auth/refresh [post] +func (h *AuthHandler) RefreshToken(c *gin.Context) { + // Get refresh token from HttpOnly cookie + refreshToken, err := c.Cookie("refresh_token") + if err != nil { + fmt.Printf("[DEBUG] RefreshToken - Cookie Error: %v\n", err) + } else { + fmt.Printf("[DEBUG] RefreshToken - Cookie Found: %s...\n", refreshToken[:10]) + } + + if err != nil || refreshToken == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Refresh token not found"}) + return + } + + // Validate refresh token and get user ID + claims, err := h.jwtService.ValidateToken(refreshToken) + if err != nil { + fmt.Printf("[DEBUG] RefreshToken - ValidateToken Error: %v\n", err) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired refresh token"}) + return + } + + // Get user ID from claims + userID, err := claims.GetSubject() + if err != nil { + fmt.Printf("[DEBUG] RefreshToken - GetSubject Error: %v\n", err) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token claims"}) + return + } + + // Parse user ID to uint64 + // Sscan bazen boşluk vs. yüzünden hata verebilir, strconv daha güvenli + parsedUint, err := strconv.ParseUint(userID, 10, 64) + if err != nil { + fmt.Printf("[DEBUG] RefreshToken - ParseUint Error: %v (userID=%s)\n", err, userID) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid user ID format"}) + return + } + uid := parsedUint + + // Get user from database + user, err := h.userService.GetUserByID(uid) + if err != nil { + fmt.Printf("[DEBUG] RefreshToken - GetUserByID FAILED: %v\n", err) + c.JSON(http.StatusUnauthorized, gin.H{"error": "Database error"}) + return + } + if user == nil { + fmt.Printf("[DEBUG] RefreshToken - User not found in DB for ID: %d\n", uid) + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + return + } + fmt.Printf("[DEBUG] RefreshToken - User found: %s\n", user.Email) + + // Generate new token pair + newAccessToken, newRefreshToken, err := h.jwtService.GenerateTokenPair(*user) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to generate tokens"}) + return + } + + // Update refresh token cookie (token rotation) + cookie := &http.Cookie{ + Name: "refresh_token", + Value: newRefreshToken, + Path: "/", + Domain: "localhost", + MaxAge: 7 * 24 * 60 * 60, // 7 days + Secure: false, + HttpOnly: true, + SameSite: http.SameSiteLaxMode, + } + http.SetCookie(c.Writer, cookie) + + // Return new access token and user info + user.Password = "" // Ensure password is not sent + c.JSON(http.StatusOK, gin.H{ + "access_token": newAccessToken, + "refresh_token": newRefreshToken, + "user": user, // Critical for frontend session restore + }) +} + +// Helper: Verify Turnstile Token +func verifyTurnstile(token string) bool { + secret := config.AppConfig.TurnstileSecretKey + if secret == "" { + fmt.Println("[WARNING] Turnstile Secret Key not configured, skipping validation") + return true // Skip validation if not configured + } + if token == "" { + // If secret is configured, token is mandatory + return false + } + + formData := url.Values{ + "secret": {secret}, + "response": {token}, + } + + resp, err := http.PostForm("https://challenges.cloudflare.com/turnstile/v0/siteverify", formData) + if err != nil { + fmt.Printf("[ERROR] Turnstile request failed: %v\n", err) + return false + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + fmt.Printf("[ERROR] Failed to read Turnstile response: %v\n", err) + return false + } + + var result struct { + Success bool `json:"success"` + } + if err := json.Unmarshal(body, &result); err != nil { + fmt.Printf("[ERROR] Failed to parse Turnstile response: %v\n", err) + return false + } + + return result.Success +} diff --git a/app/account/handlers/oauth_handler.go b/app/account/handlers/oauth_handler.go new file mode 100644 index 0000000..6c388c3 --- /dev/null +++ b/app/account/handlers/oauth_handler.go @@ -0,0 +1,297 @@ +package handlers + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strconv" + + "gobeyhan/app/account/services" + settingsServices "gobeyhan/app/settings/services" + "gobeyhan/config" + "gobeyhan/database/models" + + "github.com/gin-gonic/gin" + "golang.org/x/oauth2" + "golang.org/x/oauth2/github" + "golang.org/x/oauth2/google" +) + +type OAuthHandler struct { + userService *services.UserService + socialAccountService *services.SocialAccountService + jwtService *settingsServices.JWTService + googleOAuthConfig *oauth2.Config + githubOAuthConfig *oauth2.Config +} + +func NewOAuthHandler( + userService *services.UserService, + socialAccountService *services.SocialAccountService, + jwtService *settingsServices.JWTService, +) *OAuthHandler { + // Google OAuth config + googleConfig := &oauth2.Config{ + ClientID: config.AppConfig.GoogleClientID, + ClientSecret: config.AppConfig.GoogleClientSecret, + RedirectURL: config.AppConfig.GoogleRedirectURL, + Scopes: []string{ + "https://www.googleapis.com/auth/userinfo.email", + "https://www.googleapis.com/auth/userinfo.profile", + }, + Endpoint: google.Endpoint, + } + + // GitHub OAuth config + githubConfig := &oauth2.Config{ + ClientID: config.AppConfig.GithubClientID, + ClientSecret: config.AppConfig.GithubClientSecret, + RedirectURL: config.AppConfig.GithubRedirectURL, + Scopes: []string{"user:email"}, + Endpoint: github.Endpoint, + } + + return &OAuthHandler{ + userService: userService, + socialAccountService: socialAccountService, + jwtService: jwtService, + googleOAuthConfig: googleConfig, + githubOAuthConfig: githubConfig, + } +} + +// GoogleLogin godoc +// @Summary Google OAuth login +// @Description Redirect to Google OAuth +// @Tags auth,oauth +// @Produce json +// @Router /api/v1/auth/google [get] +func (h *OAuthHandler) GoogleLogin(c *gin.Context) { + url := h.googleOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline) + c.Redirect(http.StatusTemporaryRedirect, url) +} + +// GoogleCallback godoc +// @Summary Google OAuth callback +// @Description Handle Google OAuth callback +// @Tags auth,oauth +// @Produce json +// @Param code query string true "Authorization code" +// @Success 200 {object} object{token=string,user=models.User} +// @Failure 400 {object} object{error=string} +// @Router /api/v1/auth/google/callback [get] +func (h *OAuthHandler) GoogleCallback(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"}) + return + } + + // Exchange code for token + token, err := h.googleOAuthConfig.Exchange(context.Background(), code) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"}) + return + } + + // Get user info from Google + client := h.googleOAuthConfig.Client(context.Background(), token) + resp, err := client.Get("https://www.googleapis.com/oauth2/v2/userinfo") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info"}) + return + } + defer resp.Body.Close() + + data, _ := io.ReadAll(resp.Body) + var googleUser struct { + ID string `json:"id"` + Email string `json:"email"` + VerifiedEmail bool `json:"verified_email"` + Name string `json:"name"` + Picture string `json:"picture"` + } + json.Unmarshal(data, &googleUser) + + // Find or create user + user, accessToken, refreshToken, err := h.findOrCreateOAuthUser( + googleUser.Email, + googleUser.Name, + "google", + googleUser.ID, + ) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "token": accessToken, + "refresh_token": refreshToken, + "user": user, + }) +} + +// GithubLogin godoc +// @Summary GitHub OAuth login +// @Description Redirect to GitHub OAuth +// @Tags auth,oauth +// @Produce json +// @Router /api/v1/auth/github [get] +func (h *OAuthHandler) GithubLogin(c *gin.Context) { + url := h.githubOAuthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline) + c.Redirect(http.StatusTemporaryRedirect, url) +} + +// GithubCallback godoc +// @Summary GitHub OAuth callback +// @Description Handle GitHub OAuth callback +// @Tags auth,oauth +// @Produce json +// @Param code query string true "Authorization code" +// @Success 200 {object} object{token=string,user=models.User} +// @Failure 400 {object} object{error=string} +// @Router /api/v1/auth/github/callback [get] +func (h *OAuthHandler) GithubCallback(c *gin.Context) { + code := c.Query("code") + if code == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Code not found"}) + return + } + + // Exchange code for token + token, err := h.githubOAuthConfig.Exchange(context.Background(), code) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to exchange token"}) + return + } + + // Get user info from GitHub + client := h.githubOAuthConfig.Client(context.Background(), token) + resp, err := client.Get("https://api.github.com/user") + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get user info"}) + return + } + defer resp.Body.Close() + + data, _ := io.ReadAll(resp.Body) + var githubUser struct { + ID int `json:"id"` + Login string `json:"login"` + Email string `json:"email"` + Name string `json:"name"` + } + json.Unmarshal(data, &githubUser) + + // If email is not public, fetch it separately + if githubUser.Email == "" { + emailResp, _ := client.Get("https://api.github.com/user/emails") + if emailResp != nil { + defer emailResp.Body.Close() + emailData, _ := io.ReadAll(emailResp.Body) + var emails []struct { + Email string `json:"email"` + Primary bool `json:"primary"` + Verified bool `json:"verified"` + } + json.Unmarshal(emailData, &emails) + for _, e := range emails { + if e.Primary && e.Verified { + githubUser.Email = e.Email + break + } + } + } + } + + username := githubUser.Name + if username == "" { + username = githubUser.Login + } + + // Find or create user + user, accessToken, refreshToken, err := h.findOrCreateOAuthUser( + githubUser.Email, + username, + "github", + strconv.Itoa(githubUser.ID), + ) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "token": accessToken, + "refresh_token": refreshToken, + "user": user, + }) +} + +// findOrCreateOAuthUser finds existing user or creates new one for OAuth +func (h *OAuthHandler) findOrCreateOAuthUser( + email, username, provider, providerUserID string, +) (*models.User, string, string, error) { + // Try to find existing user by email + user, err := h.userService.GetUserByEmail(email) + if err != nil { + return nil, "", "", err + } + + // If user doesn't exist, create new one + if user == nil { + user = &models.User{ + Email: email, + UserName: username, + } + // Create user with empty password + if err := h.userService.CreateUser(user, ""); err != nil { + return nil, "", "", fmt.Errorf("failed to create user: %w", err) + } + + // Assign default role + if err := h.userService.AssignDefaultRole(user.ID); err != nil { + // Log error but continue + // fmt.Printf("Failed to assign default role: %v\n", err) + } + } + + // Check if social account exists + accounts, err := h.socialAccountService.GetSocialAccountsByUser(user.ID) + if err != nil { + return nil, "", "", err + } + + // Create social account if it doesn't exist + found := false + for _, acc := range accounts { + if acc.Provider == provider && acc.ProviderID == providerUserID { + found = true + break + } + } + + if !found { + socialAccount := &models.SocialAccount{ + UserID: user.ID, + Provider: provider, + ProviderID: providerUserID, + } + if err := h.socialAccountService.CreateSocialAccount(socialAccount); err != nil { + return nil, "", "", fmt.Errorf("failed to create social account: %w", err) + } + } + + // Generate JWT tokens + accessToken, refreshToken, err := h.jwtService.GenerateTokenPair(*user) + if err != nil { + return nil, "", "", fmt.Errorf("failed to generate tokens: %w", err) + } + + // Clear password before returning + user.Password = "" + return user, accessToken, refreshToken, nil +} diff --git a/app/account/handlers/permission_handler.go b/app/account/handlers/permission_handler.go new file mode 100644 index 0000000..8562de3 --- /dev/null +++ b/app/account/handlers/permission_handler.go @@ -0,0 +1,70 @@ +package handlers + +import ( + "gobeyhan/app/account/services" + "gobeyhan/database/models" + "net/http" + + "github.com/gin-gonic/gin" +) + +type PermissionHandler struct { + service *services.PermissionService +} + +func NewPermissionHandler(service *services.PermissionService) *PermissionHandler { + return &PermissionHandler{service: service} +} + +// AdminGetAllPermissions godoc +// @Summary Get all permissions (Admin) +// @Description Get list of all permissions +// @Tags admin,permissions +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.Permission +// @Router /api/v1/admin/permissions [get] +func (h *PermissionHandler) AdminGetAllPermissions(c *gin.Context) { + permissions, err := h.service.GetAllPermissions() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": permissions}) +} + +// AdminCreatePermission godoc +// @Summary Create a new permission (Admin) +// @Description Create a new permission +// @Tags admin,permissions +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param permission body models.Permission true "Permission object" +// @Success 201 {object} models.Permission +// @Router /api/v1/admin/permissions [post] +func (h *PermissionHandler) AdminCreatePermission(c *gin.Context) { + var input struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + permission := &models.Permission{ + Name: input.Name, + Description: input.Description, + } + + if err := h.service.CreatePermission(permission); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": permission}) +} diff --git a/app/account/handlers/role_handler.go b/app/account/handlers/role_handler.go new file mode 100644 index 0000000..17bdc72 --- /dev/null +++ b/app/account/handlers/role_handler.go @@ -0,0 +1,169 @@ +package handlers + +import ( + "gobeyhan/app/account/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type RoleHandler struct { + service *services.RoleService +} + +func NewRoleHandler(service *services.RoleService) *RoleHandler { + return &RoleHandler{service: service} +} + +// AdminGetAllRoles godoc +// @Summary Get all roles (Admin) +// @Description Get list of all roles with permissions +// @Tags admin,roles +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.Role +// @Router /api/v1/admin/roles [get] +func (h *RoleHandler) AdminGetAllRoles(c *gin.Context) { + roles, err := h.service.GetAllRoles() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": roles}) +} + +// AdminGetRoleByID godoc +// @Summary Get role by ID (Admin) +// @Description Get a single role by ID +// @Tags admin,roles +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Role ID" +// @Success 200 {object} models.Role +// @Router /api/v1/admin/roles/{id} [get] +func (h *RoleHandler) AdminGetRoleByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role ID"}) + return + } + + role, err := h.service.GetRoleByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if role == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Role not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": role}) +} + +// AdminCreateRole godoc +// @Summary Create a new role (Admin) +// @Description Create a new role +// @Tags admin,roles +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param role body models.Role true "Role object" +// @Success 201 {object} models.Role +// @Router /api/v1/admin/roles [post] +func (h *RoleHandler) AdminCreateRole(c *gin.Context) { + var input struct { + Name string `json:"name" binding:"required"` + Description string `json:"description"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + role := &models.Role{ + Name: input.Name, + Description: input.Description, + } + + if err := h.service.CreateRole(role); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": role}) +} + +// AdminUpdateRole godoc +// @Summary Update a role (Admin) +// @Description Update an existing role +// @Tags admin,roles +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Role ID" +// @Param role body models.Role true "Role object" +// @Success 200 {object} models.Role +// @Router /api/v1/admin/roles/{id} [put] +func (h *RoleHandler) AdminUpdateRole(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateRole(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated role + role, err := h.service.GetRoleByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": role}) +} + +// AdminDeleteRole godoc +// @Summary Delete a role (Admin) +// @Description Delete a role by ID +// @Tags admin,roles +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Role ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/roles/{id} [delete] +func (h *RoleHandler) AdminDeleteRole(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role ID"}) + return + } + + if err := h.service.DeleteRole(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Role deleted successfully"}) +} diff --git a/app/account/handlers/social_account_handler.go b/app/account/handlers/social_account_handler.go new file mode 100644 index 0000000..4fdaa3a --- /dev/null +++ b/app/account/handlers/social_account_handler.go @@ -0,0 +1,111 @@ +package handlers + +import ( + "gobeyhan/app/account/services" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type SocialAccountHandler struct { + service *services.SocialAccountService +} + +func NewSocialAccountHandler(service *services.SocialAccountService) *SocialAccountHandler { + return &SocialAccountHandler{service: service} +} + +// GetUserSocialAccounts godoc +// @Summary Get user's social accounts +// @Description Get all social accounts for the authenticated user +// @Tags social-accounts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.SocialAccount +// @Router /api/v1/user/social-accounts [get] +func (h *SocialAccountHandler) GetUserSocialAccounts(c *gin.Context) { + userID, exists := c.Get("user_id") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + return + } + + // Convert user_id to uint64 + var uid uint64 + switch v := userID.(type) { + case string: + uid, _ = strconv.ParseUint(v, 10, 64) + case uint64: + uid = v + case int: + uid = uint64(v) + case float64: + uid = uint64(v) + } + + accounts, err := h.service.GetSocialAccountsByUser(uid) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": accounts}) +} + +// DeleteSocialAccount godoc +// @Summary Delete a social account +// @Description Delete a social account for the authenticated user +// @Tags social-accounts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Social Account ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/user/social-accounts/{id} [delete] +func (h *SocialAccountHandler) DeleteSocialAccount(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid social account ID"}) + return + } + + // Verify ownership + account, err := h.service.GetSocialAccountByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if account == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Social account not found"}) + return + } + + userID, _ := c.Get("user_id") + var uid uint64 + switch v := userID.(type) { + case string: + uid, _ = strconv.ParseUint(v, 10, 64) + case uint64: + uid = v + case int: + uid = uint64(v) + case float64: + uid = uint64(v) + } + + if account.UserID != uid { + c.JSON(http.StatusForbidden, gin.H{"error": "You can only delete your own social accounts"}) + return + } + + if err := h.service.DeleteSocialAccount(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Social account deleted successfully"}) +} diff --git a/app/account/handlers/user_handler.go b/app/account/handlers/user_handler.go new file mode 100644 index 0000000..012b274 --- /dev/null +++ b/app/account/handlers/user_handler.go @@ -0,0 +1,287 @@ +package handlers + +import ( + "gobeyhan/app/account/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type UserHandler struct { + service *services.UserService +} + +func NewUserHandler(service *services.UserService) *UserHandler { + return &UserHandler{service: service} +} + +// AdminGetAllUsers godoc +// @Summary Get all users (Admin) +// @Description Get paginated list of all users +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(10) +// @Param include_deleted query bool false "Include soft-deleted users" +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/admin/users [get] +func (h *UserHandler) AdminGetAllUsers(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) + includeDeleted := c.DefaultQuery("include_deleted", "false") == "true" + + if page < 1 { + page = 1 + } + if limit < 1 || limit > 100 { + limit = 10 + } + + users, total, err := h.service.GetAllUsers(includeDeleted, page, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": users, + "total": total, + "page": page, + "limit": limit, + }) +} + +// AdminGetUserByID godoc +// @Summary Get user by ID (Admin) +// @Description Get a single user by ID +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Success 200 {object} models.User +// @Router /api/v1/admin/users/{id} [get] +func (h *UserHandler) AdminGetUserByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + user, err := h.service.GetUserByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if user == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "User not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": user}) +} + +// AdminCreateUser godoc +// @Summary Create a new user (Admin) +// @Description Create a new user +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param user body models.User true "User object" +// @Success 201 {object} models.User +// @Router /api/v1/admin/users [post] +func (h *UserHandler) AdminCreateUser(c *gin.Context) { + var input struct { + UserName string `json:"username"` + Email string `json:"email" binding:"required,email"` + Password string `json:"password" binding:"required"` + Avatar string `json:"avatar"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + user := &models.User{ + UserName: input.UserName, + Email: input.Email, + Avatar: input.Avatar, + } + + if err := h.service.CreateUser(user, input.Password); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": user}) +} + +// AdminUpdateUser godoc +// @Summary Update a user (Admin) +// @Description Update an existing user +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Param user body models.User true "User object" +// @Success 200 {object} models.User +// @Router /api/v1/admin/users/{id} [put] +func (h *UserHandler) AdminUpdateUser(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateUser(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated user + user, err := h.service.GetUserByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": user}) +} + +// AdminDeleteUser godoc +// @Summary Delete a user (Admin) +// @Description Soft delete a user by ID +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/users/{id} [delete] +func (h *UserHandler) AdminDeleteUser(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + if err := h.service.DeleteUser(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "User deleted successfully"}) +} + +// AdminRestoreUser godoc +// @Summary Restore a deleted user (Admin) +// @Description Restore a soft-deleted user +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/users/{id}/restore [post] +func (h *UserHandler) AdminRestoreUser(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + if err := h.service.RestoreUser(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "User restored successfully"}) +} + +// AdminAssignRole godoc +// @Summary Assign role to user (Admin) +// @Description Assign a role to a user +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Param role_id body int true "Role ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/users/{id}/roles [post] +func (h *UserHandler) AdminAssignRole(c *gin.Context) { + idStr := c.Param("id") + userID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + var input struct { + RoleID uint64 `json:"role_id" binding:"required"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.AssignRole(userID, input.RoleID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Role assigned successfully"}) +} + +// AdminRemoveRole godoc +// @Summary Remove role from user (Admin) +// @Description Remove a role from a user +// @Tags admin,users +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "User ID" +// @Param role_id path int true "Role ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/users/{id}/roles/{role_id} [delete] +func (h *UserHandler) AdminRemoveRole(c *gin.Context) { + userIDStr := c.Param("id") + userID, err := strconv.ParseUint(userIDStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid user ID"}) + return + } + + roleIDStr := c.Param("role_id") + roleID, err := strconv.ParseUint(roleIDStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid role ID"}) + return + } + + if err := h.service.RemoveRole(userID, roleID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Role removed successfully"}) +} diff --git a/app/account/services/permission_service.go b/app/account/services/permission_service.go new file mode 100644 index 0000000..340a548 --- /dev/null +++ b/app/account/services/permission_service.go @@ -0,0 +1,42 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type PermissionService struct{} + +func NewPermissionService() *PermissionService { + return &PermissionService{} +} + +// GetAllPermissions retrieves all permissions +func (s *PermissionService) GetAllPermissions() ([]models.Permission, error) { + var permissions []models.Permission + err := database.DB.Find(&permissions).Error + return permissions, err +} + +// GetPermissionByID retrieves a permission by ID +func (s *PermissionService) GetPermissionByID(id uint64) (*models.Permission, error) { + var permission models.Permission + err := database.DB.First(&permission, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &permission, nil +} + +// CreatePermission creates a new permission +func (s *PermissionService) CreatePermission(permission *models.Permission) error { + return database.DB.Create(permission).Error +} diff --git a/app/account/services/role_service.go b/app/account/services/role_service.go new file mode 100644 index 0000000..9d96d0a --- /dev/null +++ b/app/account/services/role_service.go @@ -0,0 +1,96 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type RoleService struct{} + +func NewRoleService() *RoleService { + return &RoleService{} +} + +// GetAllRoles retrieves all roles +func (s *RoleService) GetAllRoles() ([]models.Role, error) { + var roles []models.Role + err := database.DB.Preload("Permissions").Find(&roles).Error + return roles, err +} + +// GetRoleByID retrieves a role by ID +func (s *RoleService) GetRoleByID(id uint64) (*models.Role, error) { + var role models.Role + err := database.DB.Preload("Permissions").First(&role, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &role, nil +} + +// CreateRole creates a new role +func (s *RoleService) CreateRole(role *models.Role) error { + return database.DB.Create(role).Error +} + +// UpdateRole updates an existing role +func (s *RoleService) UpdateRole(id uint64, updates map[string]interface{}) error { + result := database.DB.Model(&models.Role{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// DeleteRole deletes a role by ID +func (s *RoleService) DeleteRole(id uint64) error { + result := database.DB.Delete(&models.Role{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// AssignPermission assigns a permission to a role +func (s *RoleService) AssignPermission(roleID, permissionID uint64) error { + var role models.Role + if err := database.DB.First(&role, roleID).Error; err != nil { + return err + } + + var permission models.Permission + if err := database.DB.First(&permission, permissionID).Error; err != nil { + return err + } + + return database.DB.Model(&role).Association("Permissions").Append(&permission) +} + +// RemovePermission removes a permission from a role +func (s *RoleService) RemovePermission(roleID, permissionID uint64) error { + var role models.Role + if err := database.DB.Preload("Permissions").First(&role, roleID).Error; err != nil { + return err + } + + var permission models.Permission + if err := database.DB.First(&permission, permissionID).Error; err != nil { + return err + } + + return database.DB.Model(&role).Association("Permissions").Delete(&permission) +} diff --git a/app/account/services/social_account_service.go b/app/account/services/social_account_service.go new file mode 100644 index 0000000..284d324 --- /dev/null +++ b/app/account/services/social_account_service.go @@ -0,0 +1,54 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type SocialAccountService struct{} + +func NewSocialAccountService() *SocialAccountService { + return &SocialAccountService{} +} + +// GetSocialAccountsByUser retrieves all social accounts for a user +func (s *SocialAccountService) GetSocialAccountsByUser(userID uint64) ([]models.SocialAccount, error) { + var accounts []models.SocialAccount + err := database.DB.Where("user_id = ?", userID).Find(&accounts).Error + return accounts, err +} + +// GetSocialAccountByID retrieves a social account by ID +func (s *SocialAccountService) GetSocialAccountByID(id uint64) (*models.SocialAccount, error) { + var account models.SocialAccount + err := database.DB.First(&account, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &account, nil +} + +// CreateSocialAccount creates a new social account +func (s *SocialAccountService) CreateSocialAccount(account *models.SocialAccount) error { + return database.DB.Create(account).Error +} + +// DeleteSocialAccount deletes a social account by ID +func (s *SocialAccountService) DeleteSocialAccount(id uint64) error { + result := database.DB.Delete(&models.SocialAccount{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} diff --git a/app/account/services/user_service.go b/app/account/services/user_service.go new file mode 100644 index 0000000..f8ca4fb --- /dev/null +++ b/app/account/services/user_service.go @@ -0,0 +1,184 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "golang.org/x/crypto/bcrypt" + "gorm.io/gorm" +) + +type UserService struct{} + +func NewUserService() *UserService { + return &UserService{} +} + +// GetAllUsers retrieves all users, optionally including soft-deleted ones +func (s *UserService) GetAllUsers(includeDeleted bool, page, limit int) ([]models.User, int64, error) { + var users []models.User + var total int64 + + query := database.DB.Preload("Roles").Preload("SocialAccounts") + + if includeDeleted { + query = query.Unscoped() + } + + query.Model(&models.User{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("created_at DESC"). + Find(&users).Error + + return users, total, err +} + +// GetUserByID retrieves a user by ID +func (s *UserService) GetUserByID(id uint64) (*models.User, error) { + var user models.User + err := database.DB. + Preload("Roles"). + Preload("SocialAccounts"). + First(&user, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &user, nil +} + +// GetUserByEmail retrieves a user by email +func (s *UserService) GetUserByEmail(email string) (*models.User, error) { + var user models.User + err := database.DB. + Preload("Roles"). + Preload("SocialAccounts"). + Where("email = ?", email). + First(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &user, nil +} + +// CreateUser creates a new user with hashed password +func (s *UserService) CreateUser(user *models.User, password string) error { + // Hash password if provided + if password != "" { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + user.Password = string(hashedPassword) + } + + return database.DB.Create(user).Error +} + +// VerifyPassword checks if the provided password matches the hashed password +func (s *UserService) VerifyPassword(hashedPassword, password string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password)) + return err == nil +} + +// UpdateUser updates an existing user +func (s *UserService) UpdateUser(id uint64, updates map[string]interface{}) error { + // If password is being updated, hash it first + if password, ok := updates["password"].(string); ok && password != "" { + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return err + } + updates["password"] = string(hashedPassword) + } + + result := database.DB.Model(&models.User{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// DeleteUser soft deletes a user +func (s *UserService) DeleteUser(id uint64) error { + result := database.DB.Delete(&models.User{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// RestoreUser restores a soft-deleted user +func (s *UserService) RestoreUser(id uint64) error { + result := database.DB.Model(&models.User{}).Unscoped(). + Where("id = ?", id). + Update("deleted_at", nil) + + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// AssignRole assigns a role to a user +func (s *UserService) AssignRole(userID, roleID uint64) error { + var user models.User + if err := database.DB.First(&user, userID).Error; err != nil { + return err + } + + var role models.Role + if err := database.DB.First(&role, roleID).Error; err != nil { + return err + } + + return database.DB.Model(&user).Association("Roles").Append(&role) +} + +// RemoveRole removes a role from a user +func (s *UserService) RemoveRole(userID, roleID uint64) error { + var user models.User + if err := database.DB.Preload("Roles").First(&user, userID).Error; err != nil { + return err + } + + var role models.Role + if err := database.DB.First(&role, roleID).Error; err != nil { + return err + } + + return database.DB.Model(&user).Association("Roles").Delete(&role) +} + +// AssignDefaultRole assigns the default 'user' role to a user +func (s *UserService) AssignDefaultRole(userID uint64) error { + var role models.Role + // Find role by name 'user' + if err := database.DB.Where("name = ?", "user").First(&role).Error; err != nil { + return err + } + + return s.AssignRole(userID, role.ID) +} diff --git a/app/blog/handlers/category_handler.go b/app/blog/handlers/category_handler.go new file mode 100644 index 0000000..6a60e26 --- /dev/null +++ b/app/blog/handlers/category_handler.go @@ -0,0 +1,235 @@ +package handlers + +import ( + "gobeyhan/app/blog/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type CategoryHandler struct { + service *services.CategoryService +} + +func NewCategoryHandler(service *services.CategoryService) *CategoryHandler { + return &CategoryHandler{service: service} +} + +// GetAllCategories godoc +// @Summary Get all active categories +// @Description Get list of all active categories (public endpoint) +// @Tags categories +// @Accept json +// @Produce json +// @Success 200 {array} models.Category +// @Router /api/v1/categories [get] +func (h *CategoryHandler) GetAllCategories(c *gin.Context) { + categories, err := h.service.GetAllCategories(true) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": categories}) +} + +// GetCategoryBySlug godoc +// @Summary Get category by slug +// @Description Get a single category by its slug (public endpoint) +// @Tags categories +// @Accept json +// @Produce json +// @Param slug path string true "Category Slug" +// @Success 200 {object} models.Category +// @Router /api/v1/categories/{slug} [get] +func (h *CategoryHandler) GetCategoryBySlug(c *gin.Context) { + slug := c.Param("slug") + + category, err := h.service.GetCategoryBySlug(slug) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if category == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": category}) +} + +// AdminGetAllCategories godoc +// @Summary Get all categories (Admin) +// @Description Get list of all categories including inactive ones +// @Tags admin,categories +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.Category +// @Router /api/v1/admin/categories [get] +func (h *CategoryHandler) AdminGetAllCategories(c *gin.Context) { + categories, err := h.service.GetAllCategories(false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": categories}) +} + +// GetCategoryByID godoc +// @Summary Get category by ID (Admin) +// @Description Get a single category by ID +// @Tags admin,categories +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Category ID" +// @Success 200 {object} models.Category +// @Router /api/v1/admin/categories/{id} [get] +func (h *CategoryHandler) GetCategoryByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"}) + return + } + + category, err := h.service.GetCategoryByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if category == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Category not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": category}) +} + +// CreateCategory godoc +// @Summary Create a new category (Admin) +// @Description Create a new category +// @Tags admin,categories +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param category body models.Category true "Category object" +// @Success 201 {object} models.Category +// @Router /api/v1/admin/categories [post] +func (h *CategoryHandler) CreateCategory(c *gin.Context) { + var input struct { + Title string `json:"title" binding:"required"` + Keywords string `json:"keywords"` + Desc string `json:"description"` + IsActive *bool `json:"is_active"` + Order *int `json:"order"` + Slug string `json:"slug"` + ParentID *uint64 `json:"parent_id"` + Image string `json:"image"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + category := &models.Category{ + Title: input.Title, + Keywords: input.Keywords, + Desc: input.Desc, + Slug: input.Slug, + ParentID: input.ParentID, + Image: input.Image, + } + + if input.IsActive != nil { + category.IsActive = *input.IsActive + } else { + category.IsActive = true + } + + if input.Order != nil { + category.Order = *input.Order + } else { + category.Order = 1 + } + + if err := h.service.CreateCategory(category); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": category}) +} + +// UpdateCategory godoc +// @Summary Update a category (Admin) +// @Description Update an existing category +// @Tags admin,categories +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Category ID" +// @Param category body models.Category true "Category object" +// @Success 200 {object} models.Category +// @Router /api/v1/admin/categories/{id} [put] +func (h *CategoryHandler) UpdateCategory(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateCategory(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated category + category, err := h.service.GetCategoryByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": category}) +} + +// DeleteCategory godoc +// @Summary Delete a category (Admin) +// @Description Delete a category by ID +// @Tags admin,categories +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Category ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/categories/{id} [delete] +func (h *CategoryHandler) DeleteCategory(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"}) + return + } + + if err := h.service.DeleteCategory(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Category deleted successfully"}) +} diff --git a/app/blog/handlers/category_view_handler.go b/app/blog/handlers/category_view_handler.go new file mode 100644 index 0000000..8a0496d --- /dev/null +++ b/app/blog/handlers/category_view_handler.go @@ -0,0 +1,111 @@ +package handlers + +import ( + "gobeyhan/app/blog/services" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type CategoryViewHandler struct { + service *services.CategoryViewService +} + +func NewCategoryViewHandler(service *services.CategoryViewService) *CategoryViewHandler { + return &CategoryViewHandler{service: service} +} + +// TrackCategoryView godoc +// @Summary Track a category view +// @Description Record a view event for a category (public endpoint) +// @Tags category-views +// @Accept json +// @Produce json +// @Param id path int true "Category ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/categories/{id}/view [post] +func (h *CategoryViewHandler) TrackCategoryView(c *gin.Context) { + idStr := c.Param("id") + categoryID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"}) + return + } + + ipAddress := c.ClientIP() + userAgent := c.Request.UserAgent() + + if err := h.service.TrackCategoryView(categoryID, ipAddress, userAgent); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "View tracked successfully"}) +} + +// AdminGetAllCategoryViews godoc +// @Summary Get all category views (Admin) +// @Description Get paginated list of all category views +// @Tags admin,category-views +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(10) +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/admin/category-views [get] +func (h *CategoryViewHandler) AdminGetAllCategoryViews(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) + + if page < 1 { + page = 1 + } + if limit < 1 || limit > 100 { + limit = 10 + } + + views, total, err := h.service.GetAllCategoryViews(page, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": views, + "total": total, + "page": page, + "limit": limit, + }) +} + +// GetCategoryViewStats godoc +// @Summary Get view stats for a category (Admin) +// @Description Get view count and details for a specific category +// @Tags admin,category-views +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Category ID" +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/admin/categories/{id}/views [get] +func (h *CategoryViewHandler) GetCategoryViewStats(c *gin.Context) { + idStr := c.Param("id") + categoryID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid category ID"}) + return + } + + count, err := h.service.GetCategoryViewCount(categoryID) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "category_id": categoryID, + "view_count": count, + }) +} diff --git a/app/blog/handlers/comment_handler.go b/app/blog/handlers/comment_handler.go new file mode 100644 index 0000000..b3ea695 --- /dev/null +++ b/app/blog/handlers/comment_handler.go @@ -0,0 +1,245 @@ +package handlers + +import ( + "gobeyhan/app/blog/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type CommentHandler struct { + service *services.CommentService +} + +func NewCommentHandler(service *services.CommentService) *CommentHandler { + return &CommentHandler{service: service} +} + +// GetPostComments godoc +// @Summary Get comments for a post +// @Description Get all active comments for a specific post (public endpoint) +// @Tags comments +// @Accept json +// @Produce json +// @Param id path int true "Post ID" +// @Success 200 {array} models.Comment +// @Router /api/v1/posts/{id}/comments [get] +func (h *CommentHandler) GetPostComments(c *gin.Context) { + idStr := c.Param("id") + postID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"}) + return + } + + comments, err := h.service.GetCommentsByPost(postID, true) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": comments}) +} + +// CreatePostComment godoc +// @Summary Create a comment on a post +// @Description Create a new comment (requires authentication) +// @Tags comments +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Post ID" +// @Param comment body models.Comment true "Comment object" +// @Success 201 {object} models.Comment +// @Router /api/v1/posts/{id}/comments [post] +func (h *CommentHandler) CreatePostComment(c *gin.Context) { + idStr := c.Param("id") + postID, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"}) + return + } + + // Get user ID from context (set by auth middleware) + userID, exists := c.Get("user_id") + if !exists { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"}) + return + } + + var input struct { + Title string `json:"title"` + Body string `json:"body" binding:"required"` + ParentID *uint64 `json:"parent_id"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + // Convert user_id to uint64 + var uid uint64 + switch v := userID.(type) { + case string: + uid, _ = strconv.ParseUint(v, 10, 64) + case uint64: + uid = v + case int: + uid = uint64(v) + case float64: + uid = uint64(v) + } + + comment := &models.Comment{ + UserID: uid, + ProductID: postID, + Title: input.Title, + Body: input.Body, + ParentID: input.ParentID, + IsActive: true, + } + + if err := h.service.CreateComment(comment); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": comment}) +} + +// AdminGetAllComments godoc +// @Summary Get all comments (Admin) +// @Description Get paginated list of all comments +// @Tags admin,comments +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(10) +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/admin/comments [get] +func (h *CommentHandler) AdminGetAllComments(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) + + if page < 1 { + page = 1 + } + if limit < 1 || limit > 100 { + limit = 10 + } + + comments, total, err := h.service.GetAllComments(page, limit, false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": comments, + "total": total, + "page": page, + "limit": limit, + }) +} + +// AdminGetCommentByID godoc +// @Summary Get comment by ID (Admin) +// @Description Get a single comment by ID +// @Tags admin,comments +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Comment ID" +// @Success 200 {object} models.Comment +// @Router /api/v1/admin/comments/{id} [get] +func (h *CommentHandler) AdminGetCommentByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"}) + return + } + + comment, err := h.service.GetCommentByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if comment == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Comment not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": comment}) +} + +// AdminUpdateComment godoc +// @Summary Update a comment (Admin) +// @Description Update an existing comment +// @Tags admin,comments +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Comment ID" +// @Param comment body models.Comment true "Comment object" +// @Success 200 {object} models.Comment +// @Router /api/v1/admin/comments/{id} [put] +func (h *CommentHandler) AdminUpdateComment(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateComment(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated comment + comment, err := h.service.GetCommentByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": comment}) +} + +// AdminDeleteComment godoc +// @Summary Delete a comment (Admin) +// @Description Delete a comment by ID +// @Tags admin,comments +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Comment ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/comments/{id} [delete] +func (h *CommentHandler) AdminDeleteComment(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid comment ID"}) + return + } + + if err := h.service.DeleteComment(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Comment deleted successfully"}) +} diff --git a/app/blog/handlers/post_handler.go b/app/blog/handlers/post_handler.go new file mode 100644 index 0000000..38f91f7 --- /dev/null +++ b/app/blog/handlers/post_handler.go @@ -0,0 +1,306 @@ +package handlers + +import ( + "gobeyhan/app/blog/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type PostHandler struct { + service *services.PostService +} + +func NewPostHandler(service *services.PostService) *PostHandler { + return &PostHandler{service: service} +} + +// GetAllPosts godoc +// @Summary Get all active posts +// @Description Get paginated list of active posts (public endpoint) +// @Tags posts +// @Accept json +// @Produce json +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(10) +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/posts [get] +func (h *PostHandler) GetAllPosts(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) + + if page < 1 { + page = 1 + } + if limit < 1 || limit > 100 { + limit = 10 + } + + posts, total, err := h.service.GetAllPosts(page, limit, true) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": posts, + "total": total, + "page": page, + "limit": limit, + }) +} + +// GetPostBySlug godoc +// @Summary Get post by slug +// @Description Get a single post by its slug (public endpoint) +// @Tags posts +// @Accept json +// @Produce json +// @Param slug path string true "Post Slug" +// @Success 200 {object} models.Post +// @Router /api/v1/posts/{slug} [get] +func (h *PostHandler) GetPostBySlug(c *gin.Context) { + slug := c.Param("slug") + + post, err := h.service.GetPostBySlug(slug) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if post == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": post}) +} + +// AdminGetAllPosts godoc +// @Summary Get all posts (Admin) +// @Description Get paginated list of all posts including inactive +// @Tags admin,posts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param page query int false "Page number" default(1) +// @Param limit query int false "Items per page" default(10) +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/admin/posts [get] +func (h *PostHandler) AdminGetAllPosts(c *gin.Context) { + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "10")) + + if page < 1 { + page = 1 + } + if limit < 1 || limit > 100 { + limit = 10 + } + + posts, total, err := h.service.GetAllPosts(page, limit, false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "data": posts, + "total": total, + "page": page, + "limit": limit, + }) +} + +// GetPostByID godoc +// @Summary Get post by ID (Admin) +// @Description Get a single post by ID +// @Tags admin,posts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Post ID" +// @Success 200 {object} models.Post +// @Router /api/v1/admin/posts/{id} [get] +func (h *PostHandler) GetPostByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"}) + return + } + + post, err := h.service.GetPostByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if post == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Post not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": post}) +} + +// CreatePost godoc +// @Summary Create a new post (Admin) +// @Description Create a new post +// @Tags admin,posts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param post body models.Post true "Post object" +// @Success 201 {object} models.Post +// @Router /api/v1/admin/posts [post] +func (h *PostHandler) CreatePost(c *gin.Context) { + var input struct { + Title string `json:"title" binding:"required"` + UserID *uint64 `json:"user_id"` + Content string `json:"content"` + Keywords string `json:"keywords"` + Image string `json:"image"` + Thumb string `json:"thumb"` + Video string `json:"video"` + Slug string `json:"slug"` + IsActive *bool `json:"is_active"` + IsFront *bool `json:"is_front"` + ParentID *uint64 `json:"parent_id"` + Categories []uint64 `json:"categories"` + Tags []uint64 `json:"tags"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + post := &models.Post{ + Title: input.Title, + UserID: input.UserID, + Content: input.Content, + Keywords: input.Keywords, + Image: input.Image, + Thumb: input.Thumb, + Video: input.Video, + Slug: input.Slug, + ParentID: input.ParentID, + } + + if input.IsActive != nil { + post.IsActive = *input.IsActive + } else { + post.IsActive = true + } + + if input.IsFront != nil { + post.IsFront = *input.IsFront + } else { + post.IsFront = true + } + + // Create post first + if err := h.service.CreatePost(post); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Add categories and tags if provided + if len(input.Categories) > 0 || len(input.Tags) > 0 { + updates := make(map[string]interface{}) + + if len(input.Categories) > 0 { + var categories []*models.Category + for _, catID := range input.Categories { + categories = append(categories, &models.Category{ID: catID}) + } + updates["categories"] = categories + } + + if len(input.Tags) > 0 { + var tags []*models.Tag + for _, tagID := range input.Tags { + tags = append(tags, &models.Tag{ID: tagID}) + } + updates["tags"] = tags + } + + if err := h.service.UpdatePost(post.ID, updates); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + } + + // Fetch created post with relationships + createdPost, _ := h.service.GetPostByID(post.ID) + c.JSON(http.StatusCreated, gin.H{"data": createdPost}) +} + +// UpdatePost godoc +// @Summary Update a post (Admin) +// @Description Update an existing post +// @Tags admin,posts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Post ID" +// @Param post body models.Post true "Post object" +// @Success 200 {object} models.Post +// @Router /api/v1/admin/posts/{id} [put] +func (h *PostHandler) UpdatePost(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdatePost(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated post + post, err := h.service.GetPostByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": post}) +} + +// DeletePost godoc +// @Summary Delete a post (Admin) +// @Description Delete a post by ID +// @Tags admin,posts +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Post ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/posts/{id} [delete] +func (h *PostHandler) DeletePost(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid post ID"}) + return + } + + if err := h.service.DeletePost(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Post deleted successfully"}) +} diff --git a/app/blog/handlers/tag_handler.go b/app/blog/handlers/tag_handler.go new file mode 100644 index 0000000..41ac1a0 --- /dev/null +++ b/app/blog/handlers/tag_handler.go @@ -0,0 +1,220 @@ +package handlers + +import ( + "gobeyhan/app/blog/services" + "gobeyhan/database/models" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type TagHandler struct { + service *services.TagService +} + +func NewTagHandler(service *services.TagService) *TagHandler { + return &TagHandler{service: service} +} + +// GetAllTags godoc +// @Summary Get all active tags +// @Description Get list of all active tags (public endpoint) +// @Tags tags +// @Accept json +// @Produce json +// @Success 200 {array} models.Tag +// @Router /api/v1/tags [get] +func (h *TagHandler) GetAllTags(c *gin.Context) { + tags, err := h.service.GetAllTags(true) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": tags}) +} + +// GetTagBySlug godoc +// @Summary Get tag by slug +// @Description Get a single tag by its slug (public endpoint) +// @Tags tags +// @Accept json +// @Produce json +// @Param slug path string true "Tag Slug" +// @Success 200 {object} models.Tag +// @Router /api/v1/tags/{slug} [get] +func (h *TagHandler) GetTagBySlug(c *gin.Context) { + slug := c.Param("slug") + + tag, err := h.service.GetTagBySlug(slug) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if tag == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": tag}) +} + +// AdminGetAllTags godoc +// @Summary Get all tags (Admin) +// @Description Get list of all tags including inactive ones +// @Tags admin,tags +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.Tag +// @Router /api/v1/admin/tags [get] +func (h *TagHandler) AdminGetAllTags(c *gin.Context) { + tags, err := h.service.GetAllTags(false) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": tags}) +} + +// GetTagByID godoc +// @Summary Get tag by ID (Admin) +// @Description Get a single tag by ID +// @Tags admin,tags +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Tag ID" +// @Success 200 {object} models.Tag +// @Router /api/v1/admin/tags/{id} [get] +func (h *TagHandler) GetTagByID(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"}) + return + } + + tag, err := h.service.GetTagByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + if tag == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "Tag not found"}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": tag}) +} + +// CreateTag godoc +// @Summary Create a new tag (Admin) +// @Description Create a new tag +// @Tags admin,tags +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param tag body models.Tag true "Tag object" +// @Success 201 {object} models.Tag +// @Router /api/v1/admin/tags [post] +func (h *TagHandler) CreateTag(c *gin.Context) { + var input struct { + Tag string `json:"tag" binding:"required"` + Slug string `json:"slug"` + IsActive *bool `json:"is_active"` + } + + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + tag := &models.Tag{ + Tag: input.Tag, + Slug: input.Slug, + } + + if input.IsActive != nil { + tag.IsActive = *input.IsActive + } else { + tag.IsActive = true + } + + if err := h.service.CreateTag(tag); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": tag}) +} + +// UpdateTag godoc +// @Summary Update a tag (Admin) +// @Description Update an existing tag +// @Tags admin,tags +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Tag ID" +// @Param tag body models.Tag true "Tag object" +// @Success 200 {object} models.Tag +// @Router /api/v1/admin/tags/{id} [put] +func (h *TagHandler) UpdateTag(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"}) + return + } + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateTag(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // Fetch updated tag + tag, err := h.service.GetTagByID(id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": tag}) +} + +// DeleteTag godoc +// @Summary Delete a tag (Admin) +// @Description Delete a tag by ID +// @Tags admin,tags +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Tag ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/tags/{id} [delete] +func (h *TagHandler) DeleteTag(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid tag ID"}) + return + } + + if err := h.service.DeleteTag(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Tag deleted successfully"}) +} diff --git a/app/blog/services/category_service.go b/app/blog/services/category_service.go new file mode 100644 index 0000000..810be83 --- /dev/null +++ b/app/blog/services/category_service.go @@ -0,0 +1,107 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type CategoryService struct{} + +func NewCategoryService() *CategoryService { + return &CategoryService{} +} + +// GetAllCategories retrieves all categories, optionally filtering by active status +func (s *CategoryService) GetAllCategories(activeOnly bool) ([]models.Category, error) { + var categories []models.Category + query := database.DB.Preload("Parent").Preload("Children") + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + err := query.Order("`order` ASC, created_at DESC").Find(&categories).Error + return categories, err +} + +// GetCategoryByID retrieves a category by ID with parent and children relationships +func (s *CategoryService) GetCategoryByID(id uint64) (*models.Category, error) { + var category models.Category + err := database.DB. + Preload("Parent"). + Preload("Children"). + First(&category, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &category, nil +} + +// GetCategoryBySlug retrieves a category by slug +func (s *CategoryService) GetCategoryBySlug(slug string) (*models.Category, error) { + var category models.Category + err := database.DB. + Preload("Parent"). + Preload("Children"). + Where("slug = ?", slug). + First(&category).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &category, nil +} + +// CreateCategory creates a new category +func (s *CategoryService) CreateCategory(category *models.Category) error { + return database.DB.Create(category).Error +} + +// UpdateCategory updates an existing category +func (s *CategoryService) UpdateCategory(id uint64, updates map[string]interface{}) error { + result := database.DB.Model(&models.Category{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// DeleteCategory deletes a category by ID +func (s *CategoryService) DeleteCategory(id uint64) error { + result := database.DB.Delete(&models.Category{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// GetCategoriesByParent retrieves child categories of a parent +func (s *CategoryService) GetCategoriesByParent(parentID uint64, activeOnly bool) ([]models.Category, error) { + var categories []models.Category + query := database.DB.Where("parent_id = ?", parentID) + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + err := query.Order("`order` ASC, created_at DESC").Find(&categories).Error + return categories, err +} diff --git a/app/blog/services/category_view_service.go b/app/blog/services/category_view_service.go new file mode 100644 index 0000000..5061d84 --- /dev/null +++ b/app/blog/services/category_view_service.go @@ -0,0 +1,67 @@ +package services + +import ( + "gobeyhan/database" + "gobeyhan/database/models" +) + +type CategoryViewService struct{} + +func NewCategoryViewService() *CategoryViewService { + return &CategoryViewService{} +} + +// TrackCategoryView records a category view +func (s *CategoryViewService) TrackCategoryView(categoryID uint64, ipAddress, userAgent string) error { + view := &models.CategoryView{ + CategoryID: categoryID, + IPAddress: ipAddress, + UserAgent: userAgent, + } + return database.DB.Create(view).Error +} + +// GetCategoryViewCount gets total view count for a category +func (s *CategoryViewService) GetCategoryViewCount(categoryID uint64) (int64, error) { + var count int64 + err := database.DB.Model(&models.CategoryView{}). + Where("category_id = ?", categoryID). + Count(&count).Error + return count, err +} + +// GetAllCategoryViews gets all views for a category with pagination +func (s *CategoryViewService) GetAllCategoryViews(page, limit int) ([]models.CategoryView, int64, error) { + var views []models.CategoryView + var total int64 + + query := database.DB.Preload("Category") + + query.Model(&models.CategoryView{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("created_at DESC"). + Find(&views).Error + + return views, total, err +} + +// GetViewsByCategory gets views for a specific category +func (s *CategoryViewService) GetViewsByCategory(categoryID uint64, page, limit int) ([]models.CategoryView, int64, error) { + var views []models.CategoryView + var total int64 + + query := database.DB.Where("category_id = ?", categoryID) + + query.Model(&models.CategoryView{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("created_at DESC"). + Find(&views).Error + + return views, total, err +} diff --git a/app/blog/services/comment_service.go b/app/blog/services/comment_service.go new file mode 100644 index 0000000..9322c04 --- /dev/null +++ b/app/blog/services/comment_service.go @@ -0,0 +1,115 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type CommentService struct{} + +func NewCommentService() *CommentService { + return &CommentService{} +} + +// GetCommentsByPost retrieves comments for a specific post +func (s *CommentService) GetCommentsByPost(postID uint64, activeOnly bool) ([]models.Comment, error) { + var comments []models.Comment + query := database.DB. + Where("product_id = ?", postID). + Preload("Parent"). + Preload("Children") + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + err := query.Order("created_at DESC").Find(&comments).Error + return comments, err +} + +// GetAllComments retrieves all comments with pagination +func (s *CommentService) GetAllComments(page, limit int, activeOnly bool) ([]models.Comment, int64, error) { + var comments []models.Comment + var total int64 + + query := database.DB. + Preload("Product"). + Preload("Parent"). + Preload("Children") + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + query.Model(&models.Comment{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("created_at DESC"). + Find(&comments).Error + + return comments, total, err +} + +// GetCommentByID retrieves a comment by ID +func (s *CommentService) GetCommentByID(id uint64) (*models.Comment, error) { + var comment models.Comment + err := database.DB. + Preload("Product"). + Preload("Parent"). + Preload("Children"). + First(&comment, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &comment, nil +} + +// CreateComment creates a new comment +func (s *CommentService) CreateComment(comment *models.Comment) error { + return database.DB.Create(comment).Error +} + +// UpdateComment updates an existing comment +func (s *CommentService) UpdateComment(id uint64, updates map[string]interface{}) error { + result := database.DB.Model(&models.Comment{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// DeleteComment deletes a comment by ID +func (s *CommentService) DeleteComment(id uint64) error { + result := database.DB.Delete(&models.Comment{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// GetCommentReplies retrieves replies to a specific comment +func (s *CommentService) GetCommentReplies(commentID uint64) ([]models.Comment, error) { + var replies []models.Comment + err := database.DB. + Where("parent_id = ? AND is_active = ?", commentID, true). + Order("created_at ASC"). + Find(&replies).Error + + return replies, err +} diff --git a/app/blog/services/post_service.go b/app/blog/services/post_service.go new file mode 100644 index 0000000..60f03dc --- /dev/null +++ b/app/blog/services/post_service.go @@ -0,0 +1,202 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type PostService struct{} + +func NewPostService() *PostService { + return &PostService{} +} + +// GetAllPosts retrieves all posts with pagination, optionally filtering by active status +func (s *PostService) GetAllPosts(page, limit int, activeOnly bool) ([]models.Post, int64, error) { + var posts []models.Post + var total int64 + + query := database.DB. + Preload("User"). + Preload("Categories"). + Preload("Tags"). + Preload("Parent"). + Preload("Children") + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + // Count total + query.Model(&models.Post{}).Count(&total) + + // Get paginated results + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("created_at DESC"). + Find(&posts).Error + + return posts, total, err +} + +// GetPostByID retrieves a post by ID with all relationships +func (s *PostService) GetPostByID(id uint64) (*models.Post, error) { + var post models.Post + err := database.DB. + Preload("User"). + Preload("Categories"). + Preload("Tags"). + Preload("Parent"). + Preload("Children"). + First(&post, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &post, nil +} + +// GetPostBySlug retrieves a post by slug +func (s *PostService) GetPostBySlug(slug string) (*models.Post, error) { + var post models.Post + err := database.DB. + Preload("User"). + Preload("Categories"). + Preload("Tags"). + Preload("Parent"). + Preload("Children"). + Where("slug = ? AND is_active = ?", slug, true). + First(&post).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &post, nil +} + +// CreatePost creates a new post +func (s *PostService) CreatePost(post *models.Post) error { + return database.DB.Create(post).Error +} + +// UpdatePost updates an existing post +func (s *PostService) UpdatePost(id uint64, updates map[string]interface{}) error { + // Handle many-to-many relationships separately if they're in updates + var categoryIDs []*models.Category + var tagIDs []*models.Tag + + if categories, ok := updates["categories"]; ok { + if catSlice, ok := categories.([]*models.Category); ok { + categoryIDs = catSlice + delete(updates, "categories") + } + } + + if tags, ok := updates["tags"]; ok { + if tagSlice, ok := tags.([]*models.Tag); ok { + tagIDs = tagSlice + delete(updates, "tags") + } + } + + // Update basic fields + result := database.DB.Model(&models.Post{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + + // Update relationships if provided + if len(categoryIDs) > 0 || len(tagIDs) > 0 { + var post models.Post + if err := database.DB.First(&post, id).Error; err != nil { + return err + } + + if len(categoryIDs) > 0 { + if err := database.DB.Model(&post).Association("Categories").Replace(categoryIDs); err != nil { + return err + } + } + + if len(tagIDs) > 0 { + if err := database.DB.Model(&post).Association("Tags").Replace(tagIDs); err != nil { + return err + } + } + } + + return nil +} + +// DeletePost deletes a post by ID +func (s *PostService) DeletePost(id uint64) error { + result := database.DB.Delete(&models.Post{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// GetPostsByCategory retrieves posts by category ID +func (s *PostService) GetPostsByCategory(categoryID uint64, page, limit int) ([]models.Post, int64, error) { + var posts []models.Post + var total int64 + + query := database.DB. + Joins("JOIN post_categories ON post_categories.post_id = posts.id"). + Where("post_categories.category_id = ? AND posts.is_active = ?", categoryID, true). + Preload("User"). + Preload("Categories"). + Preload("Tags") + + query.Model(&models.Post{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("posts.created_at DESC"). + Find(&posts).Error + + return posts, total, err +} + +// GetPostsByTag retrieves posts by tag ID +func (s *PostService) GetPostsByTag(tagID uint64, page, limit int) ([]models.Post, int64, error) { + var posts []models.Post + var total int64 + + query := database.DB. + Joins("JOIN post_tags ON post_tags.post_id = posts.id"). + Where("post_tags.tag_id = ? AND posts.is_active = ?", tagID, true). + Preload("User"). + Preload("Categories"). + Preload("Tags") + + query.Model(&models.Post{}).Count(&total) + + err := query. + Offset((page - 1) * limit). + Limit(limit). + Order("posts.created_at DESC"). + Find(&posts).Error + + return posts, total, err +} diff --git a/app/blog/services/tag_service.go b/app/blog/services/tag_service.go new file mode 100644 index 0000000..43f0d55 --- /dev/null +++ b/app/blog/services/tag_service.go @@ -0,0 +1,87 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +type TagService struct{} + +func NewTagService() *TagService { + return &TagService{} +} + +// GetAllTags retrieves all tags, optionally filtering by active status +func (s *TagService) GetAllTags(activeOnly bool) ([]models.Tag, error) { + var tags []models.Tag + query := database.DB + + if activeOnly { + query = query.Where("is_active = ?", true) + } + + err := query.Order("tag ASC").Find(&tags).Error + return tags, err +} + +// GetTagByID retrieves a tag by ID +func (s *TagService) GetTagByID(id uint64) (*models.Tag, error) { + var tag models.Tag + err := database.DB.First(&tag, id).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &tag, nil +} + +// GetTagBySlug retrieves a tag by slug +func (s *TagService) GetTagBySlug(slug string) (*models.Tag, error) { + var tag models.Tag + err := database.DB.Where("slug = ?", slug).First(&tag).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, err + } + + return &tag, nil +} + +// CreateTag creates a new tag +func (s *TagService) CreateTag(tag *models.Tag) error { + return database.DB.Create(tag).Error +} + +// UpdateTag updates an existing tag +func (s *TagService) UpdateTag(id uint64, updates map[string]interface{}) error { + result := database.DB.Model(&models.Tag{}).Where("id = ?", id).Updates(updates) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} + +// DeleteTag deletes a tag by ID +func (s *TagService) DeleteTag(id uint64) error { + result := database.DB.Delete(&models.Tag{}, id) + if result.Error != nil { + return result.Error + } + if result.RowsAffected == 0 { + return gorm.ErrRecordNotFound + } + return nil +} diff --git a/app/middlewares/admin_middleware.go b/app/middlewares/admin_middleware.go new file mode 100644 index 0000000..c4df3a9 --- /dev/null +++ b/app/middlewares/admin_middleware.go @@ -0,0 +1,49 @@ +package middlewares + +import ( + "net/http" + + "gobeyhan/database" + "gobeyhan/database/models" + + "github.com/gin-gonic/gin" +) + +// AdminMiddleware - Sadece admin rolündeki kullanıcıların erişimini sağlar +func AdminMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + // Get user_id from context (set by AuthMiddleware) + userID := c.GetString("user_id") + if userID == "" { + c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"}) + c.Abort() + return + } + + // Fetch user with roles + var user models.User + err := database.DB.Preload("Roles").Where("id = ?", userID).First(&user).Error + if err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + c.Abort() + return + } + + // Check if user has admin role + hasAdminRole := false + for _, role := range user.Roles { + if role.Name == "admin" { + hasAdminRole = true + break + } + } + + if !hasAdminRole { + c.JSON(http.StatusForbidden, gin.H{"error": "Admin access required"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/app/middlewares/auth_middleware.go b/app/middlewares/auth_middleware.go new file mode 100644 index 0000000..db07460 --- /dev/null +++ b/app/middlewares/auth_middleware.go @@ -0,0 +1,47 @@ +package middlewares + +import ( + "gobeyhan/app/settings/services" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +func AuthMiddleware(jwtService *services.JWTService) gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is required"}) + return + } + + tokenString := strings.Replace(authHeader, "Bearer ", "", 1) + claims, err := jwtService.ValidateToken(tokenString) + if err != nil { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token: " + err.Error()}) + return + } + + c.Set("user_id", claims.UserID) + c.Set("email", claims.Email) + c.Next() + } +} + +// OptionalAuthMiddleware checks for a token but doesn't abort if it's missing or invalid. +// It sets user_id if a valid token is present. +func OptionalAuthMiddleware(jwtService *services.JWTService) gin.HandlerFunc { + return func(c *gin.Context) { + authHeader := c.GetHeader("Authorization") + if authHeader != "" { + tokenString := strings.Replace(authHeader, "Bearer ", "", 1) + claims, err := jwtService.ValidateToken(tokenString) + if err == nil { + c.Set("user_id", claims.UserID) + c.Set("email", claims.Email) + } + } + c.Next() + } +} diff --git a/app/middlewares/dynamic_cors_middleware.go b/app/middlewares/dynamic_cors_middleware.go new file mode 100644 index 0000000..3dd486a --- /dev/null +++ b/app/middlewares/dynamic_cors_middleware.go @@ -0,0 +1,57 @@ +package middlewares + +import ( + "gobeyhan/app/settings/services" + "gobeyhan/config" + "log" + "net/http" + + "github.com/gin-gonic/gin" +) + +// DynamicCorsMiddleware - Database'den okunan CORS ayarlarıyla çalışan middleware +func DynamicCorsMiddleware(settingsService *services.SettingsService) gin.HandlerFunc { + return func(c *gin.Context) { + origin := c.Request.Header.Get("Origin") + + // If no origin header, skip CORS + if origin == "" { + c.Next() + return + } + + allowed, matchedEntry, matchedList, err := settingsService.CheckOrigin(origin) + if config.AppConfig != nil && config.AppConfig.CorsDebug { + log.Printf("cors_debug origin=%q allowed=%t matched_entry=%q matched_list=%q ip=%q", origin, allowed, matchedEntry, matchedList, c.ClientIP()) + } + if err != nil { + // On error, log and deny + c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{ + "error": "Failed to verify CORS policy", + }) + return + } + + if !allowed { + c.AbortWithStatusJSON(http.StatusForbidden, gin.H{ + "error": "Origin not allowed by CORS policy", + }) + return + } + + // Set CORS headers + c.Writer.Header().Set("Access-Control-Allow-Origin", origin) + c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") + c.Writer.Header().Set("Access-Control-Allow-Headers", "Origin, Content-Type, Accept, Authorization") + c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS") + c.Writer.Header().Set("Access-Control-Max-Age", "86400") // 24 hours + + // Handle preflight requests + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} diff --git a/app/middlewares/rate_limit_middleware.go b/app/middlewares/rate_limit_middleware.go new file mode 100644 index 0000000..3542e8b --- /dev/null +++ b/app/middlewares/rate_limit_middleware.go @@ -0,0 +1,200 @@ +package middlewares + +import ( + "fmt" + "net/http" + "strings" + "time" + + "gobeyhan/app/settings/services" + "gobeyhan/pkg/utils" + + "github.com/gin-gonic/gin" +) + +// RateLimitMiddleware creates a rate limiting middleware +func RateLimitMiddleware(maxRequests int64, duration time.Duration) gin.HandlerFunc { + cacheService := services.NewCacheService() + settingsService := services.NewSettingsService() + + return func(c *gin.Context) { + // Get client IP + clientIP := c.ClientIP() + path := c.Request.URL.Path + method := c.Request.Method + + // Skip checks for localhost (hardcoded safety) + if clientIP == "::1" || clientIP == "127.0.0.1" || clientIP == "localhost" { + fmt.Printf("%s[LOCALHOST BYPASS]%s IP: %s accessed %s %s\n", utils.ColorCyan, utils.ColorReset, clientIP, method, path) + c.Next() + return + } + + // 1. Check Blacklist from DB + blacklist, err := settingsService.GetActiveBlacklistOrigins() + if err == nil { + for _, blocked := range blacklist { + if blocked == clientIP || strings.Contains(blocked, clientIP) { + fmt.Printf("%s[BLACKLIST BLOCKED]%s IP: %s tried to access %s %s\n", utils.ColorRed, utils.ColorReset, clientIP, method, path) + c.JSON(http.StatusForbidden, gin.H{ + "error": "Access denied. Your IP is blacklisted.", + }) + c.Abort() + return + } + } + } + + // 2. Check Whitelist from DB (Skip Rate Limit) + whitelist, err := settingsService.GetActiveWhitelistOrigins() + if err == nil { + for _, allowed := range whitelist { + if allowed == clientIP || strings.Contains(allowed, clientIP) { + fmt.Printf("%s[WHITELIST ALLOWED]%s IP: %s accessed %s %s (Rate Limit Skipped)\n", utils.ColorGreen, utils.ColorReset, clientIP, method, path) + c.Next() + return + } + } + } + + key := clientIP + + // Increment counter + count, err := cacheService.IncrementRateLimit(key, duration) + if err != nil { + // If Redis is down, allow the request but log error + fmt.Printf("%s[REDIS ERROR]%s Could not increment rate limit for %s: %v\n", utils.ColorRed, utils.ColorReset, clientIP, err) + c.Next() + return + } + + remaining := maxRequests - count + if remaining < 0 { + remaining = 0 + } + + // Check if limit exceeded + if count > maxRequests { + fmt.Printf("%s[RATE LIMIT EXCEEDED]%s IP: %s - %s %s - Limit: %d\n", utils.ColorYellow, utils.ColorReset, clientIP, method, path, maxRequests) + c.JSON(http.StatusTooManyRequests, gin.H{ + "error": "Too many requests. Please try again later.", + }) + c.Abort() + return + } + + // Log normal access with remaining limit + fmt.Printf("[Rate Limit] IP: %s - %s %s - Used: %d/%d - Remaining: %d\n", clientIP, method, path, count, maxRequests, remaining) + + c.Next() + } +} + +// DynamicRateLimitMiddleware - Database'den ayarları okuyan rate limit middleware +func DynamicRateLimitMiddleware(settingName string, settingsService *services.SettingsService) gin.HandlerFunc { + cacheService := services.NewCacheService() + + return func(c *gin.Context) { + // Get client IP + clientIP := c.ClientIP() + path := c.Request.URL.Path + method := c.Request.Method + + // Skip checks for localhost + if clientIP == "::1" || clientIP == "127.0.0.1" || clientIP == "localhost" { + fmt.Printf("%s[LOCALHOST BYPASS]%s IP: %s accessed %s %s\n", utils.ColorCyan, utils.ColorReset, clientIP, method, path) + c.Next() + return + } + + // 1. Check Blacklist from DB + blacklist, err := settingsService.GetActiveBlacklistOrigins() + if err == nil { + for _, blocked := range blacklist { + if blocked == clientIP || strings.Contains(blocked, clientIP) { + fmt.Printf("%s[BLACKLIST BLOCKED]%s IP: %s tried to access %s %s\n", utils.ColorRed, utils.ColorReset, clientIP, method, path) + c.JSON(http.StatusForbidden, gin.H{ + "error": "Access denied. Your IP is blacklisted.", + }) + c.Abort() + return + } + } + } + + // 2. Check Whitelist from DB (Skip Rate Limit) + whitelist, err := settingsService.GetActiveWhitelistOrigins() + if err == nil { + for _, allowed := range whitelist { + if allowed == clientIP || strings.Contains(allowed, clientIP) { + fmt.Printf("%s[WHITELIST ALLOWED]%s IP: %s accessed %s %s (Rate Limit Skipped)\n", utils.ColorGreen, utils.ColorReset, clientIP, method, path) + c.Next() + return + } + } + } + + // Get rate limit settings from database/cache + setting, err := settingsService.GetRateLimitSettingByName(settingName) + if err != nil || setting == nil { + // If error or not found, use default and allow + c.Next() + return + } + + // Check if setting is active + if !setting.IsActive { + c.Next() + return + } + + key := settingName + ":" + clientIP + + // Increment counter + duration := time.Duration(setting.WindowSeconds) * time.Second + count, err := cacheService.IncrementRateLimit(key, duration) + if err != nil { + // If Redis is down, allow the request + c.Next() + return + } + + remaining := setting.MaxRequests - count + if remaining < 0 { + remaining = 0 + } + + // Check if limit exceeded + if count > setting.MaxRequests { + fmt.Printf("%s[RATE LIMIT EXCEEDED]%s IP: %s - %s %s - Limit: %d\n", utils.ColorYellow, utils.ColorReset, clientIP, method, path, setting.MaxRequests) + c.JSON(http.StatusTooManyRequests, gin.H{ + "error": "Too many requests. Please try again later.", + "limit": setting.MaxRequests, + "window": setting.WindowSeconds, + "retry_after": setting.WindowSeconds, + }) + c.Abort() + return + } + + // Log normal access with remaining limit + fmt.Printf("[Rate Limit] IP: %s - %s %s - Used: %d/%d - Remaining: %d\n", clientIP, method, path, count, setting.MaxRequests, remaining) + + c.Next() + } +} + +// LoginRateLimitMiddleware limits login attempts per IP +func LoginRateLimitMiddleware() gin.HandlerFunc { + return RateLimitMiddleware(5, 1*time.Minute) // 5 login attempts per minute +} + +// RegisterRateLimitMiddleware limits registration attempts per IP +func RegisterRateLimitMiddleware() gin.HandlerFunc { + return RateLimitMiddleware(3, 5*time.Minute) // 3 registration attempts per 5 minutes +} + +// APIRateLimitMiddleware general API rate limiting +func APIRateLimitMiddleware() gin.HandlerFunc { + return RateLimitMiddleware(100, 1*time.Minute) // 100 requests per minute +} diff --git a/app/routes/routes.go b/app/routes/routes.go new file mode 100644 index 0000000..1db6ad8 --- /dev/null +++ b/app/routes/routes.go @@ -0,0 +1,302 @@ +package routes + +import ( + accountHandlers "gobeyhan/app/account/handlers" + accountServices "gobeyhan/app/account/services" + blogHandlers "gobeyhan/app/blog/handlers" + blogServices "gobeyhan/app/blog/services" + "gobeyhan/app/middlewares" + settingsHandlers "gobeyhan/app/settings/handlers" + settingsServices "gobeyhan/app/settings/services" + adminPkg "gobeyhan/internal/handler/admin" + + "github.com/gin-gonic/gin" +) + +// SetupRoutes initializes all application routes +func SetupRoutes(r *gin.Engine) { + // ============================================ + // BLOG APP - Services & Handlers + // ============================================ + categoryService := blogServices.NewCategoryService() + tagService := blogServices.NewTagService() + postService := blogServices.NewPostService() + commentService := blogServices.NewCommentService() + categoryViewService := blogServices.NewCategoryViewService() + + categoryHandler := blogHandlers.NewCategoryHandler(categoryService) + tagHandler := blogHandlers.NewTagHandler(tagService) + postHandler := blogHandlers.NewPostHandler(postService) + commentHandler := blogHandlers.NewCommentHandler(commentService) + categoryViewHandler := blogHandlers.NewCategoryViewHandler(categoryViewService) + + // ============================================ + // ACCOUNT APP - Services & Handlers + // ============================================ + // ============================================ + // ACCOUNT APP - Services & Handlers + // ============================================ + userService := accountServices.NewUserService() + socialAccountService := accountServices.NewSocialAccountService() + roleService := accountServices.NewRoleService() + permissionService := accountServices.NewPermissionService() + + // Settings & Utils + settingsService := settingsServices.NewSettingsService() + jwtService := settingsServices.NewJWTService() + + // Handlers + userHandler := accountHandlers.NewUserHandler(userService) + authHandler := accountHandlers.NewAuthHandler(userService, jwtService) + oauthHandler := accountHandlers.NewOAuthHandler(userService, socialAccountService, jwtService) + // socialAccountHandler := accountHandlers.NewSocialAccountHandler(socialAccountService) + roleHandler := accountHandlers.NewRoleHandler(roleService) + permissionHandler := accountHandlers.NewPermissionHandler(permissionService) + + // ============================================ + // SETTINGS APP - Services & Handlers + // ============================================ + settingsHandler := settingsHandlers.NewSettingsHandler(settingsService) + + // ============================================ + // ADMIN UI ROUTES + // ============================================ + adminHandler := adminPkg.NewHandler() + r.GET("/admin/login", adminHandler.LoginPage) + r.POST("/admin/login", adminHandler.LoginPost) + r.GET("/admin", func(c *gin.Context) { + c.Redirect(301, "/admin/dashboard") + }) + r.GET("/admin/dashboard", adminHandler.Dashboard) + + // User CRUD + adminUserHandler := adminPkg.NewUserHandler() + r.GET("/admin/users", adminUserHandler.List) + r.GET("/admin/users/new", adminUserHandler.New) + r.POST("/admin/users", adminUserHandler.Create) + r.GET("/admin/users/:id/edit", adminUserHandler.Edit) + r.POST("/admin/users/:id", adminUserHandler.Update) + r.POST("/admin/users/:id/delete", adminUserHandler.Delete) + + // ======================================== + // SETTINGS UI ROUTES + // ======================================== + adminSettingsHandler := adminPkg.NewSettingsHandler() + + // Whitelist + r.GET("/admin/settings/whitelist", adminSettingsHandler.ListWhitelist) + r.GET("/admin/settings/whitelist/new", adminSettingsHandler.NewWhitelist) + r.POST("/admin/settings/whitelist", adminSettingsHandler.CreateWhitelist) + r.GET("/admin/settings/whitelist/:id/edit", adminSettingsHandler.EditWhitelist) + r.POST("/admin/settings/whitelist/:id", adminSettingsHandler.UpdateWhitelist) + r.POST("/admin/settings/whitelist/:id/delete", adminSettingsHandler.DeleteWhitelist) + + // Blacklist + r.GET("/admin/settings/blacklist", adminSettingsHandler.ListBlacklist) + r.GET("/admin/settings/blacklist/new", adminSettingsHandler.NewBlacklist) + r.POST("/admin/settings/blacklist", adminSettingsHandler.CreateBlacklist) + r.GET("/admin/settings/blacklist/:id/edit", adminSettingsHandler.EditBlacklist) + r.POST("/admin/settings/blacklist/:id", adminSettingsHandler.UpdateBlacklist) + r.POST("/admin/settings/blacklist/:id/delete", adminSettingsHandler.DeleteBlacklist) + + // Rate Limits + r.GET("/admin/settings/rate-limits", adminSettingsHandler.ListRateLimits) + r.GET("/admin/settings/rate-limits/:id/edit", adminSettingsHandler.EditRateLimit) + r.POST("/admin/settings/rate-limits/:id", adminSettingsHandler.UpdateRateLimit) + r.POST("/admin/settings/rate-limits/:id/delete", adminSettingsHandler.DeleteRateLimit) + + // ======================================== + // BLOG UI ROUTES + // ======================================== + adminBlogHandler := adminPkg.NewBlogHandler() + + r.GET("/admin/blog", adminBlogHandler.List) + r.GET("/admin/blog/new", adminBlogHandler.New) + r.POST("/admin/blog", adminBlogHandler.Create) + r.GET("/admin/blog/:id/edit", adminBlogHandler.Edit) + r.POST("/admin/blog/:id", adminBlogHandler.Update) + r.POST("/admin/blog/:id/delete", adminBlogHandler.Delete) + + // Categories + r.GET("/admin/blog/categories", adminBlogHandler.ListCategories) + r.GET("/admin/blog/categories/new", adminBlogHandler.NewCategory) + r.POST("/admin/blog/categories", adminBlogHandler.CreateCategory) + r.GET("/admin/blog/categories/:id/edit", adminBlogHandler.EditCategory) + r.POST("/admin/blog/categories/:id", adminBlogHandler.UpdateCategory) + r.POST("/admin/blog/categories/:id/delete", adminBlogHandler.DeleteCategory) + + // Tags + r.GET("/admin/blog/tags", adminBlogHandler.ListTags) + r.GET("/admin/blog/tags/new", adminBlogHandler.NewTag) + r.POST("/admin/blog/tags", adminBlogHandler.CreateTag) + r.GET("/admin/blog/tags/:id/edit", adminBlogHandler.EditTag) + r.POST("/admin/blog/tags/:id", adminBlogHandler.UpdateTag) + r.POST("/admin/blog/tags/:id/delete", adminBlogHandler.DeleteTag) + + // Comments + r.GET("/admin/blog/comments", adminBlogHandler.ListComments) + r.GET("/admin/blog/comments/:id/edit", adminBlogHandler.EditComment) + r.POST("/admin/blog/comments/:id", adminBlogHandler.UpdateComment) + r.POST("/admin/blog/comments/:id/delete", adminBlogHandler.DeleteComment) + + // Static files sharing + r.Static("/uploads", "./uploads") + + // ============================================ + // API v1 Group + // ============================================ + api := r.Group("/api/v1") + api.Use(middlewares.DynamicCorsMiddleware(settingsService)) + { + // ======================================== + // AUTH ENDPOINTS + // ======================================== + auth := api.Group("/auth") + { + // Basic Auth + auth.POST("/register", authHandler.Register) + auth.POST("/login", authHandler.Login) + auth.POST("/refresh", authHandler.RefreshToken) + auth.POST("/logout", authHandler.Logout) + + // OAuth + auth.GET("/google", oauthHandler.GoogleLogin) + auth.GET("/google/callback", oauthHandler.GoogleCallback) + auth.GET("/github", oauthHandler.GithubLogin) + auth.GET("/github/callback", oauthHandler.GithubCallback) + + // Protected + auth.GET("/me", middlewares.AuthMiddleware(jwtService), authHandler.GetCurrentUser) + } + + // ======================================== + // PUBLIC ENDPOINTS (Read-only) + // ======================================== + + // Blog - Categories + api.GET("/categories", categoryHandler.GetAllCategories) + api.GET("/categories/:slug", categoryHandler.GetCategoryBySlug) + api.POST("/categories/:id/view", categoryViewHandler.TrackCategoryView) + + // Blog - Tags + api.GET("/tags", tagHandler.GetAllTags) + api.GET("/tags/:slug", tagHandler.GetTagBySlug) + + // Blog - Posts + api.GET("/posts", postHandler.GetAllPosts) + api.GET("/posts/:slug", postHandler.GetPostBySlug) + + // Blog - Comments (separate route to avoid wildcard conflict) + api.GET("/comments/post/:postId", commentHandler.GetPostComments) + + // ======================================== + // AUTHENTICATED USER ENDPOINTS + // ======================================== + // NOTE: These routes require AuthMiddleware() + // Uncomment when authentication middleware is ready + + // user := api.Group("/user") + // user.Use(AuthMiddleware()) + // { + // // Blog - Comments (authenticated users can comment) + // user.POST("/comments/post/:postId", commentHandler.CreatePostComment) + // + // // Account - Social Accounts + // user.GET("/social-accounts", socialAccountHandler.GetUserSocialAccounts) + // user.DELETE("/social-accounts/:id", socialAccountHandler.DeleteSocialAccount) + // } + + // ======================================== + // ADMIN ENDPOINTS (Protected) + // ======================================== + // NOTE: These routes require AuthMiddleware() + AdminMiddleware() + + admin := api.Group("/admin") + admin.Use(middlewares.AuthMiddleware(jwtService), middlewares.AdminMiddleware()) + { + // ======================================== + // BLOG APP - Admin Routes + // ======================================== + + // Categories + admin.GET("/categories", categoryHandler.AdminGetAllCategories) + admin.GET("/categories/:id", categoryHandler.GetCategoryByID) + admin.POST("/categories", categoryHandler.CreateCategory) + admin.PUT("/categories/:id", categoryHandler.UpdateCategory) + admin.DELETE("/categories/:id", categoryHandler.DeleteCategory) + admin.GET("/categories/:id/views", categoryViewHandler.GetCategoryViewStats) + + // Tags + admin.GET("/tags", tagHandler.AdminGetAllTags) + admin.GET("/tags/:id", tagHandler.GetTagByID) + admin.POST("/tags", tagHandler.CreateTag) + admin.PUT("/tags/:id", tagHandler.UpdateTag) + admin.DELETE("/tags/:id", tagHandler.DeleteTag) + + // Posts + admin.GET("/posts", postHandler.AdminGetAllPosts) + admin.GET("/posts/:id", postHandler.GetPostByID) + admin.POST("/posts", postHandler.CreatePost) + admin.PUT("/posts/:id", postHandler.UpdatePost) + admin.DELETE("/posts/:id", postHandler.DeletePost) + + // Comments + admin.GET("/comments", commentHandler.AdminGetAllComments) + admin.GET("/comments/:id", commentHandler.AdminGetCommentByID) + admin.PUT("/comments/:id", commentHandler.AdminUpdateComment) + admin.DELETE("/comments/:id", commentHandler.AdminDeleteComment) + + // Category Views + admin.GET("/category-views", categoryViewHandler.AdminGetAllCategoryViews) + + // ======================================== + // ACCOUNT APP - Admin Routes + // ======================================== + + // Users + admin.GET("/users", userHandler.AdminGetAllUsers) + admin.GET("/users/:id", userHandler.AdminGetUserByID) + admin.POST("/users", userHandler.AdminCreateUser) + admin.PUT("/users/:id", userHandler.AdminUpdateUser) + admin.DELETE("/users/:id", userHandler.AdminDeleteUser) + admin.POST("/users/:id/restore", userHandler.AdminRestoreUser) + admin.POST("/users/:id/roles", userHandler.AdminAssignRole) + admin.DELETE("/users/:id/roles/:role_id", userHandler.AdminRemoveRole) + + // Roles + admin.GET("/roles", roleHandler.AdminGetAllRoles) + admin.GET("/roles/:id", roleHandler.AdminGetRoleByID) + admin.POST("/roles", roleHandler.AdminCreateRole) + admin.PUT("/roles/:id", roleHandler.AdminUpdateRole) + admin.DELETE("/roles/:id", roleHandler.AdminDeleteRole) + + // Permissions + admin.GET("/permissions", permissionHandler.AdminGetAllPermissions) + admin.POST("/permissions", permissionHandler.AdminCreatePermission) + + // ======================================== + // SETTINGS APP - Admin Routes + // ======================================== + + // CORS Whitelist + admin.GET("/cors/whitelist", settingsHandler.GetAllWhitelist) + admin.POST("/cors/whitelist", settingsHandler.CreateWhitelist) + admin.PUT("/cors/whitelist/:id", settingsHandler.UpdateWhitelist) + admin.DELETE("/cors/whitelist/:id", settingsHandler.DeleteWhitelist) + + // CORS Blacklist + admin.GET("/cors/blacklist", settingsHandler.GetAllBlacklist) + admin.POST("/cors/blacklist", settingsHandler.CreateBlacklist) + admin.PUT("/cors/blacklist/:id", settingsHandler.UpdateBlacklist) + admin.DELETE("/cors/blacklist/:id", settingsHandler.DeleteBlacklist) + + // CORS Cache + admin.POST("/cors/cache/invalidate", settingsHandler.InvalidateCorsCache) + + // Rate Limits + admin.GET("/rate-limits", settingsHandler.GetAllRateLimits) + admin.PUT("/rate-limits/:id", settingsHandler.UpdateRateLimit) + + } + } +} diff --git a/app/routes/routes.go.backup b/app/routes/routes.go.backup new file mode 100644 index 0000000..ea7abff --- /dev/null +++ b/app/routes/routes.go.backup @@ -0,0 +1,462 @@ +package routes + +import ( + "gobeyhan/app/middlewares" + "gobeyhan/app/services" + "net/http" + + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" +) + +func SetupRoutes(r *gin.Engine) { + jwtService := services.NewJWTService() + authService := services.NewAuthService() + authHandler := handlers.NewAuthHandler(authService) + + settingsService := services.NewSettingsService() + settingsHandler := handlers.NewSettingsHandler(settingsService) + + userManagementService := services.NewUserManagementService() + userManagementHandler := handlers.NewUserManagementHandler(userManagementService) + + avatarHandler := handlers.NewAvatarHandler() + profileHandler := handlers.NewProfileHandler() + + contactService := services.NewContactService() + contactHandler := handlers.NewContactHandler(contactService) + + tagService := services.NewTagService() + tagHandler := handlers.NewTagHandler(tagService) + + postCategoryService := services.NewPostCategoryService() + postCategoryHandler := handlers.NewPostCategoryHandler(postCategoryService) + + postTagService := services.NewPostTagService() + postTagHandler := handlers.NewPostTagHandler(postTagService) + + postService := services.NewPostService() + postHandler := handlers.NewPostHandler(postService) + + postCommentService := services.NewPostCommentService() + postCommentHandler := handlers.NewPostCommentHandler(postCommentService) + + postCategoryViewService := services.NewPostCategoryViewService() + postCategoryViewHandler := handlers.NewPostCategoryViewHandler(postCategoryViewService) + + homeService := services.NewHomeService() + homeHandler := handlers.NewHomeHandler(homeService) + + aboutService := services.NewAboutService() + aboutHandler := handlers.NewAboutHandler(aboutService) + + serviceService := services.NewServiceService() + serviceHandler := handlers.NewServiceHandler(serviceService) + + serviceTitleService := services.NewServiceTitleService() + serviceTitleHandler := handlers.NewServiceTitleHandler(serviceTitleService) + + siteInfoService := services.NewSiteInfoService() + siteInfoHandler := handlers.NewSiteInfoHandler(siteInfoService) + + bannerService := services.NewBannerService() + bannerHandler := handlers.NewBannerHandler(bannerService) + + siteSettingsService := services.NewSiteSettingsService() + siteSettingsHandler := handlers.NewSiteSettingsHandler(siteSettingsService) + + resumeService := services.NewResumeService() + resumeHandler := handlers.NewResumeHandler(resumeService) + + educationService := services.NewEducationService() + educationHandler := handlers.NewEducationHandler(educationService) + + experienceService := services.NewExperienceService() + experienceHandler := handlers.NewExperienceHandler(experienceService) + + skillService := services.NewSkillService() + skillHandler := handlers.NewSkillHandler(skillService) + + knowledgeService := services.NewKnowledgeService() + knowledgeHandler := handlers.NewKnowledgeHandler(knowledgeService) + + mainMenuService := services.NewMainMenuService() + mainMenuHandler := handlers.NewMainMenuHandler(mainMenuService) + + // Serve static files (uploaded avatars) + r.Static("/uploads", "./uploads") + + // Homepage + r.LoadHTMLGlob("web/*") + r.GET("/", func(c *gin.Context) { + c.HTML(http.StatusOK, "index.html", nil) + }) + + // Swagger route moved outside of v1 group to be accessible at /docs/index.html + r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + v1 := r.Group("/api/v1") + v1.Use(middlewares.APIRateLimitMiddleware()) // General API rate limiting + { + auth := v1.Group("/auth") + { + auth.POST("/register", middlewares.RegisterRateLimitMiddleware(), authHandler.Register) + auth.POST("/login", middlewares.LoginRateLimitMiddleware(), authHandler.Login) + auth.GET("/verify-email", authHandler.VerifyEmail) + auth.GET("/:provider", authHandler.BeginAuth) + auth.GET("/:provider/callback", authHandler.Callback) + auth.POST("/refresh", authHandler.Refresh) + + // Protected routes + protected := auth.Group("/") + protected.Use(middlewares.AuthMiddleware(jwtService)) + { + protected.GET("/me", authHandler.Me) + protected.GET("/validate", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "message": "Token is valid", + "user_id": c.GetString("user_id"), + "email": c.GetString("email"), + }) + }) + } + } + + // Contact endpoint (Public but can optionally use auth) + v1.POST("/contact", middlewares.OptionalAuthMiddleware(jwtService), contactHandler.CreateContact) + + // Public Tags Endpoint (Only active tags) + v1.GET("/tags", tagHandler.GetAllTags) + + // Public Post Categories + v1.GET("/post-categories", postCategoryHandler.GetAllPostCategories) + v1.GET("/post-categories/:slug", postCategoryHandler.GetPostCategoryBySlug) + v1.POST("/post-categories/:id/views", postCategoryViewHandler.TrackPostCategoryView) + + // Public Post Tags + v1.GET("/post-tags", postTagHandler.GetAllPostTags) + + // Public Posts + v1.GET("/posts", postHandler.GetAllPosts) + v1.GET("/posts/slug/:slug", postHandler.GetPostBySlug) + v1.GET("/posts/:id/comments", postCommentHandler.GetPostCommentsByPostID) + + // Public About Endpoints (Only active about entries) + v1.GET("/about", aboutHandler.GetAllAbout) + v1.GET("/about/active", aboutHandler.GetActiveAbout) + + // Public Services Endpoints (Only active services) + v1.GET("/services", serviceHandler.GetAllServices) + v1.GET("/services/:slug", serviceHandler.GetServiceBySlug) + + // Public Service Titles Endpoints (Only active service titles) + v1.GET("/service-titles", serviceTitleHandler.GetAllServiceTitles) + v1.GET("/service-titles/active", serviceTitleHandler.GetActiveServiceTitle) + + // Public Main Menu Endpoints (Only active entries) + v1.GET("/main-menu", mainMenuHandler.GetAllMainMenus) + v1.GET("/main-menu/active", mainMenuHandler.GetActiveMainMenu) + + // Public Site Info Endpoints (Only active entries) + v1.GET("/site-info", siteInfoHandler.GetAllSiteInfos) + v1.GET("/site-info/active", siteInfoHandler.GetActiveSiteInfo) + + // Public Banner Endpoints (Only active entries) + v1.GET("/banners", bannerHandler.GetAllBanners) + v1.GET("/banners/active", bannerHandler.GetActiveBanner) + + // Public Site Settings Endpoints (Only active entries) + v1.GET("/site-settings", siteSettingsHandler.GetAllSiteSettings) + v1.GET("/site-settings/active", siteSettingsHandler.GetActiveSiteSettings) + + // Public Homes Endpoints (Only active homes) + v1.GET("/homes", homeHandler.GetAllHomes) + v1.GET("/homes/:slug", homeHandler.GetHomeBySlug) + + // Public Resume Endpoints + v1.GET("/resumes", resumeHandler.GetAllResumes) + v1.GET("/resumes/active", resumeHandler.GetActiveResume) + v1.GET("/educations", educationHandler.GetAllEducations) + v1.GET("/experiences", experienceHandler.GetAllExperiences) + v1.GET("/skills", skillHandler.GetAllSkills) + v1.GET("/knowledges", knowledgeHandler.GetAllKnowledges) + + // User endpoints + user := v1.Group("/user") + user.Use(middlewares.AuthMiddleware(jwtService)) + { + // Avatar management + user.POST("/avatar", avatarHandler.UploadAvatar) + user.DELETE("/avatar", avatarHandler.DeleteAvatar) + } + + // Post comment creation (Auth required) + postAuth := v1.Group("/posts") + postAuth.Use(middlewares.AuthMiddleware(jwtService)) + { + postAuth.POST("/:id/comments", postCommentHandler.CreatePostComment) + } + + // Profile endpoints + profile := v1.Group("/profile") + profile.Use(middlewares.AuthMiddleware(jwtService)) + { + profile.GET("", profileHandler.GetProfile) + profile.PUT("", profileHandler.UpdateProfile) + profile.PUT("/password", profileHandler.ChangePassword) + profile.PUT("/email", profileHandler.ChangeEmail) + } + + // Settings endpoints (Admin only) + settings := v1.Group("/settings") + settings.Use(middlewares.AuthMiddleware(jwtService)) + settings.Use(middlewares.AdminMiddleware()) + { + // CORS Whitelist + corsWhitelist := settings.Group("/cors/whitelist") + { + corsWhitelist.GET("", settingsHandler.GetAllWhitelist) + corsWhitelist.POST("", settingsHandler.CreateWhitelist) + corsWhitelist.PUT("/:id", settingsHandler.UpdateWhitelist) + corsWhitelist.DELETE("/:id", settingsHandler.DeleteWhitelist) + } + + // CORS Blacklist + corsBlacklist := settings.Group("/cors/blacklist") + { + corsBlacklist.GET("", settingsHandler.GetAllBlacklist) + corsBlacklist.POST("", settingsHandler.CreateBlacklist) + corsBlacklist.PUT("/:id", settingsHandler.UpdateBlacklist) + corsBlacklist.DELETE("/:id", settingsHandler.DeleteBlacklist) + } + + // Rate Limit Settings + rateLimit := settings.Group("/ratelimit") + { + rateLimit.GET("", settingsHandler.GetAllRateLimits) + rateLimit.PUT("/:id", settingsHandler.UpdateRateLimit) + } + + // CORS Cache + settings.POST("/cors/cache/invalidate", settingsHandler.InvalidateCorsCache) + } + + // Admin - User Management + admin := v1.Group("/admin") + admin.Use(middlewares.AuthMiddleware(jwtService)) + admin.Use(middlewares.AdminMiddleware()) + { + users := admin.Group("/users") + { + users.GET("/search", userManagementHandler.SearchUsers) + users.GET("/deleted", userManagementHandler.GetDeletedUsers) // Yeni: Silinen kullanıcılar + users.GET("", userManagementHandler.GetAllUsers) + users.POST("", userManagementHandler.CreateUser) + users.GET("/:id", userManagementHandler.GetUserByID) + users.PUT("/:id", userManagementHandler.UpdateUser) + users.DELETE("/:id", userManagementHandler.DeleteUser) + users.POST("/:id/roles", userManagementHandler.AssignRoles) + users.DELETE("/:id/roles/:role", userManagementHandler.RemoveRole) + users.POST("/:id/restore", userManagementHandler.RestoreUser) // Yeni: Kullanıcıyı restore et + + // Avatar management for users (Admin) + users.POST("/:id/avatar", avatarHandler.AdminUploadAvatar) + } + + // Admin - Home Management + homes := admin.Group("/homes") + { + homes.GET("", homeHandler.AdminGetAllHomes) + homes.POST("", homeHandler.CreateHome) + homes.GET("/:id", homeHandler.AdminGetHomeByID) + homes.PUT("/:id", homeHandler.UpdateHome) + homes.DELETE("/:id", homeHandler.DeleteHome) + homes.POST("/:id/image", homeHandler.AdminUploadHomeImage) + } + + // Admin - Post Categories + postCategories := admin.Group("/post-categories") + { + postCategories.GET("", postCategoryHandler.AdminGetAllPostCategories) + postCategories.POST("", postCategoryHandler.CreatePostCategory) + postCategories.GET("/:id", postCategoryHandler.AdminGetPostCategoryByID) + postCategories.PUT("/:id", postCategoryHandler.UpdatePostCategory) + postCategories.DELETE("/:id", postCategoryHandler.DeletePostCategory) + } + + // Admin - Post Tags + postTags := admin.Group("/post-tags") + { + postTags.GET("", postTagHandler.AdminGetAllPostTags) + postTags.POST("", postTagHandler.CreatePostTag) + postTags.GET("/:id", postTagHandler.GetPostTagByID) + postTags.PUT("/:id", postTagHandler.UpdatePostTag) + postTags.DELETE("/:id", postTagHandler.DeletePostTag) + } + + // Admin - Posts + posts := admin.Group("/posts") + { + posts.GET("", postHandler.AdminGetAllPosts) + posts.POST("", postHandler.CreatePost) + posts.GET("/:id", postHandler.AdminGetPostByID) + posts.PUT("/:id", postHandler.UpdatePost) + posts.DELETE("/:id", postHandler.DeletePost) + } + + // Admin - Post Comments + postComments := admin.Group("/post-comments") + { + postComments.GET("", postCommentHandler.AdminGetAllPostComments) + postComments.GET("/:id", postCommentHandler.AdminGetPostCommentByID) + postComments.PUT("/:id", postCommentHandler.AdminUpdatePostComment) + postComments.DELETE("/:id", postCommentHandler.AdminDeletePostComment) + } + + // Admin - Post Category Views + postCategoryViews := admin.Group("/post-category-views") + { + postCategoryViews.GET("", postCategoryViewHandler.AdminGetPostCategoryViews) + } + + // Admin - About Management + about := admin.Group("/about") + { + about.GET("", aboutHandler.AdminGetAllAbout) + about.POST("", aboutHandler.CreateAbout) + about.GET("/:id", aboutHandler.AdminGetAboutByID) + about.PUT("/:id", aboutHandler.UpdateAbout) + about.DELETE("/:id", aboutHandler.DeleteAbout) + } + + // Admin - Service Management + servicesGroup := admin.Group("/services") + { + servicesGroup.GET("", serviceHandler.AdminGetAllServices) + servicesGroup.POST("", serviceHandler.CreateService) + servicesGroup.GET("/:id", serviceHandler.AdminGetServiceByID) + servicesGroup.PUT("/:id", serviceHandler.UpdateService) + servicesGroup.DELETE("/:id", serviceHandler.DeleteService) + } + + // Admin - Service Title Management + serviceTitles := admin.Group("/service-titles") + { + serviceTitles.GET("", serviceTitleHandler.AdminGetAllServiceTitles) + serviceTitles.POST("", serviceTitleHandler.CreateServiceTitle) + serviceTitles.GET("/:id", serviceTitleHandler.AdminGetServiceTitleByID) + serviceTitles.PUT("/:id", serviceTitleHandler.UpdateServiceTitle) + serviceTitles.DELETE("/:id", serviceTitleHandler.DeleteServiceTitle) + } + + // Admin - Site Info Management + siteInfo := admin.Group("/site-info") + { + siteInfo.GET("", siteInfoHandler.AdminGetAllSiteInfos) + siteInfo.POST("", siteInfoHandler.CreateSiteInfo) + siteInfo.GET("/:id", siteInfoHandler.AdminGetSiteInfoByID) + siteInfo.PUT("/:id", siteInfoHandler.UpdateSiteInfo) + siteInfo.DELETE("/:id", siteInfoHandler.DeleteSiteInfo) + } + + // Admin - Banner Management + banners := admin.Group("/banners") + { + banners.GET("", bannerHandler.AdminGetAllBanners) + banners.POST("", bannerHandler.CreateBanner) + banners.GET("/:id", bannerHandler.AdminGetBannerByID) + banners.PUT("/:id", bannerHandler.UpdateBanner) + banners.DELETE("/:id", bannerHandler.DeleteBanner) + } + + // Admin - Site Settings Management + siteSettings := admin.Group("/site-settings") + { + siteSettings.GET("", siteSettingsHandler.AdminGetAllSiteSettings) + siteSettings.POST("", siteSettingsHandler.CreateSiteSettings) + siteSettings.GET("/:id", siteSettingsHandler.AdminGetSiteSettingsByID) + siteSettings.PUT("/:id", siteSettingsHandler.UpdateSiteSettings) + siteSettings.DELETE("/:id", siteSettingsHandler.DeleteSiteSettings) + } + + // Admin - Resume Management + resumes := admin.Group("/resumes") + { + resumes.GET("", resumeHandler.AdminGetAllResumes) + resumes.POST("", resumeHandler.CreateResume) + resumes.GET("/:id", resumeHandler.AdminGetResumeByID) + resumes.PUT("/:id", resumeHandler.UpdateResume) + resumes.DELETE("/:id", resumeHandler.DeleteResume) + } + + // Admin - Education Management + educations := admin.Group("/educations") + { + educations.GET("", educationHandler.AdminGetAllEducations) + educations.POST("", educationHandler.CreateEducation) + educations.GET("/:id", educationHandler.AdminGetEducationByID) + educations.PUT("/:id", educationHandler.UpdateEducation) + educations.DELETE("/:id", educationHandler.DeleteEducation) + } + + // Admin - Experience Management + experiences := admin.Group("/experiences") + { + experiences.GET("", experienceHandler.AdminGetAllExperiences) + experiences.POST("", experienceHandler.CreateExperience) + experiences.GET("/:id", experienceHandler.AdminGetExperienceByID) + experiences.PUT("/:id", experienceHandler.UpdateExperience) + experiences.DELETE("/:id", experienceHandler.DeleteExperience) + } + + // Admin - Skill Management + skills := admin.Group("/skills") + { + skills.GET("", skillHandler.AdminGetAllSkills) + skills.POST("", skillHandler.CreateSkill) + skills.GET("/:id", skillHandler.AdminGetSkillByID) + skills.PUT("/:id", skillHandler.UpdateSkill) + skills.DELETE("/:id", skillHandler.DeleteSkill) + } + + // Admin - Knowledge Management + knowledges := admin.Group("/knowledges") + { + knowledges.GET("", knowledgeHandler.AdminGetAllKnowledges) + knowledges.POST("", knowledgeHandler.CreateKnowledge) + knowledges.GET("/:id", knowledgeHandler.AdminGetKnowledgeByID) + knowledges.PUT("/:id", knowledgeHandler.UpdateKnowledge) + knowledges.DELETE("/:id", knowledgeHandler.DeleteKnowledge) + } + + // Admin - Main Menu Management + mainMenu := admin.Group("/main-menu") + { + mainMenu.GET("", mainMenuHandler.AdminGetAllMainMenus) + mainMenu.POST("", mainMenuHandler.CreateMainMenu) + mainMenu.GET("/:id", mainMenuHandler.AdminGetMainMenuByID) + mainMenu.PUT("/:id", mainMenuHandler.UpdateMainMenu) + mainMenu.DELETE("/:id", mainMenuHandler.DeleteMainMenu) + } + + // Admin - Contact Management + contacts := admin.Group("/contacts") + { + contacts.GET("", contactHandler.GetAllContacts) + contacts.GET("/:id", contactHandler.GetContactByID) + contacts.DELETE("/:id", contactHandler.DeleteContact) + } + + // Admin - Tag Management + tags := admin.Group("/tags") + { + tags.GET("", tagHandler.AdminGetAllTags) + tags.POST("", tagHandler.CreateTag) + tags.GET("/:id", tagHandler.GetTagByID) + tags.PUT("/:id", tagHandler.UpdateTag) + tags.DELETE("/:id", tagHandler.DeleteTag) + } + } + } +} diff --git a/app/settings/handlers/settings_handler.go b/app/settings/handlers/settings_handler.go new file mode 100644 index 0000000..d741105 --- /dev/null +++ b/app/settings/handlers/settings_handler.go @@ -0,0 +1,264 @@ +package handlers + +import ( + "gobeyhan/app/settings/services" + "gobeyhan/database/models" + "net/http" + + "github.com/gin-gonic/gin" +) + +type SettingsHandler struct { + service *services.SettingsService +} + +func NewSettingsHandler(service *services.SettingsService) *SettingsHandler { + return &SettingsHandler{service: service} +} + +// GetAllWhitelist godoc +// @Summary Get all CORS whitelist entries (Admin) +// @Description Get all CORS whitelist origins +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.CorsWhitelist +// @Router /api/v1/admin/cors/whitelist [get] +func (h *SettingsHandler) GetAllWhitelist(c *gin.Context) { + whitelist, err := h.service.GetAllCorsWhitelist() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": whitelist}) +} + +// CreateWhitelist godoc +// @Summary Create CORS whitelist entry (Admin) +// @Description Add a new origin to CORS whitelist +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param whitelist body models.CorsWhitelist true "Whitelist object" +// @Success 201 {object} models.CorsWhitelist +// @Router /api/v1/admin/cors/whitelist [post] +func (h *SettingsHandler) CreateWhitelist(c *gin.Context) { + var input models.CorsWhitelist + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.CreateCorsWhitelist(&input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": input}) +} + +// UpdateWhitelist godoc +// @Summary Update CORS whitelist entry (Admin) +// @Description Update an existing CORS whitelist entry +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Whitelist ID" +// @Param whitelist body models.CorsWhitelist true "Whitelist object" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/cors/whitelist/{id} [put] +func (h *SettingsHandler) UpdateWhitelist(c *gin.Context) { + id := c.Param("id") + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateCorsWhitelist(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Whitelist updated successfully"}) +} + +// DeleteWhitelist godoc +// @Summary Delete CORS whitelist entry (Admin) +// @Description Delete a CORS whitelist entry +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Whitelist ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/cors/whitelist/{id} [delete] +func (h *SettingsHandler) DeleteWhitelist(c *gin.Context) { + id := c.Param("id") + + if err := h.service.DeleteCorsWhitelist(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Whitelist deleted successfully"}) +} + +// GetAllBlacklist godoc +// @Summary Get all CORS blacklist entries (Admin) +// @Description Get all CORS blacklist origins +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.CorsBlacklist +// @Router /api/v1/admin/cors/blacklist [get] +func (h *SettingsHandler) GetAllBlacklist(c *gin.Context) { + blacklist, err := h.service.GetAllCorsBlacklist() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": blacklist}) +} + +// CreateBlacklist godoc +// @Summary Create CORS blacklist entry (Admin) +// @Description Add a new origin to CORS blacklist +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param blacklist body models.CorsBlacklist true "Blacklist object" +// @Success 201 {object} models.CorsBlacklist +// @Router /api/v1/admin/cors/blacklist [post] +func (h *SettingsHandler) CreateBlacklist(c *gin.Context) { + var input models.CorsBlacklist + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.CreateCorsBlacklist(&input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusCreated, gin.H{"data": input}) +} + +// UpdateBlacklist godoc +// @Summary Update CORS blacklist entry (Admin) +// @Description Update an existing CORS blacklist entry +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Blacklist ID" +// @Param blacklist body models.CorsBlacklist true "Blacklist object" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/cors/blacklist/{id} [put] +func (h *SettingsHandler) UpdateBlacklist(c *gin.Context) { + id := c.Param("id") + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateCorsBlacklist(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Blacklist updated successfully"}) +} + +// DeleteBlacklist godoc +// @Summary Delete CORS blacklist entry (Admin) +// @Description Delete a CORS blacklist entry +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Blacklist ID" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/cors/blacklist/{id} [delete] +func (h *SettingsHandler) DeleteBlacklist(c *gin.Context) { + id := c.Param("id") + + if err := h.service.DeleteCorsBlacklist(id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Blacklist deleted successfully"}) +} + +// GetAllRateLimits godoc +// @Summary Get all rate limit settings (Admin) +// @Description Get all rate limit configurations +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {array} models.RateLimitSetting +// @Router /api/v1/admin/rate-limits [get] +func (h *SettingsHandler) GetAllRateLimits(c *gin.Context) { + settings, err := h.service.GetAllRateLimitSettings() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"data": settings}) +} + +// UpdateRateLimit godoc +// @Summary Update rate limit setting (Admin) +// @Description Update an existing rate limit configuration +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Param id path int true "Rate Limit ID" +// @Param setting body models.RateLimitSetting true "Rate limit object" +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/rate-limits/{id} [put] +func (h *SettingsHandler) UpdateRateLimit(c *gin.Context) { + id := c.Param("id") + + var input map[string]interface{} + if err := c.ShouldBindJSON(&input); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + if err := h.service.UpdateRateLimitSetting(id, input); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{"message": "Rate limit updated successfully"}) +} + +// InvalidateCorsCache godoc +// @Summary Invalidate CORS cache (Admin) +// @Description Clear the CORS cache to force reload from database +// @Tags admin,settings +// @Accept json +// @Produce json +// @Security BearerAuth +// @Success 200 {object} map[string]string +// @Router /api/v1/admin/cors/cache/invalidate [post] +func (h *SettingsHandler) InvalidateCorsCache(c *gin.Context) { + h.service.InvalidateCorsCache() + c.JSON(http.StatusOK, gin.H{"message": "CORS cache invalidated successfully"}) +} diff --git a/app/settings/services/cache_service.go b/app/settings/services/cache_service.go new file mode 100644 index 0000000..a8c1fcb --- /dev/null +++ b/app/settings/services/cache_service.go @@ -0,0 +1,204 @@ +package services + +import ( + "encoding/json" + "gobeyhan/database" + "gobeyhan/database/models" + "time" + + "github.com/redis/go-redis/v9" +) + +type CacheService struct{} + +func NewCacheService() *CacheService { + return &CacheService{} +} + +// User Cache +func (s *CacheService) SetUser(userID string, user *models.User, expiration time.Duration) error { + userData, err := json.Marshal(user) + if err != nil { + return err + } + return database.Set("user:"+userID, userData, expiration) +} + +func (s *CacheService) GetUser(userID string) (*models.User, error) { + data, err := database.Get("user:" + userID) + if err != nil { + return nil, err + } + + var user models.User + err = json.Unmarshal([]byte(data), &user) + if err != nil { + return nil, err + } + + return &user, nil +} + +func (s *CacheService) DeleteUser(userID string) error { + return database.Delete("user:" + userID) +} + +// Session Management +func (s *CacheService) SetSession(token string, userID string, expiration time.Duration) error { + return database.Set("session:"+token, userID, expiration) +} + +func (s *CacheService) GetSession(token string) (string, error) { + return database.Get("session:" + token) +} + +func (s *CacheService) DeleteSession(token string) error { + return database.Delete("session:" + token) +} + +// Rate Limiting +func (s *CacheService) IncrementRateLimit(key string, expiration time.Duration) (int64, error) { + count, err := database.Increment("ratelimit:" + key) + if err != nil { + return 0, err + } + + // Set expiration only for first increment + if count == 1 { + database.Expire("ratelimit:"+key, expiration) + } + + return count, nil +} + +func (s *CacheService) GetRateLimit(key string) (string, error) { + return database.Get("ratelimit:" + key) +} + +// Token Blacklist (for logout) +func (s *CacheService) BlacklistToken(token string, expiration time.Duration) error { + return database.Set("blacklist:"+token, "1", expiration) +} + +func (s *CacheService) IsTokenBlacklisted(token string) (bool, error) { + return database.Exists("blacklist:" + token) +} + +// Email Verification Token Cache +func (s *CacheService) SetEmailVerification(email string, token string, expiration time.Duration) error { + return database.Set("email_verify:"+email, token, expiration) +} + +func (s *CacheService) GetEmailVerification(email string) (string, error) { + return database.Get("email_verify:" + email) +} + +func (s *CacheService) DeleteEmailVerification(email string) error { + return database.Delete("email_verify:" + email) +} + +// Password Reset Token Cache +func (s *CacheService) SetPasswordReset(email string, token string, expiration time.Duration) error { + return database.Set("password_reset:"+email, token, expiration) +} + +func (s *CacheService) GetPasswordReset(email string) (string, error) { + return database.Get("password_reset:" + email) +} + +func (s *CacheService) DeletePasswordReset(email string) error { + return database.Delete("password_reset:" + email) +} + +// CORS Whitelist Cache +func (s *CacheService) SetCorsWhitelist(origins []string, expiration time.Duration) error { + data, err := json.Marshal(origins) + if err != nil { + return err + } + return database.Set("cors:whitelist", data, expiration) +} + +func (s *CacheService) GetCorsWhitelist() ([]string, error) { + data, err := database.Get("cors:whitelist") + if err != nil { + if err == redis.Nil { + return nil, nil + } + return nil, err + } + + var origins []string + err = json.Unmarshal([]byte(data), &origins) + if err != nil { + return nil, err + } + + return origins, nil +} + +func (s *CacheService) InvalidateCorsWhitelist() error { + return database.Delete("cors:whitelist") +} + +// CORS Blacklist Cache +func (s *CacheService) SetCorsBlacklist(origins []string, expiration time.Duration) error { + data, err := json.Marshal(origins) + if err != nil { + return err + } + return database.Set("cors:blacklist", data, expiration) +} + +func (s *CacheService) GetCorsBlacklist() ([]string, error) { + data, err := database.Get("cors:blacklist") + if err != nil { + if err == redis.Nil { + return nil, nil + } + return nil, err + } + + var origins []string + err = json.Unmarshal([]byte(data), &origins) + if err != nil { + return nil, err + } + + return origins, nil +} + +func (s *CacheService) InvalidateCorsBlacklist() error { + return database.Delete("cors:blacklist") +} + +// Rate Limit Settings Cache +func (s *CacheService) SetRateLimitSettings(settings map[string]*models.RateLimitSetting, expiration time.Duration) error { + data, err := json.Marshal(settings) + if err != nil { + return err + } + return database.Set("settings:ratelimit", data, expiration) +} + +func (s *CacheService) GetRateLimitSettings() (map[string]*models.RateLimitSetting, error) { + data, err := database.Get("settings:ratelimit") + if err != nil { + if err == redis.Nil { + return nil, nil + } + return nil, err + } + + var settings map[string]*models.RateLimitSetting + err = json.Unmarshal([]byte(data), &settings) + if err != nil { + return nil, err + } + + return settings, nil +} + +func (s *CacheService) InvalidateRateLimitSettings() error { + return database.Delete("settings:ratelimit") +} diff --git a/app/settings/services/jwt_service.go b/app/settings/services/jwt_service.go new file mode 100644 index 0000000..5ec2dc1 --- /dev/null +++ b/app/settings/services/jwt_service.go @@ -0,0 +1,173 @@ +package services + +import ( + "errors" + "fmt" + "time" + + "gobeyhan/config" + "gobeyhan/database/models" + + "github.com/golang-jwt/jwt/v5" +) + +type JWTClaim struct { + UserID string `json:"user_id"` + Email string `json:"email"` + Permissions []string `json:"permissions,omitempty"` + jwt.RegisteredClaims +} + +type JWTService struct{} + +func NewJWTService() *JWTService { + return &JWTService{} +} + +func (s *JWTService) GenerateToken(user models.User) (string, error) { + if config.AppConfig == nil || config.AppConfig.JWTSecret == "" { + return "", errors.New("jwt secret not configured") + } + + expirationTime := time.Now().Add(24 * time.Hour) + claims := &JWTClaim{ + UserID: fmt.Sprintf("%d", user.ID), + Email: user.Email, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + Issuer: "gauth-central", + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(config.AppConfig.JWTSecret)) +} + +func (s *JWTService) GenerateTokenPair(user models.User) (string, string, error) { + if config.AppConfig == nil || config.AppConfig.JWTSecret == "" { + return "", "", errors.New("jwt secret not configured") + } + + // Extract permissions + permissionMap := make(map[string]bool) + for _, role := range user.Roles { + for _, perm := range role.Permissions { + permissionMap[perm.Name] = true + } + } + var permissions []string + for p := range permissionMap { + permissions = append(permissions, p) + } + + // Access Token + expirationMinutes := 120 // Default fallback + if config.AppConfig.AccessTokenExpireMinutes > 0 { + expirationMinutes = config.AppConfig.AccessTokenExpireMinutes + } + accessTokenExp := time.Now().Add(time.Duration(expirationMinutes) * time.Minute) + accessClaims := &JWTClaim{ + UserID: fmt.Sprintf("%d", user.ID), + Email: user.Email, + Permissions: permissions, + RegisteredClaims: jwt.RegisteredClaims{ + Subject: fmt.Sprintf("%d", user.ID), // CRITICAL: Standard "sub" claim + ExpiresAt: jwt.NewNumericDate(accessTokenExp), + Issuer: "gauth-central", + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims) + signedAccessToken, err := accessToken.SignedString([]byte(config.AppConfig.JWTSecret)) + if err != nil { + return "", "", err + } + + // Refresh Token + expirationDays := 30 // Default fallback + if config.AppConfig.RefreshTokenExpireDays > 0 { + expirationDays = config.AppConfig.RefreshTokenExpireDays + } + refreshTokenExp := time.Now().Add(time.Duration(expirationDays) * 24 * time.Hour) + refreshClaims := &JWTClaim{ + UserID: fmt.Sprintf("%d", user.ID), + Email: user.Email, + Permissions: nil, // Refresh token doesn't need permissions usually, or keep them if needed + RegisteredClaims: jwt.RegisteredClaims{ + Subject: fmt.Sprintf("%d", user.ID), // CRITICAL: Standard "sub" claim + ExpiresAt: jwt.NewNumericDate(refreshTokenExp), + Issuer: "gauth-central", + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims) + signedRefreshToken, err := refreshToken.SignedString([]byte(config.AppConfig.JWTSecret)) + if err != nil { + return "", "", err + } + + return signedAccessToken, signedRefreshToken, nil +} + +func (s *JWTService) ValidateToken(signedToken string) (*JWTClaim, error) { + if config.AppConfig == nil || config.AppConfig.JWTSecret == "" { + return nil, errors.New("jwt secret not configured") + } + + token, err := jwt.ParseWithClaims( + signedToken, + &JWTClaim{}, + func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, errors.New("unexpected signing method") + } + return []byte(config.AppConfig.JWTSecret), nil + }, + ) + + if err != nil { + return nil, err + } + + claims, ok := token.Claims.(*JWTClaim) + if !ok { + return nil, errors.New("could not parse claims") + } + + if claims.ExpiresAt.Time.Before(time.Now()) { + return nil, errors.New("token expired") + } + + return claims, nil +} + +// GenerateVerificationToken generates a JWT token for email verification (24 hours expiry) +func (s *JWTService) GenerateVerificationToken(userID, email string) (string, error) { + if config.AppConfig == nil || config.AppConfig.JWTSecret == "" { + return "", errors.New("jwt secret not configured") + } + + expirationTime := time.Now().Add(24 * time.Hour) + claims := &JWTClaim{ + UserID: userID, + Email: email, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(expirationTime), + Issuer: "gauth-central", + IssuedAt: jwt.NewNumericDate(time.Now()), + }, + } + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + return token.SignedString([]byte(config.AppConfig.JWTSecret)) +} + +// ValidateVerificationToken validates a verification token and returns user ID and email +func (s *JWTService) ValidateVerificationToken(tokenString string) (string, string, error) { + claims, err := s.ValidateToken(tokenString) + if err != nil { + return "", "", err + } + return claims.UserID, claims.Email, nil +} diff --git a/app/settings/services/settings_service.go b/app/settings/services/settings_service.go new file mode 100644 index 0000000..ba0fac5 --- /dev/null +++ b/app/settings/services/settings_service.go @@ -0,0 +1,391 @@ +package services + +import ( + "errors" + "gobeyhan/database" + "gobeyhan/database/models" + "log" + "net/url" + "strings" + "time" + + "gorm.io/gorm" +) + +type SettingsService struct { + cacheService *CacheService +} + +func NewSettingsService() *SettingsService { + return &SettingsService{ + cacheService: NewCacheService(), + } +} + +// ==================== CORS WHITELIST ==================== + +func (s *SettingsService) GetAllCorsWhitelist() ([]models.CorsWhitelist, error) { + var whitelists []models.CorsWhitelist + err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&whitelists).Error + return whitelists, err +} + +func (s *SettingsService) GetActiveWhitelistOrigins() ([]string, error) { + // Try cache first + cached, err := s.cacheService.GetCorsWhitelist() + if err == nil && cached != nil { + return cached, nil + } + + origins, err := s.getActiveWhitelistOriginsFromDB() + if err != nil { + return nil, err + } + + // Cache for 1 hour + s.cacheService.SetCorsWhitelist(origins, 1*time.Hour) + + return origins, nil +} + +var ErrCorsOriginExists = errors.New("cors origin already exists") + +func (s *SettingsService) CreateCorsWhitelist(whitelist *models.CorsWhitelist) error { + var existing models.CorsWhitelist + err := database.DB.Where("LOWER(origin) = LOWER(?)", whitelist.Origin).First(&existing).Error + if err == nil { + if existing.IsActive { + return ErrCorsOriginExists + } + + updates := map[string]interface{}{ + "is_active": true, + "description": whitelist.Description, + "created_by": whitelist.CreatedBy, + } + err = database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", existing.ID).Updates(updates).Error + if err != nil { + return err + } + s.InvalidateCorsCache() + return nil + } + + if !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + err = database.DB.Create(whitelist).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) UpdateCorsWhitelist(id string, updates map[string]interface{}) error { + err := database.DB.Model(&models.CorsWhitelist{}).Where("id = ?", id).Updates(updates).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) DeleteCorsWhitelist(id string) error { + err := database.DB.Delete(&models.CorsWhitelist{}, "id = ?", id).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) GetCorsWhitelistByID(id uint64) (*models.CorsWhitelist, error) { + var item models.CorsWhitelist + err := database.DB.First(&item, id).Error + if err != nil { + return nil, err + } + return &item, nil +} + +// ==================== CORS BLACKLIST ==================== + +func (s *SettingsService) GetAllCorsBlacklist() ([]models.CorsBlacklist, error) { + var blacklists []models.CorsBlacklist + err := database.DB.Where("is_active = ?", true).Order("created_at DESC").Find(&blacklists).Error + return blacklists, err +} + +func (s *SettingsService) GetActiveBlacklistOrigins() ([]string, error) { + // Try cache first + cached, err := s.cacheService.GetCorsBlacklist() + if err == nil && cached != nil { + return cached, nil + } + + origins, err := s.getActiveBlacklistOriginsFromDB() + if err != nil { + return nil, err + } + + // Cache for 1 hour + s.cacheService.SetCorsBlacklist(origins, 1*time.Hour) + + return origins, nil +} + +func (s *SettingsService) CreateCorsBlacklist(blacklist *models.CorsBlacklist) error { + err := database.DB.Create(blacklist).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) UpdateCorsBlacklist(id string, updates map[string]interface{}) error { + err := database.DB.Model(&models.CorsBlacklist{}).Where("id = ?", id).Updates(updates).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) DeleteCorsBlacklist(id string) error { + err := database.DB.Delete(&models.CorsBlacklist{}, "id = ?", id).Error + if err != nil { + return err + } + + // Invalidate cache + s.InvalidateCorsCache() + return nil +} + +func (s *SettingsService) GetCorsBlacklistByID(id uint64) (*models.CorsBlacklist, error) { + var item models.CorsBlacklist + err := database.DB.First(&item, id).Error + if err != nil { + return nil, err + } + return &item, nil +} + +// ==================== RATE LIMIT SETTINGS ==================== + +func (s *SettingsService) GetAllRateLimitSettings() ([]models.RateLimitSetting, error) { + var settings []models.RateLimitSetting + err := database.DB.Order("name ASC").Find(&settings).Error + return settings, err +} + +func (s *SettingsService) GetRateLimitSettingsMap() (map[string]*models.RateLimitSetting, error) { + // Try cache first + cached, err := s.cacheService.GetRateLimitSettings() + if err == nil && cached != nil { + return cached, nil + } + + // Fetch from database + var settings []models.RateLimitSetting + err = database.DB.Where("is_active = ?", true).Find(&settings).Error + if err != nil { + return nil, err + } + + settingsMap := make(map[string]*models.RateLimitSetting) + for i := range settings { + settingsMap[settings[i].Name] = &settings[i] + } + + // Cache for 1 hour + s.cacheService.SetRateLimitSettings(settingsMap, 1*time.Hour) + + return settingsMap, nil +} + +func (s *SettingsService) GetRateLimitSettingByName(name string) (*models.RateLimitSetting, error) { + settingsMap, err := s.GetRateLimitSettingsMap() + if err != nil { + return nil, err + } + + setting, exists := settingsMap[name] + if !exists { + return nil, nil + } + + return setting, nil +} + +func (s *SettingsService) UpdateRateLimitSetting(id string, updates map[string]interface{}) error { + err := database.DB.Model(&models.RateLimitSetting{}).Where("id = ?", id).Updates(updates).Error + if err != nil { + return err + } + + // Invalidate cache + s.cacheService.InvalidateRateLimitSettings() + return nil +} + +func (s *SettingsService) GetRateLimitSettingByID(id uint64) (*models.RateLimitSetting, error) { + var item models.RateLimitSetting + err := database.DB.First(&item, id).Error + if err != nil { + return nil, err + } + return &item, nil +} + +func (s *SettingsService) DeleteRateLimitSetting(id string) error { + err := database.DB.Delete(&models.RateLimitSetting{}, "id = ?", id).Error + if err != nil { + return err + } + + // Invalidate cache + s.cacheService.InvalidateRateLimitSettings() + return nil +} + +// Invalidate CORS caches (whitelist + blacklist) +func (s *SettingsService) InvalidateCorsCache() { + s.cacheService.InvalidateCorsWhitelist() + s.cacheService.InvalidateCorsBlacklist() + log.Println("cors_cache_invalidated") +} + +// Check if origin is allowed +func (s *SettingsService) IsOriginAllowed(origin string) (bool, error) { + allowed, _, _, err := s.CheckOrigin(origin) + return allowed, err +} + +// CheckOrigin returns decision details for debug logging. +func (s *SettingsService) CheckOrigin(origin string) (bool, string, string, error) { + // Check blacklist first + blacklist, err := s.GetActiveBlacklistOrigins() + if err != nil { + return false, "", "", err + } + + for _, blocked := range blacklist { + if originMatchesEntry(origin, blocked) { + return false, blocked, "blacklist", nil + } + } + + // Fallback: refresh blacklist on miss (stale cache protection) + freshBlacklist, err := s.getActiveBlacklistOriginsFromDB() + if err != nil { + return false, "", "", err + } + if len(freshBlacklist) != 0 { + s.cacheService.SetCorsBlacklist(freshBlacklist, 1*time.Hour) + } + for _, blocked := range freshBlacklist { + if originMatchesEntry(origin, blocked) { + return false, blocked, "blacklist", nil + } + } + + // Check whitelist + whitelist, err := s.GetActiveWhitelistOrigins() + if err != nil { + return false, "", "", err + } + + for _, allowed := range whitelist { + if allowed == "*" || originMatchesEntry(origin, allowed) { + return true, allowed, "whitelist", nil + } + } + + // Fallback: refresh whitelist on miss (stale cache protection) + freshWhitelist, err := s.getActiveWhitelistOriginsFromDB() + if err != nil { + return false, "", "", err + } + if len(freshWhitelist) != 0 { + s.cacheService.SetCorsWhitelist(freshWhitelist, 1*time.Hour) + } + for _, allowed := range freshWhitelist { + if allowed == "*" || originMatchesEntry(origin, allowed) { + return true, allowed, "whitelist", nil + } + } + + return false, "", "whitelist", nil +} + +func (s *SettingsService) getActiveWhitelistOriginsFromDB() ([]string, error) { + var whitelists []models.CorsWhitelist + err := database.DB.Where("is_active = ?", true).Find(&whitelists).Error + if err != nil { + return nil, err + } + + origins := make([]string, len(whitelists)) + for i, w := range whitelists { + origins[i] = w.Origin + } + + return origins, nil +} + +func (s *SettingsService) getActiveBlacklistOriginsFromDB() ([]string, error) { + var blacklists []models.CorsBlacklist + err := database.DB.Where("is_active = ?", true).Find(&blacklists).Error + if err != nil { + return nil, err + } + + origins := make([]string, len(blacklists)) + for i, b := range blacklists { + origins[i] = b.Origin + } + + return origins, nil +} + +func originMatchesEntry(origin string, entry string) bool { + origin = strings.TrimSpace(origin) + entry = strings.TrimSpace(entry) + if origin == "" || entry == "" { + return false + } + + originLower := strings.ToLower(origin) + entryLower := strings.ToLower(entry) + if strings.Contains(entryLower, "://") { + return originLower == entryLower + } + + parsed, err := url.Parse(originLower) + if err != nil || parsed.Host == "" { + return false + } + + hostLower := strings.ToLower(parsed.Host) + if entryLower == hostLower { + return true + } + + // Allow entries like "127.0.0.1" to match any port + hostOnly := strings.Split(hostLower, ":")[0] + return entryLower == hostOnly +} diff --git a/client.http b/client.http new file mode 100644 index 0000000..4b3f115 --- /dev/null +++ b/client.http @@ -0,0 +1,59 @@ +@baseUrl = http://localhost:8080/api/v1 +@contentType = application/json +@token = eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyIiwiZW1haWwiOiJiZXloYW5AYmV5aGFuLmRldiIsImlzcyI6ImdhdXRoLWNlbnRyYWwiLCJleHAiOjE3NzA4NTY0MzcsImlhdCI6MTc3MDg0OTIzN30.z5iEjj7rgPy-SODJFN-PeEshAof0hYeqqrCqDUgaUuU +### Register +POST {{baseUrl}}/auth/register +Content-Type: {{contentType}} + +{ + "email": "beyhan@beyhan.dev", + "password": "1923btO**", + "username": "Beyhan Oğur" +} + +### Login (Admin) +# Default Admin Credentials: +# Email: admin@gauth.local +# Password: Admin@123 +POST {{baseUrl}}/auth/login +Content-Type: {{contentType}} + +{ + "email": "admin@gauth.local", + "password": "Admin@123" +} + +### Login (User) +POST {{baseUrl}}/auth/login +Content-Type: {{contentType}} + +{ + "email": "beyhan@beyhan.dev", + "password": "1923btO**" +} +### Login (User) +POST {{baseUrl}}/auth/login +Content-Type: {{contentType}} + +{ + "email": "ares@ares.com", + "password": "1923btO**" +} + +### OAuth Login (Google) +GET {{baseUrl}}/auth/google + +### OAuth Login (GitHub) +GET {{baseUrl}}/auth/github + +### Logout +POST {{baseUrl}}/auth/logout +Content-Type: {{contentType}} + +### Get Categories (Public) +GET {{baseUrl}}/categories + +### Get Current User (Protected) +# Replace @token with the actual token received from login +GET {{baseUrl}}/auth/me +Authorization: Bearer {{token}} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..fc4fdfb --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,83 @@ +package main + +import ( + "log" + + "gobeyhan/app/routes" + "gobeyhan/config" + "gobeyhan/database" + + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + + _ "gobeyhan/docs" // Swagger docs +) + +// @title Beyhan Backend API +// @version 2.0 +// @description Modular REST API with Blog, Account, and Settings apps +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.email support@beyhan.com + +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT + +// @host localhost:8080 +// @BasePath /api/v1 + +// @securityDefinitions.apikey BearerAuth +// @in header +// @name Authorization +// @description Type "Bearer" followed by a space and JWT token. + +func main() { + // Load configuration + config.LoadConfig() + + // Connect to database + database.ConnectDB() + + // Connect to Redis + database.ConnectRedis() + if err := database.FlushAll(); err != nil { + log.Printf("Warning: Failed to flush Redis cache: %v", err) + } + + // Run migrations in development + if config.AppConfig.Env == "development" { + if err := database.Migrate(database.DB); err != nil { + log.Fatalf("Migration failed: %v", err) + } + log.Println("Migration complete") + } + + // Initialize Gin + if config.AppConfig.Env == "production" { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.Default() + + // Swagger endpoint + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + // Setup routes + routes.SetupRoutes(r) + + // Start server + port := config.AppConfig.Port + if port == "" { + port = "8080" + } + + log.Printf("🚀 Server starting on port %s", port) + log.Printf("📚 Swagger UI: http://localhost:%s/swagger/index.html", port) + log.Printf("🌐 API Base: http://localhost:%s/api/v1", port) + + if err := r.Run(":" + port); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..79e7024 --- /dev/null +++ b/config/config.go @@ -0,0 +1,232 @@ +package config + +import ( + "log" + "os" + "strconv" + "strings" + + "github.com/joho/godotenv" +) + +type Config struct { + Env string // e.g. development, production + Port string + DBUrl string + JWTSecret string + AppURL string // e.g. https://api.example.com - used for verification links in emails + GoogleClientID string + GoogleClientSecret string + GithubClientID string + GithubClientSecret string + GoogleRedirectURL string + GithubRedirectURL string + ClientCallbackURL string + OAuthRedirectURL string + RedisUrl string + AccessTokenExpireMinutes int + RefreshTokenExpireDays int + + // Avatar Settings + AvatarHeight int + AvatarWidth int + AvatarQuality int + AvatarFormat string + AvatarMode string // cover, contain, resize + + // Home Image Settings + HomeImageHeight int + HomeImageWidth int + HomeImageQuality int + HomeImageFormat string + HomeImageMode string // cover, contain, resize + + // About Image Settings + AboutImageHeight int + AboutImageWidth int + AboutImageQuality int + AboutImageFormat string + AboutImageMode string // cover, contain, resize + + // Service Image Settings + ServiceImageHeight int + ServiceImageWidth int + ServiceImageQuality int + ServiceImageFormat string + ServiceImageMode string // cover, contain, resize + + // Post Image Settings + PostImageHeight int + PostImageWidth int + PostImageQuality int + PostImageFormat string + PostImageMode string // cover, contain, resize + + // Post Category Image Settings + PostCategoryImageHeight int + PostCategoryImageWidth int + PostCategoryImageQuality int + PostCategoryImageFormat string + PostCategoryImageMode string // cover, contain, resize + + // Settings Logo Settings + SettingsLogoHeight int + SettingsLogoWidth int + SettingsLogoQuality int + SettingsLogoFormat string + SettingsLogoMode string // cover, contain, resize + + // Banner Image Settings + BannerImageHeight int + BannerImageWidth int + BannerImageQuality int + BannerImageFormat string + BannerImageMode string // cover, contain, resize + + // Banner Thumb Settings + BannerThumbHeight int + BannerThumbWidth int + BannerThumbQuality int + BannerThumbFormat string + BannerThumbMode string // cover, contain, resize + + // Email Settings + EmailHost string + EmailPort string + EmailHostUser string + EmailHostPassword string + EmailFrom string + CorsDebug bool + TurnstileSecretKey string +} + +var AppConfig *Config + +func LoadConfig() { + err := godotenv.Load() + if err != nil { + log.Println("Warning: Error loading .env file, continuing with system env") + } + + AppConfig = &Config{ + Env: getEnv("APP_ENV", "development"), + Port: getEnv("PORT", "8080"), + DBUrl: getEnv("DB_URL", ""), + JWTSecret: getEnv("JWT_SECRET", "default_secret"), + AppURL: getEnv("APP_URL", "http://localhost:8080"), + GoogleClientID: getEnv("GOOGLE_CLIENT_ID", ""), + GoogleClientSecret: getEnv("GOOGLE_CLIENT_SECRET", ""), + GithubClientID: getEnv("GITHUB_CLIENT_ID", ""), + GithubClientSecret: getEnv("GITHUB_CLIENT_SECRET", ""), + GoogleRedirectURL: getEnv("GOOGLE_REDIRECT_URL", "http://localhost:8080/api/v1/auth/google/callback"), + GithubRedirectURL: getEnv("GITHUB_REDIRECT_URL", "http://localhost:8080/api/v1/auth/github/callback"), + ClientCallbackURL: getEnv("CLIENT_CALLBACK_URL", ""), + OAuthRedirectURL: getEnv("OAUTH_REDIRECT_URL", ""), + RedisUrl: getEnv("REDIS_URL", ""), + AccessTokenExpireMinutes: getEnvAsInt("ACCESS_TOKEN_EXPIRE_MINUTES", 120), // Default 120 minutes + RefreshTokenExpireDays: getEnvAsInt("REFRESH_TOKEN_EXPIRE_DAYS", 30), // Default 30 days + + // Avatar Defaults + AvatarHeight: getEnvAsInt("AVATAR_H", 0), // Default 0 (auto) + AvatarWidth: getEnvAsInt("AVATAR_W", 800), // Default 800 + AvatarQuality: getEnvAsInt("AVATAR_Q", 80), // Default 80 + AvatarFormat: getEnv("AVATAR_F", "webp"), // Default webp + AvatarMode: getEnv("AVATAR_B", "contain"), // Default contain (Fit) + + // Home Image Defaults + HomeImageHeight: getEnvAsInt("HOME_IMAGE_H", 0), // Default 0 (auto) + HomeImageWidth: getEnvAsInt("HOME_IMAGE_W", 800), // Default 800 + HomeImageQuality: getEnvAsInt("HOME_IMAGE_Q", 80), // Default 80 + HomeImageFormat: getEnv("HOME_IMAGE_F", "webp"), // Default webp + HomeImageMode: getEnv("HOME_IMAGE_B", "contain"), // Default contain (Fit) + + // About Image Defaults + AboutImageHeight: getEnvAsInt("ABOUTME_IMAGE_H", getEnvAsInt("ABOUT_IMAGE_H", 0)), + AboutImageWidth: getEnvAsInt("ABOUTME_IMAGE_W", getEnvAsInt("ABOUT_IMAGE_W", 800)), + AboutImageQuality: getEnvAsInt("ABOUTME_IMAGE_Q", getEnvAsInt("ABOUT_IMAGE_Q", 80)), + AboutImageFormat: getEnv("ABOUTME_IMAGE_F", getEnv("ABOUT_IMAGE_F", "webp")), + AboutImageMode: getEnv("ABOUTME_IMAGE_B", getEnv("ABOUT_IMAGE_B", "contain")), + + // Service Image Defaults + ServiceImageHeight: getEnvAsInt("SERVICE_IMAGE_H", 256), + ServiceImageWidth: getEnvAsInt("SERVICE_IMAGE_W", 256), + ServiceImageQuality: getEnvAsInt("SERVICE_IMAGE_Q", 90), + ServiceImageFormat: getEnv("SERVICE_IMAGE_F", "png"), + ServiceImageMode: getEnv("SERVICE_IMAGE_B", "cover"), + + // Post Image Defaults + PostImageHeight: getEnvAsInt("POST_IMAGE_H", 450), + PostImageWidth: getEnvAsInt("POST_IMAGE_W", 700), + PostImageQuality: getEnvAsInt("POST_IMAGE_Q", 90), + PostImageFormat: getEnv("POST_IMAGE_F", "webp"), + PostImageMode: getEnv("POST_IMAGE_B", "cover"), + + // Post Category Image Defaults + PostCategoryImageHeight: getEnvAsInt("POST_CATEGORY_IMAGE_H", 300), + PostCategoryImageWidth: getEnvAsInt("POST_CATEGORY_IMAGE_W", 300), + PostCategoryImageQuality: getEnvAsInt("POST_CATEGORY_IMAGE_Q", 85), + PostCategoryImageFormat: getEnv("POST_CATEGORY_IMAGE_F", "png"), + PostCategoryImageMode: getEnv("POST_CATEGORY_IMAGE_B", "cover"), + + // Settings Logo Defaults + SettingsLogoHeight: getEnvAsInt("SETTINGS_LOGO_H", 54), + SettingsLogoWidth: getEnvAsInt("SETTINGS_LOGO_W", 165), + SettingsLogoQuality: getEnvAsInt("SETTINGS_LOGO_Q", 85), + SettingsLogoFormat: getEnv("SETTINGS_LOGO_F", "png"), + SettingsLogoMode: getEnv("SETTINGS_LOGO_B", "cover"), + + // Banner Image Defaults + BannerImageHeight: getEnvAsInt("BANNER_IMAGE_H", 700), + BannerImageWidth: getEnvAsInt("BANNER_IMAGE_W", 1920), + BannerImageQuality: getEnvAsInt("BANNER_IMAGE_Q", 85), + BannerImageFormat: getEnv("BANNER_IMAGE_F", "webp"), + BannerImageMode: getEnv("BANNER_IMAGE_B", "cover"), + + // Banner Thumb Defaults + BannerThumbHeight: getEnvAsInt("BANNER_THUMB_H", 48), + BannerThumbWidth: getEnvAsInt("BANNER_THUMB_W", 48), + BannerThumbQuality: getEnvAsInt("BANNER_THUMB_Q", 90), + BannerThumbFormat: getEnv("BANNER_THUMB_F", "png"), + BannerThumbMode: getEnv("BANNER_THUMB_B", "cover"), + + // Email Settings + EmailHost: getEnv("EMAIL_HOST", "localhost"), + EmailPort: getEnv("EMAIL_PORT", "1025"), + EmailHostUser: getEnv("EMAIL_HOST_USER", ""), + EmailHostPassword: getEnv("EMAIL_HOST_PASSWORD", ""), + EmailFrom: getEnv("EMAIL_FROM", "noreply@gauth.local"), + CorsDebug: getEnvAsBool("CORS_DEBUG", false), + TurnstileSecretKey: getEnv("CLOUD_FLARE_SECRET", ""), + } +} + +func getEnv(key, fallback string) string { + if value, exists := os.LookupEnv(key); exists { + return value + } + return fallback +} + +func getEnvAsInt(key string, fallback int) int { + valueStr := getEnv(key, "") + if valueStr == "" { + return fallback + } + value, err := strconv.Atoi(valueStr) + if err != nil { + return fallback + } + return value +} + +func getEnvAsBool(key string, fallback bool) bool { + valueStr := strings.TrimSpace(getEnv(key, "")) + if valueStr == "" { + return fallback + } + value, err := strconv.ParseBool(valueStr) + if err != nil { + return fallback + } + return value +} diff --git a/database/README_migrate.md b/database/README_migrate.md new file mode 100644 index 0000000..c980664 --- /dev/null +++ b/database/README_migrate.md @@ -0,0 +1,31 @@ +Kısa kullanım + +Bu proje için GORM AutoMigrate helper'ı `Migrate(db *gorm.DB) error` fonksiyonu olarak sağlanmıştır. + +Örnek kullanım (ör. `main.go` içinde): + +```go +import ( + "gobeyhan/config" // DB konfigürasyonunuza göre düzenleyin + "gobeyhan/database" +) + +func main() { + db, err := config.NewDB() // veya projenizdeki DB bağlantı fonksiyonu + if err != nil { + panic(err) + } + + if err := database.Migrate(db); err != nil { + panic(err) + } + + // uygulama başlat +} +``` + +Notlar: +- `database/migrate.go` sadece modeller için `AutoMigrate` çağrısını yapar. +- Thumbnail oluşturma ve dosya upload işlemleri model hook'larında değil upload handler'larında yapılmalıdır. +- Eğer DB seviyesinde benzersiz constraint'ler isterseniz, GORM tag veya migration dosyası ile `uniqueIndex` ekleyin. + diff --git a/database/db.go b/database/db.go new file mode 100644 index 0000000..dcbf87b --- /dev/null +++ b/database/db.go @@ -0,0 +1,329 @@ +package database + +import ( + //"fmt" + "log" + "strings" + "time" + + "gobeyhan/config" + "gobeyhan/database/models" + + "golang.org/x/crypto/bcrypt" + "gorm.io/driver/mysql" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +var DB *gorm.DB + +func ConnectDB() { + dsn := config.AppConfig.DBUrl + if dsn == "" { + log.Fatal("DB_URL is not set in .env") + } + + // Configure GORM with optimized settings + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: logger.Default.LogMode(logger.Info), // Only log errors, suppress SLOW SQL warnings + PrepareStmt: true, // Prepare statements for better performance + NowFunc: func() time.Time { + return time.Now().UTC() + }, + }) + if err != nil { + log.Fatal("Failed to connect to database:", err) + } + + log.Println("Connected to Database successfully") + DB = db + + // MySQL doesn't require enabling uuid-ossp extension. noop for compatibility + onEnableUUIDForMySQL() +} + +func onEnableUUIDForMySQL() { + // noop: Postgres-only extension; for MySQL UUID handling is usually done at application level + log.Println("UUID extension step skipped for MySQL (not required)") +} + +func SeedAll() { + if DB == nil { + log.Println("DB not initialized: call ConnectDB() before SeedAll") + return + } + + // Run AutoMigrate using the helper in migrate.go + if err := Migrate(DB); err != nil { + log.Printf("AutoMigrate failed: %v", err) + return + } + + // Run schema/data migrations + migrateUserNameColumn() + migrateEmailVerifiedColumn() + + // Seed initial data + seedRolesAndPermissions() + seedDefaultSettings() + SeedDefaultAdmin() + + log.Println("Database migration and seeding complete") +} + +func migrateEmailVerifiedColumn() { + // Check column existence via information_schema for MySQL + var count int64 + DB.Raw(` + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'email_verified' + `).Scan(&count) + + if count == 0 { + // Column doesn't exist, nothing to migrate + return + } + + // Only set existing users (created before email verification feature) as verified + var usersToVerify int64 + DB.Model(&models.User{}).Where("(email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL").Count(&usersToVerify) + + if usersToVerify > 0 { + DB.Exec("UPDATE users SET email_verified = true WHERE (email_verify_token IS NULL OR email_verify_token = '') AND email_verified IS NULL") + log.Printf("Email verification migration: %d existing users marked as verified", usersToVerify) + } +} + +func migrateUserNameColumn() { + // Check column existence via information_schema for MySQL + var count int64 + DB.Raw(` + SELECT COUNT(*) + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'user_name' + `).Scan(&count) + + if count == 0 { + // Column doesn't exist, add it + log.Println("Adding user_name column...") + DB.Exec("ALTER TABLE users ADD COLUMN user_name TEXT") + + // Update existing users with default usernames + DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(CAST(id AS CHAR), 1, 8)) WHERE user_name IS NULL") + + // Add NOT NULL constraint + DB.Exec("ALTER TABLE users MODIFY COLUMN user_name TEXT NOT NULL") + log.Println("user_name column added successfully") + return + } + + // Column exists, update null or empty values + log.Println("Updating users with null or empty usernames...") + DB.Exec("UPDATE users SET user_name = CONCAT('user_', SUBSTRING(CAST(id AS CHAR), 1, 8)) WHERE user_name IS NULL OR user_name = ''") + + // Check if NOT NULL constraint exists using information_schema + var isNullable string + DB.Raw(` + SELECT IS_NULLABLE + FROM information_schema.COLUMNS + WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'user_name' + `).Scan(&isNullable) + + if strings.ToUpper(isNullable) != "NO" { + log.Println("Adding NOT NULL constraint to user_name...") + DB.Exec("ALTER TABLE users MODIFY COLUMN user_name TEXT NOT NULL") + } +} + +func seedRolesAndPermissions() { + // 1. Define Permissions + permissions := []models.Permission{ + {Name: "user:read", Description: "Can read user data"}, + {Name: "user:write", Description: "Can modify user data"}, + {Name: "admin:access", Description: "Can access admin panel"}, + } + + for _, p := range permissions { + DB.FirstOrCreate(&models.Permission{}, models.Permission{Name: p.Name, Description: p.Description}) + } + + // 2. Define Roles + roles := []string{"admin", "user"} + for _, r := range roles { + DB.FirstOrCreate(&models.Role{}, models.Role{Name: r, Description: "Default " + r + " role"}) + } + + // 3. Assign Permissions to Admin Role + var adminRole models.Role + DB.Preload("Permissions").Where("name = ?", "admin").First(&adminRole) + + // Fetch all permissions to assign to admin + var allPermissions []models.Permission + DB.Find(&allPermissions) + + // Update association (replace current set) + DB.Model(&adminRole).Association("Permissions").Replace(allPermissions) + + // 4. Assign Basic Permissions to User Role + var userRole models.Role + DB.Preload("Permissions").Where("name = ?", "user").First(&userRole) + + var userPermissions []models.Permission + DB.Where("name IN ?", []string{"user:read"}).Find(&userPermissions) + + DB.Model(&userRole).Association("Permissions").Replace(userPermissions) + + log.Println("Roles and Permissions seeded") +} + +func seedDefaultSettings() { + // Seed default CORS whitelist + var whitelistCount int64 + DB.Model(&models.CorsWhitelist{}).Count(&whitelistCount) + + if whitelistCount == 0 { + defaultWhitelist := []models.CorsWhitelist{ + { + Origin: "http://localhost:3000", + Description: "Default local frontend", + IsActive: true, + CreatedBy: "system", + }, + { + Origin: "http://localhost:8080", + Description: "Backend self", + IsActive: true, + CreatedBy: "system", + }, + } + + for _, w := range defaultWhitelist { + DB.Create(&w) + } + log.Println("Default CORS whitelist seeded") + } + + // Seed default rate limit settings + var rateLimitCount int64 + DB.Model(&models.RateLimitSetting{}).Count(&rateLimitCount) + + if rateLimitCount == 0 { + defaultRateLimits := []models.RateLimitSetting{ + { + Name: "login", + Description: "Login endpoint rate limit", + MaxRequests: 5, + WindowSeconds: 60, // 1 minute + IsActive: true, + }, + { + Name: "register", + Description: "Registration endpoint rate limit", + MaxRequests: 3, + WindowSeconds: 300, // 5 minutes + IsActive: true, + }, + { + Name: "api", + Description: "General API rate limit", + MaxRequests: 100, + WindowSeconds: 60, // 1 minute + IsActive: true, + }, + } + + for _, r := range defaultRateLimits { + DB.Create(&r) + } + log.Println("Default rate limit settings seeded") + } +} + +// SeedDefaultAdmin creates the default admin user if it doesn't exist +func SeedDefaultAdmin() { + if DB == nil { + log.Println("DB not initialized: call ConnectDB() before seeding") + return + } + + // Use a transaction to ensure atomic create + role assignment + tx := DB.Begin() + defer func() { + if r := recover(); r != nil { + tx.Rollback() + log.Printf("panic during SeedDefaultAdmin: %v", r) + } + }() + + // Check if admin user already exists (including soft-deleted) + var adminUser models.User + err := tx.Unscoped().Where("email = ?", "admin@gauth.local").First(&adminUser).Error + + if err != nil { + // Admin user doesn't exist, create one + // Hash default password: "Admin@123" + hashedPassword, err := bcrypt.GenerateFromPassword([]byte("Admin@123"), bcrypt.DefaultCost) + if err != nil { + log.Printf("Failed to hash admin password: %v", err) + tx.Rollback() + return + } + + trueBool := true + adminUser = models.User{ + Email: "admin@gauth.local", + UserName: "admin", + Password: string(hashedPassword), + EmailVerified: &trueBool, + } + + if err := tx.Create(&adminUser).Error; err != nil { + log.Printf("Failed to create admin user: %v", err) + tx.Rollback() + return + } + + // Log created admin ID and type for debugging + log.Printf("Admin created - ID value: %v (type: %T)", adminUser.ID, adminUser.ID) + + log.Println("✅ Default admin user created:") + log.Println(" Email: admin@gauth.local") + log.Println(" Password: Admin@123") + log.Println(" ⚠️ Please change this password after first login!") + } else { + // Admin user exists (possibly soft-deleted) + if adminUser.DeletedAt.Valid { + log.Println("Restoring deleted admin user...") + if err := tx.Model(&adminUser).Unscoped().Update("deleted_at", nil).Error; err != nil { + log.Printf("Failed to restore admin user: %v", err) + tx.Rollback() + return + } + } + + // Log existing admin ID for debugging + log.Printf("Admin already exists - ID value: %v (type: %T)", adminUser.ID, adminUser.ID) + } + + // Ensure admin role is assigned + var adminRole models.Role + if err := tx.Where("name = ?", "admin").First(&adminRole).Error; err != nil { + log.Printf("Admin role not found: %v", err) + tx.Rollback() + return + } + + if err := tx.Model(&adminUser).Association("Roles").Append(&adminRole); err != nil { + log.Printf("Failed to assign admin role: %v", err) + tx.Rollback() + return + } + + if err := tx.Commit().Error; err != nil { + log.Printf("Failed to commit admin seed transaction: %v", err) + return + } + + log.Println("Varsayılan Yönetici Yaratıldı...") +} diff --git a/database/migrate.go b/database/migrate.go new file mode 100644 index 0000000..66dbddd --- /dev/null +++ b/database/migrate.go @@ -0,0 +1,26 @@ +package database + +import ( + "gobeyhan/database/models" + + "gorm.io/gorm" +) + +// Migrate runs AutoMigrate for all models used in the project. +func Migrate(db *gorm.DB) error { + // Order can matter due to foreign keys; migrate parents first + return db.AutoMigrate( + &models.User{}, + &models.SocialAccount{}, + &models.Role{}, + &models.Permission{}, + &models.Category{}, + &models.Tag{}, + &models.Post{}, + &models.CategoryView{}, + &models.Comment{}, + &models.CorsWhitelist{}, + &models.CorsBlacklist{}, + &models.RateLimitSetting{}, + ) +} diff --git a/database/models/blog.go b/database/models/blog.go new file mode 100644 index 0000000..37beff4 --- /dev/null +++ b/database/models/blog.go @@ -0,0 +1,274 @@ +package models + +import ( + "errors" + "fmt" + "path/filepath" + "strings" + "time" + + "gorm.io/gorm" +) + +// Note: This file maps Django models to GORM models for MySQL. +// Image fields are stored as file path strings. Thumbnail generation and image processing +// should be handled elsewhere (e.g., during upload) — TODO: integrate with image processing service. + +// Category represents post categories. +type Category struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Title string `gorm:"size:254;not null" json:"title"` + Keywords string `gorm:"size:254" json:"keywords"` + Desc string `gorm:"size:254" json:"description"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + IsActive bool `gorm:"default:true;index" json:"is_active"` + Order int `gorm:"default:1;index" json:"order"` + Slug string `gorm:"size:250;not null;index" json:"slug"` + ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` + Parent *Category `gorm:"foreignKey:ParentID" json:"parent,omitempty"` + Children []*Category `gorm:"foreignKey:ParentID" json:"children,omitempty"` + Image string `gorm:"size:1024" json:"image"` +} + +func (Category) TableName() string { + return "categories" +} + +// BeforeCreate hook to set slug +func (c *Category) BeforeCreate(tx *gorm.DB) (err error) { + if c.Slug == "" { + c.Slug, err = generateUniqueSlugForCategory(tx, c.Title) + return err + } + return nil +} + +// BeforeUpdate hook ensures slug exists +func (c *Category) BeforeUpdate(tx *gorm.DB) (err error) { + if c.Slug == "" { + c.Slug, err = generateUniqueSlugForCategory(tx, c.Title) + return err + } + return nil +} + +func generateUniqueSlugForCategory(db *gorm.DB, title string) (string, error) { + slug := normalizeSlug(title) + base := slug + var count int64 + try := 1 + for { + db.Model(&Category{}).Where("slug = ?", slug).Count(&count) + if count == 0 { + return slug, nil + } + slug = fmt.Sprintf("%s-%d", base, try) + try++ + if try > 1000 { + return "", errors.New("unable to generate unique slug") + } + } +} + +// Tags model +type Tag struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Tag string `gorm:"size:254;not null" json:"tag"` + Slug string `gorm:"size:250;not null;index" json:"slug"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + IsActive bool `gorm:"default:true;index" json:"is_active"` +} + +func (Tag) TableName() string { return "tags" } + +func (t *Tag) BeforeCreate(tx *gorm.DB) (err error) { + if t.Slug == "" { + t.Slug, err = generateUniqueSlugForTag(tx, t.Tag) + return err + } + return nil +} + +func (t *Tag) BeforeUpdate(tx *gorm.DB) (err error) { + if t.Slug == "" { + t.Slug, err = generateUniqueSlugForTag(tx, t.Tag) + return err + } + return nil +} + +func generateUniqueSlugForTag(db *gorm.DB, tag string) (string, error) { + slug := normalizeSlug(tag) + base := slug + var count int64 + try := 1 + for { + db.Model(&Tag{}).Where("slug = ?", slug).Count(&count) + if count == 0 { + return slug, nil + } + slug = fmt.Sprintf("%s-%d", base, try) + try++ + if try > 1000 { + return "", errors.New("unable to generate unique slug") + } + } +} + +// Post model +type Post struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Title string `gorm:"size:254;not null" json:"title"` + UserID *uint64 `gorm:"type:bigint unsigned;index" json:"user_id"` + User *User `gorm:"foreignKey:UserID" json:"user,omitempty"` + Content string `gorm:"type:text" json:"content"` + Categories []*Category `gorm:"many2many:post_categories;" json:"categories"` + Keywords string `gorm:"size:254" json:"keywords"` + Tags []*Tag `gorm:"many2many:post_tags;" json:"tags"` + Image string `gorm:"size:1024" json:"image"` + Thumb string `gorm:"size:1024" json:"thumb"` + Video string `gorm:"size:254;default:'none'" json:"video"` + Slug string `gorm:"size:250;not null;index" json:"slug"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + IsActive bool `gorm:"default:true;index" json:"is_active"` + IsFront bool `gorm:"default:true;index" json:"is_front"` + ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` + Parent *Post `gorm:"foreignKey:ParentID" json:"parent,omitempty"` + Children []*Post `gorm:"foreignKey:ParentID" json:"children,omitempty"` +} + +func (Post) TableName() string { return "posts" } + +func (p *Post) BeforeCreate(tx *gorm.DB) (err error) { + if p.Slug == "" { + p.Slug, err = generateUniqueSlugForPost(tx, p.Title) + if err != nil { + return err + } + } + // Note: Thumbnail generation should be handled in the upload flow. + return nil +} + +func (p *Post) BeforeUpdate(tx *gorm.DB) (err error) { + if p.Slug == "" { + p.Slug, err = generateUniqueSlugForPost(tx, p.Title) + return err + } + return nil +} + +func generateUniqueSlugForPost(db *gorm.DB, title string) (string, error) { + slug := normalizeSlug(title) + base := slug + var count int64 + try := 1 + for { + db.Model(&Post{}).Where("slug = ?", slug).Count(&count) + if count == 0 { + return slug, nil + } + slug = fmt.Sprintf("%s-%d", base, try) + try++ + if try > 1000 { + return "", errors.New("unable to generate unique slug") + } + } +} + +// CategoryView model +type CategoryView struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + CategoryID uint64 `gorm:"type:bigint unsigned;index" json:"category_id"` + Category *Category `gorm:"foreignKey:CategoryID" json:"category"` + IPAddress string `gorm:"size:45;index" json:"ip_address"` + UserAgent string `gorm:"type:text" json:"user_agent"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` +} + +func (CategoryView) TableName() string { return "category_views" } + +// Comment model +type Comment struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + UserID uint64 `gorm:"type:bigint unsigned;index" json:"user_id"` + ProductID uint64 `gorm:"type:bigint unsigned;index" json:"product_id"` + Product Post `gorm:"foreignKey:ProductID" json:"product"` + Title string `gorm:"size:254" json:"title"` + Body string `gorm:"type:text" json:"body"` + CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at"` + UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at"` + IsActive bool `gorm:"default:true;index" json:"is_active"` + Slug string `gorm:"size:250;index" json:"slug"` + ParentID *uint64 `gorm:"type:bigint unsigned;index" json:"parent_id"` + Parent *Comment `gorm:"foreignKey:ParentID" json:"parent,omitempty"` + Children []*Comment `gorm:"foreignKey:ParentID" json:"children,omitempty"` +} + +func (Comment) TableName() string { return "comments" } + +func (c *Comment) BeforeCreate(tx *gorm.DB) (err error) { + if c.Slug == "" { + c.Slug, err = generateUniqueSlugForComment(tx, c.Title) + return err + } + return nil +} + +func (c *Comment) BeforeUpdate(tx *gorm.DB) (err error) { + if c.Slug == "" { + c.Slug, err = generateUniqueSlugForComment(tx, c.Title) + return err + } + return nil +} + +func generateUniqueSlugForComment(db *gorm.DB, title string) (string, error) { + slug := normalizeSlug(title) + base := slug + var count int64 + try := 1 + for { + db.Model(&Comment{}).Where("slug = ?", slug).Count(&count) + if count == 0 { + return slug, nil + } + slug = fmt.Sprintf("%s-%d", base, try) + try++ + if try > 1000 { + return "", errors.New("unable to generate unique slug") + } + } +} + +// normalizeSlug replaces Turkish characters, lowercases and makes a basic slug. +func normalizeSlug(s string) string { + replacer := strings.NewReplacer( + "ı", "i", + "İ", "i", + "ç", "c", + "Ç", "c", + "ş", "s", + "Ş", "s", + "ö", "o", + "Ö", "o", + "ü", "u", + "Ü", "u", + " ", "-", + ) + s = replacer.Replace(s) + s = strings.ToLower(s) + s = strings.TrimSpace(s) + // remove multiple dashes + for strings.Contains(s, "--") { + s = strings.ReplaceAll(s, "--", "-") + } + // remove extension-like parts + s = strings.Trim(s, "-._") + // sanitize file-like chars + s = filepath.Clean(s) + return s +} diff --git a/database/models/cors_setting.go b/database/models/cors_setting.go new file mode 100644 index 0000000..9128737 --- /dev/null +++ b/database/models/cors_setting.go @@ -0,0 +1,40 @@ +package models + +import ( + "time" +) + +// CorsWhitelist - CORS için izin verilen origin'ler +type CorsWhitelist struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"` + Description string `gorm:"type:text" json:"description"` + IsActive bool `gorm:"default:true" json:"is_active"` + CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// CorsBlacklist - CORS için yasaklanan origin'ler +type CorsBlacklist struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Origin string `gorm:"type:varchar(255);uniqueIndex;not null" json:"origin"` + Reason string `gorm:"type:text" json:"reason"` + IsActive bool `gorm:"default:true" json:"is_active"` + CreatedBy string `gorm:"type:varchar(255)" json:"created_by,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// RateLimitSetting - Rate limit ayarları +type RateLimitSetting struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Name string `gorm:"type:varchar(100);uniqueIndex;not null" json:"name"` // e.g., "login", "register", "api" + Description string `gorm:"type:text" json:"description"` + MaxRequests int64 `gorm:"not null" json:"max_requests"` // Max istek sayısı + WindowSeconds int `gorm:"not null" json:"window_seconds"` // Zaman penceresi (saniye) + IsActive bool `gorm:"default:true" json:"is_active"` + UpdatedBy string `gorm:"type:varchar(255)" json:"updated_by,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} diff --git a/database/models/role.go b/database/models/role.go new file mode 100644 index 0000000..3c4367f --- /dev/null +++ b/database/models/role.go @@ -0,0 +1,14 @@ +package models + +type Role struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Name string `gorm:"uniqueIndex;not null" json:"name"` // admin, user + Description string `json:"description"` + Permissions []Permission `gorm:"many2many:role_permissions;" json:"permissions"` +} + +type Permission struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + Name string `gorm:"uniqueIndex;not null" json:"name"` // user:read, user:write + Description string `json:"description"` +} diff --git a/database/models/user.go b/database/models/user.go new file mode 100644 index 0000000..7d4a1a0 --- /dev/null +++ b/database/models/user.go @@ -0,0 +1,51 @@ +package models + +import ( + "time" + + "gorm.io/gorm" +) + +// User model structure +type User struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + UserName string `json:"username"` + Email string `gorm:"uniqueIndex;not null" json:"email"` + Password string `json:"-"` // Password shouldn't be returned in JSON + Avatar string `gorm:"type:varchar(500)" json:"avatar,omitempty"` // Avatar URL from OAuth or uploaded + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt gorm.DeletedAt `gorm:"index" json:"-"` + + // Email verification: only required for email/password registration; OAuth users are treated as verified + // Changed to *bool to handle false values correctly with GORM defaults + EmailVerified *bool `gorm:"default:false" json:"email_verified"` // default false for email/password registration + EmailVerifyToken string `gorm:"index" json:"-"` + EmailVerifiedAt *time.Time `json:"email_verified_at,omitempty"` + + SocialAccounts []SocialAccount `gorm:"foreignKey:UserID" json:"social_accounts,omitempty"` + Roles []Role `gorm:"many2many:user_roles;" json:"roles"` +} + +// Helper to safely get EmailVerified status +func (u *User) IsEmailVerified() bool { + if u.EmailVerified == nil { + return false + } + return *u.EmailVerified +} + +// SocialAccount model structure +type SocialAccount struct { + ID uint64 `gorm:"type:bigint unsigned;autoIncrement;primaryKey" json:"id"` + UserID uint64 `gorm:"type:bigint unsigned;not null;index" json:"user_id"` + Provider string `gorm:"not null" json:"provider"` // google, github + ProviderID string `gorm:"not null" json:"provider_id"` + Email string `json:"email"` + Name string `json:"name,omitempty"` // Full name from provider + AvatarURL string `json:"avatar_url,omitempty"` // Avatar URL from provider + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// Hooks can be added here if needed diff --git a/database/redis.go b/database/redis.go new file mode 100644 index 0000000..ed817c9 --- /dev/null +++ b/database/redis.go @@ -0,0 +1,106 @@ +package database + +import ( + "context" + "log" + "time" + + "gobeyhan/config" + + "github.com/redis/go-redis/v9" +) + +var RedisClient *redis.Client +var ctx = context.Background() + +func ConnectRedis() { + redisURL := config.AppConfig.RedisUrl + if redisURL == "" { + log.Println("Warning: REDIS_URL is not set, continuing without Redis cache") + return + } + + opt, err := redis.ParseURL(redisURL) + if err != nil { + log.Printf("Warning: Failed to parse Redis URL: %v, continuing without Redis cache", err) + return + } + + RedisClient = redis.NewClient(opt) + + // Test connection + _, err = RedisClient.Ping(ctx).Result() + if err != nil { + log.Printf("Warning: Failed to connect to Redis: %v, continuing without Redis cache", err) + RedisClient = nil + return + } + + log.Println("Connected to Redis successfully") +} + +// Set stores a key-value pair in Redis with expiration +func Set(key string, value interface{}, expiration time.Duration) error { + if RedisClient == nil { + return nil // Gracefully handle when Redis is not available + } + return RedisClient.Set(ctx, key, value, expiration).Err() +} + +// Get retrieves a value from Redis +func Get(key string) (string, error) { + if RedisClient == nil { + return "", redis.Nil // Return Nil error when Redis is not available + } + return RedisClient.Get(ctx, key).Result() +} + +// Delete removes a key from Redis +func Delete(key string) error { + if RedisClient == nil { + return nil + } + return RedisClient.Del(ctx, key).Err() +} + +// Exists checks if a key exists in Redis +func Exists(key string) (bool, error) { + if RedisClient == nil { + return false, nil + } + count, err := RedisClient.Exists(ctx, key).Result() + return count > 0, err +} + +// SetWithJSON stores a JSON-serializable value in Redis +func SetEx(key string, value interface{}, seconds int) error { + if RedisClient == nil { + return nil + } + return RedisClient.Set(ctx, key, value, time.Duration(seconds)*time.Second).Err() +} + +// Increment increments a counter in Redis +func Increment(key string) (int64, error) { + if RedisClient == nil { + return 0, nil + } + return RedisClient.Incr(ctx, key).Result() +} + +// Expire sets expiration time for a key +func Expire(key string, expiration time.Duration) error { + if RedisClient == nil { + return nil + } + return RedisClient.Expire(ctx, key, expiration).Err() +} + +// FlushAll clears all keys in the current database +func FlushAll() error { + if RedisClient == nil { + return nil + } + log.Println("🧹 Clearing Redis Cache...") + return RedisClient.FlushDB(ctx).Err() +} diff --git a/dev.sh b/dev.sh new file mode 100644 index 0000000..5d24772 --- /dev/null +++ b/dev.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +cd admin-panel && yarn dev & air && fg \ No newline at end of file diff --git a/docs/docs.go b/docs/docs.go new file mode 100644 index 0000000..e1b5995 --- /dev/null +++ b/docs/docs.go @@ -0,0 +1,3034 @@ +// Package docs Code generated by swaggo/swag. DO NOT EDIT +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "schemes": {{ marshal .Schemes }}, + "swagger": "2.0", + "info": { + "description": "{{escape .Description}}", + "title": "{{.Title}}", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@beyhan.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "{{.Version}}" + }, + "host": "{{.Host}}", + "basePath": "{{.BasePath}}", + "paths": { + "/api/v1/admin/categories": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all categories including inactive ones", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Get all categories (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Create a new category (Admin)", + "parameters": [ + { + "description": "Category object", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Category" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "/api/v1/admin/categories/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single category by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Get category by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Update a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Category object", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Category" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a category by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Delete a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/categories/{id}/views": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get view count and details for a specific category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "category-views" + ], + "summary": "Get view stats for a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/category-views": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all category views", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "category-views" + ], + "summary": "Get all category views (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/comments": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all comments", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Get all comments (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/comments/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single comment by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Get comment by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Update a comment (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Comment object", + "name": "comment", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a comment by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Delete a comment (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/blacklist": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all CORS blacklist origins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all CORS blacklist entries (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new origin to CORS blacklist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Create CORS blacklist entry (Admin)", + "parameters": [ + { + "description": "Blacklist object", + "name": "blacklist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + } + } + }, + "/api/v1/admin/cors/blacklist/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing CORS blacklist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update CORS blacklist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Blacklist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Blacklist object", + "name": "blacklist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a CORS blacklist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Delete CORS blacklist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Blacklist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/cache/invalidate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Clear the CORS cache to force reload from database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Invalidate CORS cache (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/whitelist": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all CORS whitelist origins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all CORS whitelist entries (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new origin to CORS whitelist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Create CORS whitelist entry (Admin)", + "parameters": [ + { + "description": "Whitelist object", + "name": "whitelist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + } + } + }, + "/api/v1/admin/cors/whitelist/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing CORS whitelist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update CORS whitelist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Whitelist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Whitelist object", + "name": "whitelist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a CORS whitelist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Delete CORS whitelist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Whitelist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/permissions": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all permissions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "permissions" + ], + "summary": "Get all permissions (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Permission" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "permissions" + ], + "summary": "Create a new permission (Admin)", + "parameters": [ + { + "description": "Permission object", + "name": "permission", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Permission" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Permission" + } + } + } + } + }, + "/api/v1/admin/posts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all posts including inactive", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Get all posts (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new post", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Create a new post (Admin)", + "parameters": [ + { + "description": "Post object", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Post" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + } + }, + "/api/v1/admin/posts/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single post by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Get post by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing post", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Update a post (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Post object", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a post by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Delete a post (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/rate-limits": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all rate limit configurations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all rate limit settings (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.RateLimitSetting" + } + } + } + } + } + }, + "/api/v1/admin/rate-limits/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing rate limit configuration", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update rate limit setting (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Rate Limit ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Rate limit object", + "name": "setting", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RateLimitSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/roles": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all roles with permissions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Get all roles (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Role" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Create a new role (Admin)", + "parameters": [ + { + "description": "Role object", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Role" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + } + }, + "/api/v1/admin/roles/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single role by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Get role by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Update a role (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Role object", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Role" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a role by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Delete a role (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/tags": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all tags including inactive ones", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Get all tags (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Create a new tag (Admin)", + "parameters": [ + { + "description": "Tag object", + "name": "tag", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "/api/v1/admin/tags/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single tag by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Get tag by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Update a tag (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Tag object", + "name": "tag", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a tag by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Delete a tag (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all users", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Get all users (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "boolean", + "description": "Include soft-deleted users", + "name": "include_deleted", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Create a new user (Admin)", + "parameters": [ + { + "description": "User object", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + } + }, + "/api/v1/admin/users/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single user by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Get user by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Update a user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "User object", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Soft delete a user by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Delete a user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/restore": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Restore a soft-deleted user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Restore a deleted user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/roles": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Assign a role to a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Assign role to user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Role ID", + "name": "role_id", + "in": "body", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/roles/{role_id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Remove a role from a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Remove role from user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "role_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/auth/github": { + "get": { + "description": "Redirect to GitHub OAuth", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "GitHub OAuth login", + "responses": {} + } + }, + "/api/v1/auth/github/callback": { + "get": { + "description": "Handle GitHub OAuth callback", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "GitHub OAuth callback", + "parameters": [ + { + "type": "string", + "description": "Authorization code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/google": { + "get": { + "description": "Redirect to Google OAuth", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "Google OAuth login", + "responses": {} + } + }, + "/api/v1/auth/google/callback": { + "get": { + "description": "Handle Google OAuth callback", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "Google OAuth callback", + "parameters": [ + { + "type": "string", + "description": "Authorization code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/login": { + "post": { + "description": "Login with email and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Login user", + "parameters": [ + { + "description": "Login credentials", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/logout": { + "post": { + "description": "Logout (client-side token removal)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Logout user", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get current authenticated user information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Get current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/register": { + "post": { + "description": "Create a new user account with email and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Register a new user", + "parameters": [ + { + "description": "Registration data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/categories": { + "get": { + "description": "Get list of all active categories (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "categories" + ], + "summary": "Get all active categories", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + } + } + } + } + }, + "/api/v1/categories/{id}/view": { + "post": { + "description": "Record a view event for a category (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "category-views" + ], + "summary": "Track a category view", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/categories/{slug}": { + "get": { + "description": "Get a single category by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "categories" + ], + "summary": "Get category by slug", + "parameters": [ + { + "type": "string", + "description": "Category Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "/api/v1/posts": { + "get": { + "description": "Get paginated list of active posts (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "posts" + ], + "summary": "Get all active posts", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/posts/{id}/comments": { + "get": { + "description": "Get all active comments for a specific post (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "comments" + ], + "summary": "Get comments for a post", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Comment" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new comment (requires authentication)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "comments" + ], + "summary": "Create a comment on a post", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Comment object", + "name": "comment", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + } + }, + "/api/v1/posts/{slug}": { + "get": { + "description": "Get a single post by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "posts" + ], + "summary": "Get post by slug", + "parameters": [ + { + "type": "string", + "description": "Post Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + } + }, + "/api/v1/tags": { + "get": { + "description": "Get list of all active tags (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "tags" + ], + "summary": "Get all active tags", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + } + }, + "/api/v1/tags/{slug}": { + "get": { + "description": "Get a single tag by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "tags" + ], + "summary": "Get tag by slug", + "parameters": [ + { + "type": "string", + "description": "Tag Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "/api/v1/user/social-accounts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all social accounts for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social-accounts" + ], + "summary": "Get user's social accounts", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SocialAccount" + } + } + } + } + } + }, + "/api/v1/user/social-accounts/{id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a social account for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social-accounts" + ], + "summary": "Delete a social account", + "parameters": [ + { + "type": "integer", + "description": "Social Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "definitions": { + "models.Category": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "keywords": { + "type": "string" + }, + "order": { + "type": "integer" + }, + "parent": { + "$ref": "#/definitions/models.Category" + }, + "parent_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.Comment": { + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Comment" + } + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "parent": { + "$ref": "#/definitions/models.Comment" + }, + "parent_id": { + "type": "integer" + }, + "product": { + "$ref": "#/definitions/models.Post" + }, + "product_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "models.CorsBlacklist": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "origin": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.CorsWhitelist": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "origin": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.Permission": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "user:read, user:write", + "type": "string" + } + } + }, + "models.Post": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Post" + } + }, + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_front": { + "type": "boolean" + }, + "keywords": { + "type": "string" + }, + "parent": { + "$ref": "#/definitions/models.Post" + }, + "parent_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + }, + "thumb": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + }, + "user_id": { + "type": "integer" + }, + "video": { + "type": "string" + } + } + }, + "models.RateLimitSetting": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "max_requests": { + "description": "Max istek sayısı", + "type": "integer" + }, + "name": { + "description": "e.g., \"login\", \"register\", \"api\"", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "window_seconds": { + "description": "Zaman penceresi (saniye)", + "type": "integer" + } + } + }, + "models.Role": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "admin, user", + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Permission" + } + } + } + }, + "models.SocialAccount": { + "type": "object", + "properties": { + "avatar_url": { + "description": "Avatar URL from provider", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "Full name from provider", + "type": "string" + }, + "provider": { + "description": "google, github", + "type": "string" + }, + "provider_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "models.Tag": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "avatar": { + "description": "Avatar URL from OAuth or uploaded", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_verified": { + "description": "Email verification: only required for email/password registration; OAuth users are treated as verified\nChanged to *bool to handle false values correctly with GORM defaults", + "type": "boolean" + }, + "email_verified_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Role" + } + }, + "social_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SocialAccount" + } + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "description": "Type \"Bearer\" followed by a space and JWT token.", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +}` + +// SwaggerInfo holds exported Swagger Info so clients can modify it +var SwaggerInfo = &swag.Spec{ + Version: "2.0", + Host: "localhost:8080", + BasePath: "/", + Schemes: []string{}, + Title: "Beyhan Backend API", + Description: "Modular REST API with Blog, Account, and Settings apps", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} diff --git a/docs/swagger.json b/docs/swagger.json new file mode 100644 index 0000000..b3736f7 --- /dev/null +++ b/docs/swagger.json @@ -0,0 +1,3010 @@ +{ + "swagger": "2.0", + "info": { + "description": "Modular REST API with Blog, Account, and Settings apps", + "title": "Beyhan Backend API", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "name": "API Support", + "email": "support@beyhan.com" + }, + "license": { + "name": "MIT", + "url": "https://opensource.org/licenses/MIT" + }, + "version": "2.0" + }, + "host": "localhost:8080", + "basePath": "/", + "paths": { + "/api/v1/admin/categories": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all categories including inactive ones", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Get all categories (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Create a new category (Admin)", + "parameters": [ + { + "description": "Category object", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Category" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "/api/v1/admin/categories/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single category by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Get category by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Update a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Category object", + "name": "category", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Category" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a category by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "categories" + ], + "summary": "Delete a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/categories/{id}/views": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get view count and details for a specific category", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "category-views" + ], + "summary": "Get view stats for a category (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/category-views": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all category views", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "category-views" + ], + "summary": "Get all category views (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/comments": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all comments", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Get all comments (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/admin/comments/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single comment by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Get comment by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing comment", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Update a comment (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Comment object", + "name": "comment", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a comment by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "comments" + ], + "summary": "Delete a comment (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Comment ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/blacklist": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all CORS blacklist origins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all CORS blacklist entries (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new origin to CORS blacklist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Create CORS blacklist entry (Admin)", + "parameters": [ + { + "description": "Blacklist object", + "name": "blacklist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + } + } + }, + "/api/v1/admin/cors/blacklist/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing CORS blacklist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update CORS blacklist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Blacklist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Blacklist object", + "name": "blacklist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsBlacklist" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a CORS blacklist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Delete CORS blacklist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Blacklist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/cache/invalidate": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Clear the CORS cache to force reload from database", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Invalidate CORS cache (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/cors/whitelist": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all CORS whitelist origins", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all CORS whitelist entries (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Add a new origin to CORS whitelist", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Create CORS whitelist entry (Admin)", + "parameters": [ + { + "description": "Whitelist object", + "name": "whitelist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + } + } + }, + "/api/v1/admin/cors/whitelist/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing CORS whitelist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update CORS whitelist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Whitelist ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Whitelist object", + "name": "whitelist", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.CorsWhitelist" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a CORS whitelist entry", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Delete CORS whitelist entry (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Whitelist ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/permissions": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all permissions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "permissions" + ], + "summary": "Get all permissions (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Permission" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new permission", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "permissions" + ], + "summary": "Create a new permission (Admin)", + "parameters": [ + { + "description": "Permission object", + "name": "permission", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Permission" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Permission" + } + } + } + } + }, + "/api/v1/admin/posts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all posts including inactive", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Get all posts (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new post", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Create a new post (Admin)", + "parameters": [ + { + "description": "Post object", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Post" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + } + }, + "/api/v1/admin/posts/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single post by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Get post by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing post", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Update a post (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Post object", + "name": "post", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Post" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a post by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "posts" + ], + "summary": "Delete a post (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/rate-limits": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all rate limit configurations", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Get all rate limit settings (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.RateLimitSetting" + } + } + } + } + } + }, + "/api/v1/admin/rate-limits/{id}": { + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing rate limit configuration", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "settings" + ], + "summary": "Update rate limit setting (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Rate Limit ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Rate limit object", + "name": "setting", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.RateLimitSetting" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/roles": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all roles with permissions", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Get all roles (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Role" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Create a new role (Admin)", + "parameters": [ + { + "description": "Role object", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Role" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + } + }, + "/api/v1/admin/roles/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single role by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Get role by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing role", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Update a role (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Role object", + "name": "role", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Role" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Role" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a role by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "roles" + ], + "summary": "Delete a role (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Role ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/tags": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get list of all tags including inactive ones", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Get all tags (Admin)", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Create a new tag (Admin)", + "parameters": [ + { + "description": "Tag object", + "name": "tag", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "/api/v1/admin/tags/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single tag by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Get tag by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing tag", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Update a tag (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Tag object", + "name": "tag", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a tag by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "tags" + ], + "summary": "Delete a tag (Admin)", + "parameters": [ + { + "type": "integer", + "description": "Tag ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get paginated list of all users", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Get all users (Admin)", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + }, + { + "type": "boolean", + "description": "Include soft-deleted users", + "name": "include_deleted", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Create a new user (Admin)", + "parameters": [ + { + "description": "User object", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + } + }, + "/api/v1/admin/users/{id}": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get a single user by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Get user by ID (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "put": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Update an existing user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Update a user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "User object", + "name": "user", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.User" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Soft delete a user by ID", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Delete a user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/restore": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Restore a soft-deleted user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Restore a deleted user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/roles": { + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Assign a role to a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Assign role to user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Role ID", + "name": "role_id", + "in": "body", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/admin/users/{id}/roles/{role_id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Remove a role from a user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "admin", + "users" + ], + "summary": "Remove role from user (Admin)", + "parameters": [ + { + "type": "integer", + "description": "User ID", + "name": "id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "Role ID", + "name": "role_id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/auth/github": { + "get": { + "description": "Redirect to GitHub OAuth", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "GitHub OAuth login", + "responses": {} + } + }, + "/api/v1/auth/github/callback": { + "get": { + "description": "Handle GitHub OAuth callback", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "GitHub OAuth callback", + "parameters": [ + { + "type": "string", + "description": "Authorization code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/google": { + "get": { + "description": "Redirect to Google OAuth", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "Google OAuth login", + "responses": {} + } + }, + "/api/v1/auth/google/callback": { + "get": { + "description": "Handle Google OAuth callback", + "produces": [ + "application/json" + ], + "tags": [ + "auth", + "oauth" + ], + "summary": "Google OAuth callback", + "parameters": [ + { + "type": "string", + "description": "Authorization code", + "name": "code", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/login": { + "post": { + "description": "Login with email and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Login user", + "parameters": [ + { + "description": "Login credentials", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + } + } + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/logout": { + "post": { + "description": "Logout (client-side token removal)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Logout user", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/me": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get current authenticated user information", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Get current user", + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.User" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/auth/register": { + "post": { + "description": "Create a new user account with email and password", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "auth" + ], + "summary": "Register a new user", + "parameters": [ + { + "description": "Registration data", + "name": "request", + "in": "body", + "required": true, + "schema": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "type": "object", + "properties": { + "token": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + } + } + } + }, + "400": { + "description": "Bad Request", + "schema": { + "type": "object", + "properties": { + "error": { + "type": "string" + } + } + } + } + } + } + }, + "/api/v1/categories": { + "get": { + "description": "Get list of all active categories (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "categories" + ], + "summary": "Get all active categories", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + } + } + } + } + }, + "/api/v1/categories/{id}/view": { + "post": { + "description": "Record a view event for a category (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "category-views" + ], + "summary": "Track a category view", + "parameters": [ + { + "type": "integer", + "description": "Category ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + }, + "/api/v1/categories/{slug}": { + "get": { + "description": "Get a single category by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "categories" + ], + "summary": "Get category by slug", + "parameters": [ + { + "type": "string", + "description": "Category Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Category" + } + } + } + } + }, + "/api/v1/posts": { + "get": { + "description": "Get paginated list of active posts (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "posts" + ], + "summary": "Get all active posts", + "parameters": [ + { + "type": "integer", + "default": 1, + "description": "Page number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 10, + "description": "Items per page", + "name": "limit", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, + "/api/v1/posts/{id}/comments": { + "get": { + "description": "Get all active comments for a specific post (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "comments" + ], + "summary": "Get comments for a post", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Comment" + } + } + } + } + }, + "post": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Create a new comment (requires authentication)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "comments" + ], + "summary": "Create a comment on a post", + "parameters": [ + { + "type": "integer", + "description": "Post ID", + "name": "id", + "in": "path", + "required": true + }, + { + "description": "Comment object", + "name": "comment", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + ], + "responses": { + "201": { + "description": "Created", + "schema": { + "$ref": "#/definitions/models.Comment" + } + } + } + } + }, + "/api/v1/posts/{slug}": { + "get": { + "description": "Get a single post by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "posts" + ], + "summary": "Get post by slug", + "parameters": [ + { + "type": "string", + "description": "Post Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Post" + } + } + } + } + }, + "/api/v1/tags": { + "get": { + "description": "Get list of all active tags (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "tags" + ], + "summary": "Get all active tags", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + } + }, + "/api/v1/tags/{slug}": { + "get": { + "description": "Get a single tag by its slug (public endpoint)", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "tags" + ], + "summary": "Get tag by slug", + "parameters": [ + { + "type": "string", + "description": "Tag Slug", + "name": "slug", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/models.Tag" + } + } + } + } + }, + "/api/v1/user/social-accounts": { + "get": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Get all social accounts for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social-accounts" + ], + "summary": "Get user's social accounts", + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SocialAccount" + } + } + } + } + } + }, + "/api/v1/user/social-accounts/{id}": { + "delete": { + "security": [ + { + "BearerAuth": [] + } + ], + "description": "Delete a social account for the authenticated user", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "social-accounts" + ], + "summary": "Delete a social account", + "parameters": [ + { + "type": "integer", + "description": "Social Account ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + } + } + } + } + }, + "definitions": { + "models.Category": { + "type": "object", + "properties": { + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + }, + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "keywords": { + "type": "string" + }, + "order": { + "type": "integer" + }, + "parent": { + "$ref": "#/definitions/models.Category" + }, + "parent_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.Comment": { + "type": "object", + "properties": { + "body": { + "type": "string" + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Comment" + } + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "parent": { + "$ref": "#/definitions/models.Comment" + }, + "parent_id": { + "type": "integer" + }, + "product": { + "$ref": "#/definitions/models.Post" + }, + "product_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "models.CorsBlacklist": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "origin": { + "type": "string" + }, + "reason": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.CorsWhitelist": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "created_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "origin": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.Permission": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "user:read, user:write", + "type": "string" + } + } + }, + "models.Post": { + "type": "object", + "properties": { + "categories": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Category" + } + }, + "children": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Post" + } + }, + "content": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "is_active": { + "type": "boolean" + }, + "is_front": { + "type": "boolean" + }, + "keywords": { + "type": "string" + }, + "parent": { + "$ref": "#/definitions/models.Post" + }, + "parent_id": { + "type": "integer" + }, + "slug": { + "type": "string" + }, + "tags": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Tag" + } + }, + "thumb": { + "type": "string" + }, + "title": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user": { + "$ref": "#/definitions/models.User" + }, + "user_id": { + "type": "integer" + }, + "video": { + "type": "string" + } + } + }, + "models.RateLimitSetting": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "max_requests": { + "description": "Max istek sayısı", + "type": "integer" + }, + "name": { + "description": "e.g., \"login\", \"register\", \"api\"", + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "updated_by": { + "type": "string" + }, + "window_seconds": { + "description": "Zaman penceresi (saniye)", + "type": "integer" + } + } + }, + "models.Role": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "admin, user", + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Permission" + } + } + } + }, + "models.SocialAccount": { + "type": "object", + "properties": { + "avatar_url": { + "description": "Avatar URL from provider", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "description": "Full name from provider", + "type": "string" + }, + "provider": { + "description": "google, github", + "type": "string" + }, + "provider_id": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "user_id": { + "type": "integer" + } + } + }, + "models.Tag": { + "type": "object", + "properties": { + "created_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "is_active": { + "type": "boolean" + }, + "slug": { + "type": "string" + }, + "tag": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + } + }, + "models.User": { + "type": "object", + "properties": { + "avatar": { + "description": "Avatar URL from OAuth or uploaded", + "type": "string" + }, + "created_at": { + "type": "string" + }, + "email": { + "type": "string" + }, + "email_verified": { + "description": "Email verification: only required for email/password registration; OAuth users are treated as verified\nChanged to *bool to handle false values correctly with GORM defaults", + "type": "boolean" + }, + "email_verified_at": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "roles": { + "type": "array", + "items": { + "$ref": "#/definitions/models.Role" + } + }, + "social_accounts": { + "type": "array", + "items": { + "$ref": "#/definitions/models.SocialAccount" + } + }, + "updated_at": { + "type": "string" + }, + "username": { + "type": "string" + } + } + } + }, + "securityDefinitions": { + "BearerAuth": { + "description": "Type \"Bearer\" followed by a space and JWT token.", + "type": "apiKey", + "name": "Authorization", + "in": "header" + } + } +} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100644 index 0000000..ca70c9f --- /dev/null +++ b/docs/swagger.yaml @@ -0,0 +1,1942 @@ +basePath: / +definitions: + models.Category: + properties: + children: + items: + $ref: '#/definitions/models.Category' + type: array + created_at: + type: string + description: + type: string + id: + type: integer + image: + type: string + is_active: + type: boolean + keywords: + type: string + order: + type: integer + parent: + $ref: '#/definitions/models.Category' + parent_id: + type: integer + slug: + type: string + title: + type: string + updated_at: + type: string + type: object + models.Comment: + properties: + body: + type: string + children: + items: + $ref: '#/definitions/models.Comment' + type: array + created_at: + type: string + id: + type: integer + is_active: + type: boolean + parent: + $ref: '#/definitions/models.Comment' + parent_id: + type: integer + product: + $ref: '#/definitions/models.Post' + product_id: + type: integer + slug: + type: string + title: + type: string + updated_at: + type: string + user_id: + type: integer + type: object + models.CorsBlacklist: + properties: + created_at: + type: string + created_by: + type: string + id: + type: integer + is_active: + type: boolean + origin: + type: string + reason: + type: string + updated_at: + type: string + type: object + models.CorsWhitelist: + properties: + created_at: + type: string + created_by: + type: string + description: + type: string + id: + type: integer + is_active: + type: boolean + origin: + type: string + updated_at: + type: string + type: object + models.Permission: + properties: + description: + type: string + id: + type: integer + name: + description: user:read, user:write + type: string + type: object + models.Post: + properties: + categories: + items: + $ref: '#/definitions/models.Category' + type: array + children: + items: + $ref: '#/definitions/models.Post' + type: array + content: + type: string + created_at: + type: string + id: + type: integer + image: + type: string + is_active: + type: boolean + is_front: + type: boolean + keywords: + type: string + parent: + $ref: '#/definitions/models.Post' + parent_id: + type: integer + slug: + type: string + tags: + items: + $ref: '#/definitions/models.Tag' + type: array + thumb: + type: string + title: + type: string + updated_at: + type: string + user: + $ref: '#/definitions/models.User' + user_id: + type: integer + video: + type: string + type: object + models.RateLimitSetting: + properties: + created_at: + type: string + description: + type: string + id: + type: integer + is_active: + type: boolean + max_requests: + description: Max istek sayısı + type: integer + name: + description: e.g., "login", "register", "api" + type: string + updated_at: + type: string + updated_by: + type: string + window_seconds: + description: Zaman penceresi (saniye) + type: integer + type: object + models.Role: + properties: + description: + type: string + id: + type: integer + name: + description: admin, user + type: string + permissions: + items: + $ref: '#/definitions/models.Permission' + type: array + type: object + models.SocialAccount: + properties: + avatar_url: + description: Avatar URL from provider + type: string + created_at: + type: string + email: + type: string + id: + type: integer + name: + description: Full name from provider + type: string + provider: + description: google, github + type: string + provider_id: + type: string + updated_at: + type: string + user_id: + type: integer + type: object + models.Tag: + properties: + created_at: + type: string + id: + type: integer + is_active: + type: boolean + slug: + type: string + tag: + type: string + updated_at: + type: string + type: object + models.User: + properties: + avatar: + description: Avatar URL from OAuth or uploaded + type: string + created_at: + type: string + email: + type: string + email_verified: + description: |- + Email verification: only required for email/password registration; OAuth users are treated as verified + Changed to *bool to handle false values correctly with GORM defaults + type: boolean + email_verified_at: + type: string + id: + type: integer + roles: + items: + $ref: '#/definitions/models.Role' + type: array + social_accounts: + items: + $ref: '#/definitions/models.SocialAccount' + type: array + updated_at: + type: string + username: + type: string + type: object +host: localhost:8080 +info: + contact: + email: support@beyhan.com + name: API Support + description: Modular REST API with Blog, Account, and Settings apps + license: + name: MIT + url: https://opensource.org/licenses/MIT + termsOfService: http://swagger.io/terms/ + title: Beyhan Backend API + version: "2.0" +paths: + /api/v1/admin/categories: + get: + consumes: + - application/json + description: Get list of all categories including inactive ones + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Category' + type: array + security: + - BearerAuth: [] + summary: Get all categories (Admin) + tags: + - admin + - categories + post: + consumes: + - application/json + description: Create a new category + parameters: + - description: Category object + in: body + name: category + required: true + schema: + $ref: '#/definitions/models.Category' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Category' + security: + - BearerAuth: [] + summary: Create a new category (Admin) + tags: + - admin + - categories + /api/v1/admin/categories/{id}: + delete: + consumes: + - application/json + description: Delete a category by ID + parameters: + - description: Category ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a category (Admin) + tags: + - admin + - categories + get: + consumes: + - application/json + description: Get a single category by ID + parameters: + - description: Category ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Category' + security: + - BearerAuth: [] + summary: Get category by ID (Admin) + tags: + - admin + - categories + put: + consumes: + - application/json + description: Update an existing category + parameters: + - description: Category ID + in: path + name: id + required: true + type: integer + - description: Category object + in: body + name: category + required: true + schema: + $ref: '#/definitions/models.Category' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Category' + security: + - BearerAuth: [] + summary: Update a category (Admin) + tags: + - admin + - categories + /api/v1/admin/categories/{id}/views: + get: + consumes: + - application/json + description: Get view count and details for a specific category + parameters: + - description: Category ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - BearerAuth: [] + summary: Get view stats for a category (Admin) + tags: + - admin + - category-views + /api/v1/admin/category-views: + get: + consumes: + - application/json + description: Get paginated list of all category views + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 10 + description: Items per page + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - BearerAuth: [] + summary: Get all category views (Admin) + tags: + - admin + - category-views + /api/v1/admin/comments: + get: + consumes: + - application/json + description: Get paginated list of all comments + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 10 + description: Items per page + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - BearerAuth: [] + summary: Get all comments (Admin) + tags: + - admin + - comments + /api/v1/admin/comments/{id}: + delete: + consumes: + - application/json + description: Delete a comment by ID + parameters: + - description: Comment ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a comment (Admin) + tags: + - admin + - comments + get: + consumes: + - application/json + description: Get a single comment by ID + parameters: + - description: Comment ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Comment' + security: + - BearerAuth: [] + summary: Get comment by ID (Admin) + tags: + - admin + - comments + put: + consumes: + - application/json + description: Update an existing comment + parameters: + - description: Comment ID + in: path + name: id + required: true + type: integer + - description: Comment object + in: body + name: comment + required: true + schema: + $ref: '#/definitions/models.Comment' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Comment' + security: + - BearerAuth: [] + summary: Update a comment (Admin) + tags: + - admin + - comments + /api/v1/admin/cors/blacklist: + get: + consumes: + - application/json + description: Get all CORS blacklist origins + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.CorsBlacklist' + type: array + security: + - BearerAuth: [] + summary: Get all CORS blacklist entries (Admin) + tags: + - admin + - settings + post: + consumes: + - application/json + description: Add a new origin to CORS blacklist + parameters: + - description: Blacklist object + in: body + name: blacklist + required: true + schema: + $ref: '#/definitions/models.CorsBlacklist' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.CorsBlacklist' + security: + - BearerAuth: [] + summary: Create CORS blacklist entry (Admin) + tags: + - admin + - settings + /api/v1/admin/cors/blacklist/{id}: + delete: + consumes: + - application/json + description: Delete a CORS blacklist entry + parameters: + - description: Blacklist ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete CORS blacklist entry (Admin) + tags: + - admin + - settings + put: + consumes: + - application/json + description: Update an existing CORS blacklist entry + parameters: + - description: Blacklist ID + in: path + name: id + required: true + type: integer + - description: Blacklist object + in: body + name: blacklist + required: true + schema: + $ref: '#/definitions/models.CorsBlacklist' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Update CORS blacklist entry (Admin) + tags: + - admin + - settings + /api/v1/admin/cors/cache/invalidate: + post: + consumes: + - application/json + description: Clear the CORS cache to force reload from database + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Invalidate CORS cache (Admin) + tags: + - admin + - settings + /api/v1/admin/cors/whitelist: + get: + consumes: + - application/json + description: Get all CORS whitelist origins + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.CorsWhitelist' + type: array + security: + - BearerAuth: [] + summary: Get all CORS whitelist entries (Admin) + tags: + - admin + - settings + post: + consumes: + - application/json + description: Add a new origin to CORS whitelist + parameters: + - description: Whitelist object + in: body + name: whitelist + required: true + schema: + $ref: '#/definitions/models.CorsWhitelist' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.CorsWhitelist' + security: + - BearerAuth: [] + summary: Create CORS whitelist entry (Admin) + tags: + - admin + - settings + /api/v1/admin/cors/whitelist/{id}: + delete: + consumes: + - application/json + description: Delete a CORS whitelist entry + parameters: + - description: Whitelist ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete CORS whitelist entry (Admin) + tags: + - admin + - settings + put: + consumes: + - application/json + description: Update an existing CORS whitelist entry + parameters: + - description: Whitelist ID + in: path + name: id + required: true + type: integer + - description: Whitelist object + in: body + name: whitelist + required: true + schema: + $ref: '#/definitions/models.CorsWhitelist' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Update CORS whitelist entry (Admin) + tags: + - admin + - settings + /api/v1/admin/permissions: + get: + consumes: + - application/json + description: Get list of all permissions + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Permission' + type: array + security: + - BearerAuth: [] + summary: Get all permissions (Admin) + tags: + - admin + - permissions + post: + consumes: + - application/json + description: Create a new permission + parameters: + - description: Permission object + in: body + name: permission + required: true + schema: + $ref: '#/definitions/models.Permission' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Permission' + security: + - BearerAuth: [] + summary: Create a new permission (Admin) + tags: + - admin + - permissions + /api/v1/admin/posts: + get: + consumes: + - application/json + description: Get paginated list of all posts including inactive + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 10 + description: Items per page + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - BearerAuth: [] + summary: Get all posts (Admin) + tags: + - admin + - posts + post: + consumes: + - application/json + description: Create a new post + parameters: + - description: Post object + in: body + name: post + required: true + schema: + $ref: '#/definitions/models.Post' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Post' + security: + - BearerAuth: [] + summary: Create a new post (Admin) + tags: + - admin + - posts + /api/v1/admin/posts/{id}: + delete: + consumes: + - application/json + description: Delete a post by ID + parameters: + - description: Post ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a post (Admin) + tags: + - admin + - posts + get: + consumes: + - application/json + description: Get a single post by ID + parameters: + - description: Post ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Post' + security: + - BearerAuth: [] + summary: Get post by ID (Admin) + tags: + - admin + - posts + put: + consumes: + - application/json + description: Update an existing post + parameters: + - description: Post ID + in: path + name: id + required: true + type: integer + - description: Post object + in: body + name: post + required: true + schema: + $ref: '#/definitions/models.Post' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Post' + security: + - BearerAuth: [] + summary: Update a post (Admin) + tags: + - admin + - posts + /api/v1/admin/rate-limits: + get: + consumes: + - application/json + description: Get all rate limit configurations + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.RateLimitSetting' + type: array + security: + - BearerAuth: [] + summary: Get all rate limit settings (Admin) + tags: + - admin + - settings + /api/v1/admin/rate-limits/{id}: + put: + consumes: + - application/json + description: Update an existing rate limit configuration + parameters: + - description: Rate Limit ID + in: path + name: id + required: true + type: integer + - description: Rate limit object + in: body + name: setting + required: true + schema: + $ref: '#/definitions/models.RateLimitSetting' + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Update rate limit setting (Admin) + tags: + - admin + - settings + /api/v1/admin/roles: + get: + consumes: + - application/json + description: Get list of all roles with permissions + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Role' + type: array + security: + - BearerAuth: [] + summary: Get all roles (Admin) + tags: + - admin + - roles + post: + consumes: + - application/json + description: Create a new role + parameters: + - description: Role object + in: body + name: role + required: true + schema: + $ref: '#/definitions/models.Role' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Role' + security: + - BearerAuth: [] + summary: Create a new role (Admin) + tags: + - admin + - roles + /api/v1/admin/roles/{id}: + delete: + consumes: + - application/json + description: Delete a role by ID + parameters: + - description: Role ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a role (Admin) + tags: + - admin + - roles + get: + consumes: + - application/json + description: Get a single role by ID + parameters: + - description: Role ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Role' + security: + - BearerAuth: [] + summary: Get role by ID (Admin) + tags: + - admin + - roles + put: + consumes: + - application/json + description: Update an existing role + parameters: + - description: Role ID + in: path + name: id + required: true + type: integer + - description: Role object + in: body + name: role + required: true + schema: + $ref: '#/definitions/models.Role' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Role' + security: + - BearerAuth: [] + summary: Update a role (Admin) + tags: + - admin + - roles + /api/v1/admin/tags: + get: + consumes: + - application/json + description: Get list of all tags including inactive ones + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Tag' + type: array + security: + - BearerAuth: [] + summary: Get all tags (Admin) + tags: + - admin + - tags + post: + consumes: + - application/json + description: Create a new tag + parameters: + - description: Tag object + in: body + name: tag + required: true + schema: + $ref: '#/definitions/models.Tag' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Tag' + security: + - BearerAuth: [] + summary: Create a new tag (Admin) + tags: + - admin + - tags + /api/v1/admin/tags/{id}: + delete: + consumes: + - application/json + description: Delete a tag by ID + parameters: + - description: Tag ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a tag (Admin) + tags: + - admin + - tags + get: + consumes: + - application/json + description: Get a single tag by ID + parameters: + - description: Tag ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Tag' + security: + - BearerAuth: [] + summary: Get tag by ID (Admin) + tags: + - admin + - tags + put: + consumes: + - application/json + description: Update an existing tag + parameters: + - description: Tag ID + in: path + name: id + required: true + type: integer + - description: Tag object + in: body + name: tag + required: true + schema: + $ref: '#/definitions/models.Tag' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Tag' + security: + - BearerAuth: [] + summary: Update a tag (Admin) + tags: + - admin + - tags + /api/v1/admin/users: + get: + consumes: + - application/json + description: Get paginated list of all users + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 10 + description: Items per page + in: query + name: limit + type: integer + - description: Include soft-deleted users + in: query + name: include_deleted + type: boolean + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + security: + - BearerAuth: [] + summary: Get all users (Admin) + tags: + - admin + - users + post: + consumes: + - application/json + description: Create a new user + parameters: + - description: User object + in: body + name: user + required: true + schema: + $ref: '#/definitions/models.User' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.User' + security: + - BearerAuth: [] + summary: Create a new user (Admin) + tags: + - admin + - users + /api/v1/admin/users/{id}: + delete: + consumes: + - application/json + description: Soft delete a user by ID + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a user (Admin) + tags: + - admin + - users + get: + consumes: + - application/json + description: Get a single user by ID + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + security: + - BearerAuth: [] + summary: Get user by ID (Admin) + tags: + - admin + - users + put: + consumes: + - application/json + description: Update an existing user + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + - description: User object + in: body + name: user + required: true + schema: + $ref: '#/definitions/models.User' + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + security: + - BearerAuth: [] + summary: Update a user (Admin) + tags: + - admin + - users + /api/v1/admin/users/{id}/restore: + post: + consumes: + - application/json + description: Restore a soft-deleted user + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Restore a deleted user (Admin) + tags: + - admin + - users + /api/v1/admin/users/{id}/roles: + post: + consumes: + - application/json + description: Assign a role to a user + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + - description: Role ID + in: body + name: role_id + required: true + schema: + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Assign role to user (Admin) + tags: + - admin + - users + /api/v1/admin/users/{id}/roles/{role_id}: + delete: + consumes: + - application/json + description: Remove a role from a user + parameters: + - description: User ID + in: path + name: id + required: true + type: integer + - description: Role ID + in: path + name: role_id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Remove role from user (Admin) + tags: + - admin + - users + /api/v1/auth/github: + get: + description: Redirect to GitHub OAuth + produces: + - application/json + responses: {} + summary: GitHub OAuth login + tags: + - auth + - oauth + /api/v1/auth/github/callback: + get: + description: Handle GitHub OAuth callback + parameters: + - description: Authorization code + in: query + name: code + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + properties: + token: + type: string + user: + $ref: '#/definitions/models.User' + type: object + "400": + description: Bad Request + schema: + properties: + error: + type: string + type: object + summary: GitHub OAuth callback + tags: + - auth + - oauth + /api/v1/auth/google: + get: + description: Redirect to Google OAuth + produces: + - application/json + responses: {} + summary: Google OAuth login + tags: + - auth + - oauth + /api/v1/auth/google/callback: + get: + description: Handle Google OAuth callback + parameters: + - description: Authorization code + in: query + name: code + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + properties: + token: + type: string + user: + $ref: '#/definitions/models.User' + type: object + "400": + description: Bad Request + schema: + properties: + error: + type: string + type: object + summary: Google OAuth callback + tags: + - auth + - oauth + /api/v1/auth/login: + post: + consumes: + - application/json + description: Login with email and password + parameters: + - description: Login credentials + in: body + name: request + required: true + schema: + properties: + email: + type: string + password: + type: string + type: object + produces: + - application/json + responses: + "200": + description: OK + schema: + properties: + token: + type: string + user: + $ref: '#/definitions/models.User' + type: object + "400": + description: Bad Request + schema: + properties: + error: + type: string + type: object + "401": + description: Unauthorized + schema: + properties: + error: + type: string + type: object + summary: Login user + tags: + - auth + /api/v1/auth/logout: + post: + consumes: + - application/json + description: Logout (client-side token removal) + produces: + - application/json + responses: + "200": + description: OK + schema: + properties: + message: + type: string + type: object + summary: Logout user + tags: + - auth + /api/v1/auth/me: + get: + consumes: + - application/json + description: Get current authenticated user information + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.User' + "401": + description: Unauthorized + schema: + properties: + error: + type: string + type: object + security: + - BearerAuth: [] + summary: Get current user + tags: + - auth + /api/v1/auth/register: + post: + consumes: + - application/json + description: Create a new user account with email and password + parameters: + - description: Registration data + in: body + name: request + required: true + schema: + properties: + email: + type: string + password: + type: string + username: + type: string + type: object + produces: + - application/json + responses: + "201": + description: Created + schema: + properties: + token: + type: string + user: + $ref: '#/definitions/models.User' + type: object + "400": + description: Bad Request + schema: + properties: + error: + type: string + type: object + summary: Register a new user + tags: + - auth + /api/v1/categories: + get: + consumes: + - application/json + description: Get list of all active categories (public endpoint) + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Category' + type: array + summary: Get all active categories + tags: + - categories + /api/v1/categories/{id}/view: + post: + consumes: + - application/json + description: Record a view event for a category (public endpoint) + parameters: + - description: Category ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + summary: Track a category view + tags: + - category-views + /api/v1/categories/{slug}: + get: + consumes: + - application/json + description: Get a single category by its slug (public endpoint) + parameters: + - description: Category Slug + in: path + name: slug + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Category' + summary: Get category by slug + tags: + - categories + /api/v1/posts: + get: + consumes: + - application/json + description: Get paginated list of active posts (public endpoint) + parameters: + - default: 1 + description: Page number + in: query + name: page + type: integer + - default: 10 + description: Items per page + in: query + name: limit + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: true + type: object + summary: Get all active posts + tags: + - posts + /api/v1/posts/{id}/comments: + get: + consumes: + - application/json + description: Get all active comments for a specific post (public endpoint) + parameters: + - description: Post ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Comment' + type: array + summary: Get comments for a post + tags: + - comments + post: + consumes: + - application/json + description: Create a new comment (requires authentication) + parameters: + - description: Post ID + in: path + name: id + required: true + type: integer + - description: Comment object + in: body + name: comment + required: true + schema: + $ref: '#/definitions/models.Comment' + produces: + - application/json + responses: + "201": + description: Created + schema: + $ref: '#/definitions/models.Comment' + security: + - BearerAuth: [] + summary: Create a comment on a post + tags: + - comments + /api/v1/posts/{slug}: + get: + consumes: + - application/json + description: Get a single post by its slug (public endpoint) + parameters: + - description: Post Slug + in: path + name: slug + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Post' + summary: Get post by slug + tags: + - posts + /api/v1/tags: + get: + consumes: + - application/json + description: Get list of all active tags (public endpoint) + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.Tag' + type: array + summary: Get all active tags + tags: + - tags + /api/v1/tags/{slug}: + get: + consumes: + - application/json + description: Get a single tag by its slug (public endpoint) + parameters: + - description: Tag Slug + in: path + name: slug + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + $ref: '#/definitions/models.Tag' + summary: Get tag by slug + tags: + - tags + /api/v1/user/social-accounts: + get: + consumes: + - application/json + description: Get all social accounts for the authenticated user + produces: + - application/json + responses: + "200": + description: OK + schema: + items: + $ref: '#/definitions/models.SocialAccount' + type: array + security: + - BearerAuth: [] + summary: Get user's social accounts + tags: + - social-accounts + /api/v1/user/social-accounts/{id}: + delete: + consumes: + - application/json + description: Delete a social account for the authenticated user + parameters: + - description: Social Account ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + additionalProperties: + type: string + type: object + security: + - BearerAuth: [] + summary: Delete a social account + tags: + - social-accounts +securityDefinitions: + BearerAuth: + description: Type "Bearer" followed by a space and JWT token. + in: header + name: Authorization + type: apiKey +swagger: "2.0" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d62994a --- /dev/null +++ b/go.mod @@ -0,0 +1,78 @@ +module gobeyhan + +go 1.25 + +require ( + github.com/a-h/templ v0.3.977 + github.com/chai2010/webp v1.4.0 + github.com/disintegration/imaging v1.6.2 + github.com/gin-gonic/gin v1.11.0 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/jackc/pgx/v5 v5.6.0 + github.com/joho/godotenv v1.5.1 + github.com/redis/go-redis/v9 v9.17.3 + github.com/swaggo/files v1.0.1 + github.com/swaggo/gin-swagger v1.6.1 + github.com/swaggo/swag v1.16.6 + golang.org/x/crypto v0.48.0 + golang.org/x/oauth2 v0.35.0 + gorm.io/driver/mysql v1.6.0 + gorm.io/gorm v1.31.1 +) + +require ( + cloud.google.com/go/compute/metadata v0.3.0 // indirect + filippo.io/edwards25519 v1.1.0 // indirect + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/gabriel-vasile/mimetype v1.4.13 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-openapi/jsonpointer v0.22.4 // indirect + github.com/go-openapi/jsonreference v0.21.4 // indirect + github.com/go-openapi/spec v0.22.3 // indirect + github.com/go-openapi/swag/conv v0.25.4 // indirect + github.com/go-openapi/swag/jsonname v0.25.4 // indirect + github.com/go-openapi/swag/jsonutils v0.25.4 // indirect + github.com/go-openapi/swag/loading v0.25.4 // indirect + github.com/go-openapi/swag/stringutils v0.25.4 // indirect + github.com/go-openapi/swag/typeutils v0.25.4 // indirect + github.com/go-openapi/swag/yamlutils v0.25.4 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.59.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + go.uber.org/mock v0.6.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/arch v0.24.0 // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/mod v0.33.0 // indirect + golang.org/x/net v0.50.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/tools v0.42.0 // indirect + google.golang.org/protobuf v1.36.11 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..445839a --- /dev/null +++ b/go.sum @@ -0,0 +1,215 @@ +cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc= +cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/a-h/templ v0.3.977 h1:kiKAPXTZE2Iaf8JbtM21r54A8bCNsncrfnokZZSrSDg= +github.com/a-h/templ v0.3.977/go.mod h1:oCZcnKRf5jjsGpf2yELzQfodLphd2mwecwG4Crk5HBo= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko= +github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM= +github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.11.0 h1:OW/6PLjyusp2PPXtyxKHU0RbX6I/l28FTdDlae5ueWk= +github.com/gin-gonic/gin v1.11.0/go.mod h1:+iq/FyxlGzII0KHiBGjuNn4UNENUlKbGlNmc+W50Dls= +github.com/go-openapi/jsonpointer v0.22.4 h1:dZtK82WlNpVLDW2jlA1YCiVJFVqkED1MegOUy9kR5T4= +github.com/go-openapi/jsonpointer v0.22.4/go.mod h1:elX9+UgznpFhgBuaMQ7iu4lvvX1nvNsesQ3oxmYTw80= +github.com/go-openapi/jsonreference v0.21.4 h1:24qaE2y9bx/q3uRK/qN+TDwbok1NhbSmGjjySRCHtC8= +github.com/go-openapi/jsonreference v0.21.4/go.mod h1:rIENPTjDbLpzQmQWCj5kKj3ZlmEh+EFVbz3RTUh30/4= +github.com/go-openapi/spec v0.22.3 h1:qRSmj6Smz2rEBxMnLRBMeBWxbbOvuOoElvSvObIgwQc= +github.com/go-openapi/spec v0.22.3/go.mod h1:iIImLODL2loCh3Vnox8TY2YWYJZjMAKYyLH2Mu8lOZs= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag/conv v0.25.4 h1:/Dd7p0LZXczgUcC/Ikm1+YqVzkEeCc9LnOWjfkpkfe4= +github.com/go-openapi/swag/conv v0.25.4/go.mod h1:3LXfie/lwoAv0NHoEuY1hjoFAYkvlqI/Bn5EQDD3PPU= +github.com/go-openapi/swag/jsonname v0.25.4 h1:bZH0+MsS03MbnwBXYhuTttMOqk+5KcQ9869Vye1bNHI= +github.com/go-openapi/swag/jsonname v0.25.4/go.mod h1:GPVEk9CWVhNvWhZgrnvRA6utbAltopbKwDu8mXNUMag= +github.com/go-openapi/swag/jsonutils v0.25.4 h1:VSchfbGhD4UTf4vCdR2F4TLBdLwHyUDTd1/q4i+jGZA= +github.com/go-openapi/swag/jsonutils v0.25.4/go.mod h1:7OYGXpvVFPn4PpaSdPHJBtF0iGnbEaTk8AvBkoWnaAY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4 h1:IACsSvBhiNJwlDix7wq39SS2Fh7lUOCJRmx/4SN4sVo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.4/go.mod h1:Mt0Ost9l3cUzVv4OEZG+WSeoHwjWLnarzMePNDAOBiM= +github.com/go-openapi/swag/loading v0.25.4 h1:jN4MvLj0X6yhCDduRsxDDw1aHe+ZWoLjW+9ZQWIKn2s= +github.com/go-openapi/swag/loading v0.25.4/go.mod h1:rpUM1ZiyEP9+mNLIQUdMiD7dCETXvkkC30z53i+ftTE= +github.com/go-openapi/swag/stringutils v0.25.4 h1:O6dU1Rd8bej4HPA3/CLPciNBBDwZj9HiEpdVsb8B5A8= +github.com/go-openapi/swag/stringutils v0.25.4/go.mod h1:GTsRvhJW5xM5gkgiFe0fV3PUlFm0dr8vki6/VSRaZK0= +github.com/go-openapi/swag/typeutils v0.25.4 h1:1/fbZOUN472NTc39zpa+YGHn3jzHWhv42wAJSN91wRw= +github.com/go-openapi/swag/typeutils v0.25.4/go.mod h1:Ou7g//Wx8tTLS9vG0UmzfCsjZjKhpjxayRKTHXf2pTE= +github.com/go-openapi/swag/yamlutils v0.25.4 h1:6jdaeSItEUb7ioS9lFoCZ65Cne1/RZtPBZ9A56h92Sw= +github.com/go-openapi/swag/yamlutils v0.25.4/go.mod h1:MNzq1ulQu+yd8Kl7wPOut/YHAAU/H6hL91fF+E2RFwc= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2 h1:0+Y41Pz1NkbTHz8NngxTuAXxEodtNSI1WG1c/m5Akw4= +github.com/go-openapi/testify/enable/yaml/v2 v2.0.2/go.mod h1:kme83333GCtJQHXQ8UKX3IBZu6z8T5Dvy5+CW3NLUUg= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM= +github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= +github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= +github.com/quic-go/quic-go v0.59.0 h1:OLJkp1Mlm/aS7dpKgTc6cnpynnD2Xg7C1pwL6vy/SAw= +github.com/quic-go/quic-go v0.59.0/go.mod h1:upnsH4Ju1YkqpLXC305eW3yDZ4NfnNbmQRCMWS58IKU= +github.com/redis/go-redis/v9 v9.17.3 h1:fN29NdNrE17KttK5Ndf20buqfDZwGNgoUr9qjl1DQx4= +github.com/redis/go-redis/v9 v9.17.3/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= +github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= +github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY= +github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw= +github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= +github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/arch v0.24.0 h1:qlJ3M9upxvFfwRM51tTg3Yl+8CP9vCC1E7vlFpgv99Y= +golang.org/x/arch v0.24.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= +golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.50.0 h1:ucWh9eiCGyDR3vtzso0WMQinm2Dnt8cFMuQa9K33J60= +golang.org/x/net v0.50.0/go.mod h1:UgoSli3F/pBgdJBHCTc+tp3gmrU4XswgGRgtnwWTfyM= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= +golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/internal/handler/admin/blog_handler.go b/internal/handler/admin/blog_handler.go new file mode 100644 index 0000000..51c5105 --- /dev/null +++ b/internal/handler/admin/blog_handler.go @@ -0,0 +1,537 @@ +package admin + +import ( + "gobeyhan/config" + "gobeyhan/database" + "gobeyhan/database/models" + "gobeyhan/pkg/utils" + "gobeyhan/views/admin/blog" + "log" + "net/http" + "path/filepath" + "strconv" + + "github.com/gin-gonic/gin" +) + +type BlogHandler struct{} + +func NewBlogHandler() *BlogHandler { + return &BlogHandler{} +} + +// List displays all blog posts +func (h *BlogHandler) List(c *gin.Context) { + var posts []models.Post + err := database.DB. + Preload("User"). + Preload("Categories"). + Preload("Tags"). + Order("created_at DESC"). + Find(&posts).Error + + if err != nil { + log.Printf("[BlogHandler.List] Error fetching posts: %v", err) + c.String(http.StatusInternalServerError, "Error fetching posts") + return + } + + blog.List(posts).Render(c.Request.Context(), c.Writer) +} + +// New displays the create form +func (h *BlogHandler) New(c *gin.Context) { + var categories []models.Category + var tags []models.Tag + + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + + blog.Create(categories, tags, nil).Render(c.Request.Context(), c.Writer) +} + +// Create handles post creation +func (h *BlogHandler) Create(c *gin.Context) { + title := c.PostForm("title") + content := c.PostForm("content") + keywords := c.PostForm("keywords") + isActive := c.PostForm("is_active") == "on" + isFront := c.PostForm("is_front") == "on" + + log.Printf("[BlogHandler.Create] Received: Title=%s, ContentSize=%d, Active=%v", title, len(content), isActive) + + // Validation + errors := make(map[string]string) + if title == "" { + errors["title"] = "Title is required" + } + if content == "" { + errors["content"] = "Content is required" + } + + if len(errors) > 0 { + log.Printf("[BlogHandler.Create] Validation failed: %v", errors) + var categories []models.Category + var tags []models.Tag + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + blog.Create(categories, tags, errors).Render(c.Request.Context(), c.Writer) + return + } + + // Handle image upload + imagePath := c.PostForm("image") // Fallback to manual link if provided + file, err := c.FormFile("image_file") + if err == nil && file != nil { + opts := &utils.ImageOptions{ + Width: config.AppConfig.PostImageWidth, + Height: config.AppConfig.PostImageHeight, + Quality: float32(config.AppConfig.PostImageQuality), + Format: config.AppConfig.PostImageFormat, + Mode: config.AppConfig.PostImageMode, + } + path, err := utils.SaveOptimizedImage(file, filepath.Join("uploads", "blog"), "post", opts) + if err == nil { + imagePath = path + log.Printf("[BlogHandler.Create] Image saved to: %s", imagePath) + } else { + log.Printf("[BlogHandler.Create] Image upload error: %v", err) + } + } + + // Create post + post := models.Post{ + Title: title, + Content: content, + Keywords: keywords, + Image: imagePath, + IsActive: isActive, + IsFront: isFront, + } + + if err := database.DB.Create(&post).Error; err != nil { + log.Printf("[BlogHandler.Create] DB Create error: %v", err) + errors["general"] = "Error creating post: " + err.Error() + var categories []models.Category + var tags []models.Tag + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + blog.Create(categories, tags, errors).Render(c.Request.Context(), c.Writer) + return + } + + log.Printf("[BlogHandler.Create] Post created successfully: ID=%d", post.ID) + + // Handle categories + categoryIDs := c.PostFormArray("category_ids") + if len(categoryIDs) > 0 { + var categories []*models.Category + for _, idStr := range categoryIDs { + id, _ := strconv.ParseUint(idStr, 10, 64) + categories = append(categories, &models.Category{ID: id}) + } + if err := database.DB.Model(&post).Association("Categories").Replace(categories); err != nil { + log.Printf("[BlogHandler.Create] Error associating categories: %v", err) + } + } + + // Handle tags + tagIDs := c.PostFormArray("tag_ids") + if len(tagIDs) > 0 { + var tags []*models.Tag + for _, idStr := range tagIDs { + id, _ := strconv.ParseUint(idStr, 10, 64) + tags = append(tags, &models.Tag{ID: id}) + } + if err := database.DB.Model(&post).Association("Tags").Replace(tags); err != nil { + log.Printf("[BlogHandler.Create] Error associating tags: %v", err) + } + } + + c.Redirect(http.StatusSeeOther, "/admin/blog") +} + +// Edit displays the edit form +func (h *BlogHandler) Edit(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + var post models.Post + err = database.DB. + Preload("Categories"). + Preload("Tags"). + First(&post, id).Error + + if err != nil { + log.Printf("[BlogHandler.Edit] Post not found: ID=%d, error=%v", id, err) + c.String(http.StatusNotFound, "Post not found") + return + } + + var categories []models.Category + var tags []models.Tag + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + + blog.Edit(&post, categories, tags, nil).Render(c.Request.Context(), c.Writer) +} + +// Update handles post updates +func (h *BlogHandler) Update(c *gin.Context) { + id := c.Param("id") + idUint, _ := strconv.ParseUint(id, 10, 64) + + title := c.PostForm("title") + content := c.PostForm("content") + keywords := c.PostForm("keywords") + isActive := c.PostForm("is_active") == "on" + isFront := c.PostForm("is_front") == "on" + + log.Printf("[BlogHandler.Update] Received update for ID=%s: Title=%s, ContentSize=%d", id, title, len(content)) + + // Validation + errors := make(map[string]string) + if title == "" { + errors["title"] = "Title is required" + } + if content == "" { + errors["content"] = "Content is required" + } + + if len(errors) > 0 { + log.Printf("[BlogHandler.Update] Validation failed: %v", errors) + var post models.Post + database.DB.Preload("Categories").Preload("Tags").First(&post, idUint) + var categories []models.Category + var tags []models.Tag + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + blog.Edit(&post, categories, tags, errors).Render(c.Request.Context(), c.Writer) + return + } + + // Handle image upload + imagePath := c.PostForm("image") // Keep existing or use manual link + file, err := c.FormFile("image_file") + if err == nil && file != nil { + opts := &utils.ImageOptions{ + Width: config.AppConfig.PostImageWidth, + Height: config.AppConfig.PostImageHeight, + Quality: float32(config.AppConfig.PostImageQuality), + Format: config.AppConfig.PostImageFormat, + Mode: config.AppConfig.PostImageMode, + } + path, err := utils.SaveOptimizedImage(file, filepath.Join("uploads", "blog"), "post", opts) + if err == nil { + imagePath = path + log.Printf("[BlogHandler.Update] Image updated to: %s", imagePath) + } else { + log.Printf("[BlogHandler.Update] Image upload error: %v", err) + } + } + + // Update post + updates := map[string]interface{}{ + "title": title, + "content": content, + "keywords": keywords, + "image": imagePath, + "is_active": isActive, + "is_front": isFront, + } + + if err := database.DB.Model(&models.Post{}).Where("id = ?", id).Updates(updates).Error; err != nil { + log.Printf("[BlogHandler.Update] DB Update error: %v", err) + errors["general"] = "Error updating post" + var post models.Post + database.DB.Preload("Categories").Preload("Tags").First(&post, idUint) + var categories []models.Category + var tags []models.Tag + database.DB.Where("is_active = ?", true).Find(&categories) + database.DB.Where("is_active = ?", true).Find(&tags) + blog.Edit(&post, categories, tags, errors).Render(c.Request.Context(), c.Writer) + return + } + + // Update categories and tags separately (GORM Associations) + var post models.Post + if err := database.DB.First(&post, idUint).Error; err == nil { + // Categories + categoryIDs := c.PostFormArray("category_ids") + var categories []*models.Category + for _, idStr := range categoryIDs { + catID, _ := strconv.ParseUint(idStr, 10, 64) + categories = append(categories, &models.Category{ID: catID}) + } + database.DB.Model(&post).Association("Categories").Replace(categories) + + // Tags + tagIDs := c.PostFormArray("tag_ids") + var tags []*models.Tag + for _, idStr := range tagIDs { + tagID, _ := strconv.ParseUint(idStr, 10, 64) + tags = append(tags, &models.Tag{ID: tagID}) + } + database.DB.Model(&post).Association("Tags").Replace(tags) + } + + log.Printf("[BlogHandler.Update] Post updated successfully: ID=%s", id) + + c.Redirect(http.StatusSeeOther, "/admin/blog") +} + +// Delete handles post deletion +func (h *BlogHandler) Delete(c *gin.Context) { + id := c.Param("id") + log.Printf("[BlogHandler.Delete] Deleting ID=%s", id) + + if err := database.DB.Delete(&models.Post{}, "id = ?", id).Error; err != nil { + log.Printf("[BlogHandler.Delete] Error: %v", err) + c.String(http.StatusInternalServerError, "Error deleting post") + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog") +} + +// ============================================ +// CATEGORY HANDLERS +// ============================================ + +func (h *BlogHandler) ListCategories(c *gin.Context) { + var categories []models.Category + database.DB.Order("`order` ASC").Find(&categories) + blog.CategoryList(categories).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) NewCategory(c *gin.Context) { + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&models.Category{}, categories, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) CreateCategory(c *gin.Context) { + var cat models.Category + cat.Title = c.PostForm("title") + cat.Slug = c.PostForm("slug") + cat.Desc = c.PostForm("description") + cat.Keywords = c.PostForm("keywords") + cat.IsActive = c.PostForm("is_active") == "on" + + order, _ := strconv.Atoi(c.PostForm("order")) + cat.Order = order + + parentIDStr := c.PostForm("parent_id") + if parentIDStr != "" { + pID, _ := strconv.ParseUint(parentIDStr, 10, 64) + cat.ParentID = &pID + } + + if cat.Title == "" { + errors := map[string]string{"title": "Title is required"} + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) + return + } + + if err := database.DB.Create(&cat).Error; err != nil { + errors := map[string]string{"general": err.Error()} + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog/categories") +} + +func (h *BlogHandler) EditCategory(c *gin.Context) { + id := c.Param("id") + var cat models.Category + if err := database.DB.First(&cat, id).Error; err != nil { + c.String(http.StatusNotFound, "Category not found") + return + } + + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&cat, categories, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) UpdateCategory(c *gin.Context) { + id := c.Param("id") + var cat models.Category + if err := database.DB.First(&cat, id).Error; err != nil { + c.String(http.StatusNotFound, "Category not found") + return + } + + cat.Title = c.PostForm("title") + cat.Slug = c.PostForm("slug") + cat.Desc = c.PostForm("description") + cat.Keywords = c.PostForm("keywords") + cat.IsActive = c.PostForm("is_active") == "on" + + order, _ := strconv.Atoi(c.PostForm("order")) + cat.Order = order + + parentIDStr := c.PostForm("parent_id") + if parentIDStr != "" { + pID, _ := strconv.ParseUint(parentIDStr, 10, 64) + cat.ParentID = &pID + } else { + cat.ParentID = nil + } + + if cat.Title == "" { + errors := map[string]string{"title": "Title is required"} + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) + return + } + + if err := database.DB.Save(&cat).Error; err != nil { + errors := map[string]string{"general": err.Error()} + var categories []models.Category + database.DB.Find(&categories) + blog.CategoryForm(&cat, categories, errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog/categories") +} + +func (h *BlogHandler) DeleteCategory(c *gin.Context) { + id := c.Param("id") + database.DB.Delete(&models.Category{}, id) + c.Redirect(http.StatusSeeOther, "/admin/blog/categories") +} + +// ============================================ +// TAG HANDLERS +// ============================================ + +func (h *BlogHandler) ListTags(c *gin.Context) { + var tags []models.Tag + database.DB.Order("tag ASC").Find(&tags) + blog.TagList(tags).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) NewTag(c *gin.Context) { + blog.TagForm(&models.Tag{}, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) CreateTag(c *gin.Context) { + var tag models.Tag + tag.Tag = c.PostForm("tag") + tag.Slug = c.PostForm("slug") + tag.IsActive = true // Default + + if tag.Tag == "" { + errors := map[string]string{"tag": "Tag name is required"} + blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) + return + } + + if err := database.DB.Create(&tag).Error; err != nil { + errors := map[string]string{"general": err.Error()} + blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog/tags") +} + +func (h *BlogHandler) EditTag(c *gin.Context) { + id := c.Param("id") + var tag models.Tag + if err := database.DB.First(&tag, id).Error; err != nil { + c.String(http.StatusNotFound, "Tag not found") + return + } + blog.TagForm(&tag, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) UpdateTag(c *gin.Context) { + id := c.Param("id") + var tag models.Tag + if err := database.DB.First(&tag, id).Error; err != nil { + c.String(http.StatusNotFound, "Tag not found") + return + } + + tag.Tag = c.PostForm("tag") + tag.Slug = c.PostForm("slug") + + if tag.Tag == "" { + errors := map[string]string{"tag": "Tag name is required"} + blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) + return + } + + if err := database.DB.Save(&tag).Error; err != nil { + errors := map[string]string{"general": err.Error()} + blog.TagForm(&tag, errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog/tags") +} + +func (h *BlogHandler) DeleteTag(c *gin.Context) { + id := c.Param("id") + database.DB.Delete(&models.Tag{}, id) + c.Redirect(http.StatusSeeOther, "/admin/blog/tags") +} + +// ============================================ +// COMMENT HANDLERS +// ============================================ + +func (h *BlogHandler) ListComments(c *gin.Context) { + var comments []models.Comment + database.DB.Preload("Product").Order("created_at DESC").Find(&comments) + blog.CommentList(comments).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) EditComment(c *gin.Context) { + id := c.Param("id") + var comment models.Comment + if err := database.DB.Preload("Product").First(&comment, id).Error; err != nil { + c.String(http.StatusNotFound, "Comment not found") + return + } + blog.CommentForm(&comment, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *BlogHandler) UpdateComment(c *gin.Context) { + id := c.Param("id") + var comment models.Comment + if err := database.DB.First(&comment, id).Error; err != nil { + c.String(http.StatusNotFound, "Comment not found") + return + } + + comment.Body = c.PostForm("body") + comment.IsActive = c.PostForm("is_active") == "on" + + if err := database.DB.Save(&comment).Error; err != nil { + errors := map[string]string{"general": err.Error()} + blog.CommentForm(&comment, errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/blog/comments") +} + +func (h *BlogHandler) DeleteComment(c *gin.Context) { + id := c.Param("id") + database.DB.Delete(&models.Comment{}, id) + c.Redirect(http.StatusSeeOther, "/admin/blog/comments") +} diff --git a/internal/handler/admin/handler.go b/internal/handler/admin/handler.go new file mode 100644 index 0000000..b565518 --- /dev/null +++ b/internal/handler/admin/handler.go @@ -0,0 +1,26 @@ +package admin + +import ( + view "gobeyhan/views/admin" + + "github.com/gin-gonic/gin" +) + +type Handler struct{} + +func NewHandler() *Handler { + return &Handler{} +} + +func (h *Handler) LoginPage(c *gin.Context) { + view.Login().Render(c.Request.Context(), c.Writer) +} + +func (h *Handler) LoginPost(c *gin.Context) { + // TODO: Implement actual login logic + c.Redirect(303, "/admin/dashboard") +} + +func (h *Handler) Dashboard(c *gin.Context) { + view.Dashboard().Render(c.Request.Context(), c.Writer) +} diff --git a/internal/handler/admin/settings_handler.go b/internal/handler/admin/settings_handler.go new file mode 100644 index 0000000..d911c4d --- /dev/null +++ b/internal/handler/admin/settings_handler.go @@ -0,0 +1,308 @@ +package admin + +import ( + "gobeyhan/app/settings/services" + "gobeyhan/database/models" + "gobeyhan/views/admin/settings" // We will create this package + "net/http" + "strconv" + + "github.com/gin-gonic/gin" +) + +type SettingsHandler struct { + service *services.SettingsService +} + +func NewSettingsHandler() *SettingsHandler { + return &SettingsHandler{ + service: services.NewSettingsService(), + } +} + +// ==================== WHITELIST ==================== + +func (h *SettingsHandler) ListWhitelist(c *gin.Context) { + items, err := h.service.GetAllCorsWhitelist() + if err != nil { + c.String(http.StatusInternalServerError, "Error fetching whitelist") + return + } + settings.WhitelistList(items).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) NewWhitelist(c *gin.Context) { + settings.WhitelistCreate(nil).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) CreateWhitelist(c *gin.Context) { + origin := c.PostForm("origin") + description := c.PostForm("description") + + // Basic Validation + errors := make(map[string]string) + if origin == "" { + errors["origin"] = "Origin is required" + } + + if len(errors) > 0 { + settings.WhitelistCreate(errors).Render(c.Request.Context(), c.Writer) + return + } + + item := &models.CorsWhitelist{ + Origin: origin, + Description: description, + IsActive: true, + } + + if err := h.service.CreateCorsWhitelist(item); err != nil { + errors["origin"] = "Error creating whitelist entry: " + err.Error() + settings.WhitelistCreate(errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/settings/whitelist") +} + +func (h *SettingsHandler) EditWhitelist(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + item, err := h.service.GetCorsWhitelistByID(id) + if err != nil { + c.String(http.StatusNotFound, "Item not found") + return + } + + settings.WhitelistEdit(item, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) UpdateWhitelist(c *gin.Context) { + id := c.Param("id") + origin := c.PostForm("origin") + description := c.PostForm("description") + + // Basic Validation + errors := make(map[string]string) + if origin == "" { + errors["origin"] = "Origin is required" + } + + if len(errors) > 0 { + // Fetch item again to display form with errors + idUint, _ := strconv.ParseUint(id, 10, 64) + item, _ := h.service.GetCorsWhitelistByID(idUint) + if item == nil { + c.String(http.StatusNotFound, "Item not found") + return + } + // Preserve user input + item.Origin = origin + item.Description = description + settings.WhitelistEdit(item, errors).Render(c.Request.Context(), c.Writer) + return + } + + updates := map[string]interface{}{ + "origin": origin, + "description": description, + } + + if err := h.service.UpdateCorsWhitelist(id, updates); err != nil { + idUint, _ := strconv.ParseUint(id, 10, 64) + item, _ := h.service.GetCorsWhitelistByID(idUint) + settings.WhitelistEdit(item, map[string]string{"origin": "Error updating: " + err.Error()}).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/settings/whitelist") +} + +func (h *SettingsHandler) DeleteWhitelist(c *gin.Context) { + id := c.Param("id") + if err := h.service.DeleteCorsWhitelist(id); err != nil { // Service takes string ID + c.String(http.StatusInternalServerError, "Error deleting item") + return + } + c.Redirect(http.StatusSeeOther, "/admin/settings/whitelist") +} + +// ==================== BLACKLIST ==================== + +func (h *SettingsHandler) ListBlacklist(c *gin.Context) { + items, err := h.service.GetAllCorsBlacklist() + if err != nil { + c.String(http.StatusInternalServerError, "Error fetching blacklist") + return + } + settings.BlacklistList(items).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) NewBlacklist(c *gin.Context) { + settings.BlacklistCreate(nil).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) CreateBlacklist(c *gin.Context) { + origin := c.PostForm("origin") + description := c.PostForm("description") + + errors := make(map[string]string) + if origin == "" { + errors["origin"] = "Origin is required" + } + + if len(errors) > 0 { + settings.BlacklistCreate(errors).Render(c.Request.Context(), c.Writer) + return + } + + item := &models.CorsBlacklist{ + Origin: origin, + Reason: description, + IsActive: true, + } + + if err := h.service.CreateCorsBlacklist(item); err != nil { + errors["origin"] = "Error creating entry: " + err.Error() + settings.BlacklistCreate(errors).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/settings/blacklist") +} + +func (h *SettingsHandler) DeleteBlacklist(c *gin.Context) { + id := c.Param("id") + if err := h.service.DeleteCorsBlacklist(id); err != nil { + c.String(http.StatusInternalServerError, "Error deleting item") + return + } + c.Redirect(http.StatusSeeOther, "/admin/settings/blacklist") +} + +func (h *SettingsHandler) EditBlacklist(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + item, err := h.service.GetCorsBlacklistByID(id) + if err != nil { + c.String(http.StatusNotFound, "Item not found") + return + } + + settings.BlacklistEdit(item, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) UpdateBlacklist(c *gin.Context) { + id := c.Param("id") + origin := c.PostForm("origin") + reason := c.PostForm("reason") + + errors := make(map[string]string) + if origin == "" { + errors["origin"] = "Origin is required" + } + + if len(errors) > 0 { + idUint, _ := strconv.ParseUint(id, 10, 64) + item, _ := h.service.GetCorsBlacklistByID(idUint) + if item == nil { + c.String(http.StatusNotFound, "Item not found") + return + } + item.Origin = origin + item.Reason = reason + settings.BlacklistEdit(item, errors).Render(c.Request.Context(), c.Writer) + return + } + + updates := map[string]interface{}{ + "origin": origin, + "reason": reason, + } + + if err := h.service.UpdateCorsBlacklist(id, updates); err != nil { + idUint, _ := strconv.ParseUint(id, 10, 64) + item, _ := h.service.GetCorsBlacklistByID(idUint) + settings.BlacklistEdit(item, map[string]string{"origin": "Error updating: " + err.Error()}).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/settings/blacklist") +} + +// ==================== RATE LIMITS ==================== + +func (h *SettingsHandler) ListRateLimits(c *gin.Context) { + items, err := h.service.GetAllRateLimitSettings() + if err != nil { + c.String(http.StatusInternalServerError, "Error fetching rate limits") + return + } + settings.RateLimitList(items).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) EditRateLimit(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + item, err := h.service.GetRateLimitSettingByID(id) + if err != nil { + c.String(http.StatusNotFound, "Item not found") + return + } + + settings.RateLimitEdit(item, nil).Render(c.Request.Context(), c.Writer) +} + +func (h *SettingsHandler) UpdateRateLimit(c *gin.Context) { + id := c.Param("id") + limitStr := c.PostForm("max_requests") + windowStr := c.PostForm("window_seconds") + description := c.PostForm("description") + + limit, _ := strconv.ParseInt(limitStr, 10, 64) + window, _ := strconv.Atoi(windowStr) + + updates := map[string]interface{}{ + "description": description, + } + if limit > 0 { + updates["max_requests"] = limit + } + if window > 0 { + updates["window_seconds"] = window + } + + if err := h.service.UpdateRateLimitSetting(id, updates); err != nil { + // Handle error (redisplay form) + idUint, _ := strconv.ParseUint(id, 10, 64) + item, _ := h.service.GetRateLimitSettingByID(idUint) + settings.RateLimitEdit(item, map[string]string{"general": "Error updating: " + err.Error()}).Render(c.Request.Context(), c.Writer) + return + } + + c.Redirect(http.StatusSeeOther, "/admin/settings/rate-limits") +} + +func (h *SettingsHandler) DeleteRateLimit(c *gin.Context) { + id := c.Param("id") + if err := h.service.DeleteRateLimitSetting(id); err != nil { + c.String(http.StatusInternalServerError, "Error deleting item") + return + } + c.Redirect(http.StatusSeeOther, "/admin/settings/rate-limits") +} diff --git a/internal/handler/admin/user_handler.go b/internal/handler/admin/user_handler.go new file mode 100644 index 0000000..f528605 --- /dev/null +++ b/internal/handler/admin/user_handler.go @@ -0,0 +1,195 @@ +package admin + +import ( + "net/http" + "strconv" + + "gobeyhan/app/account/services" + "gobeyhan/database/models" + view "gobeyhan/views/admin/user" + + "github.com/gin-gonic/gin" +) + +type UserHandler struct { + userService *services.UserService + roleService *services.RoleService +} + +func NewUserHandler() *UserHandler { + return &UserHandler{ + userService: services.NewUserService(), + roleService: services.NewRoleService(), + } +} + +// List Users +func (h *UserHandler) List(c *gin.Context) { + users, _, err := h.userService.GetAllUsers(false, 1, 100) // TODO: Implement pagination + if err != nil { + c.String(http.StatusInternalServerError, "Error fetching users") + return + } + view.List(users).Render(c.Request.Context(), c.Writer) +} + +// New User Form +func (h *UserHandler) New(c *gin.Context) { + roles, _ := h.roleService.GetAllRoles() + view.Create(roles, map[string]string{}).Render(c.Request.Context(), c.Writer) +} + +// Create User Action +func (h *UserHandler) Create(c *gin.Context) { + username := c.PostForm("username") + email := c.PostForm("email") + password := c.PostForm("password") + + // Basic Validation + errors := make(map[string]string) + if username == "" { + errors["username"] = "Username is required" + } + if email == "" { + errors["email"] = "Email is required" + } + if password == "" { + errors["password"] = "Password is required" + } + + if len(errors) > 0 { + roles, _ := h.roleService.GetAllRoles() + view.Create(roles, errors).Render(c.Request.Context(), c.Writer) + return + } + + user := &models.User{ + UserName: username, + Email: email, + } + + if err := h.userService.CreateUser(user, password); err != nil { + errors["email"] = "Error creating user (e.g. email exists)" + roles, _ := h.roleService.GetAllRoles() + view.Create(roles, errors).Render(c.Request.Context(), c.Writer) + return + } + + // Handle Role Assignment + roleIDStr := c.PostForm("role_id") + if roleID, err := strconv.ParseUint(roleIDStr, 10, 64); err == nil && roleID > 0 { + h.userService.AssignRole(user.ID, roleID) + } else { + // Assign default role if no role selected (or as fallback) + h.userService.AssignDefaultRole(user.ID) + } + + // Handle Email Verification + emailVerified := c.PostForm("email_verified") == "on" + if emailVerified { + h.userService.UpdateUser(user.ID, map[string]interface{}{ + "email_verified": true, + }) + } + + c.Redirect(http.StatusSeeOther, "/admin/users") +} + +// Edit User Form +func (h *UserHandler) Edit(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + user, err := h.userService.GetUserByID(id) + if err != nil || user == nil { + c.String(http.StatusNotFound, "User not found") + return + } + + roles, _ := h.roleService.GetAllRoles() + view.Edit(*user, roles, map[string]string{}).Render(c.Request.Context(), c.Writer) +} + +// Update User Action +func (h *UserHandler) Update(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + username := c.PostForm("username") + email := c.PostForm("email") + password := c.PostForm("password") + + // Basic Validation + errors := make(map[string]string) + if username == "" { + errors["username"] = "Username is required" + } + if email == "" { + errors["email"] = "Email is required" + } + + if len(errors) > 0 { + user, _ := h.userService.GetUserByID(id) + if user != nil { + // Keep submitted values? simplified for now + user.UserName = username + user.Email = email + roles, _ := h.roleService.GetAllRoles() + view.Edit(*user, roles, errors).Render(c.Request.Context(), c.Writer) + } + return + } + + updates := map[string]interface{}{ + "username": username, + "email": email, + "email_verified": c.PostForm("email_verified") == "on", + } + if password != "" { + updates["password"] = password + } + + if err := h.userService.UpdateUser(id, updates); err != nil { + c.String(http.StatusInternalServerError, "Error updating user") + return + } + + // Update Role + roleIDStr := c.PostForm("role_id") + if roleID, err := strconv.ParseUint(roleIDStr, 10, 64); err == nil && roleID > 0 { + // Remove existing roles first (simplified approach for single role) + // Ideally we should check if role changed + user, _ := h.userService.GetUserByID(id) + if len(user.Roles) > 0 { + h.userService.RemoveRole(id, user.Roles[0].ID) + } + h.userService.AssignRole(id, roleID) + } + + c.Redirect(http.StatusSeeOther, "/admin/users") +} + +// Delete User Action +func (h *UserHandler) Delete(c *gin.Context) { + idStr := c.Param("id") + id, err := strconv.ParseUint(idStr, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid ID") + return + } + + if err := h.userService.DeleteUser(id); err != nil { + c.String(http.StatusInternalServerError, "Error deleting user") + return + } + + c.Redirect(http.StatusSeeOther, "/admin/users") +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a2510ea --- /dev/null +++ b/main.go @@ -0,0 +1,170 @@ +package main + +import ( + "flag" + "fmt" + "log" + "os" + "strings" + + "gobeyhan/app/routes" + "gobeyhan/config" + "gobeyhan/database" + "gobeyhan/pkg/utils" + + "github.com/gin-gonic/gin" + swaggerFiles "github.com/swaggo/files" + ginSwagger "github.com/swaggo/gin-swagger" + + _ "gobeyhan/docs" // Swagger docs +) + +// @title Beyhan Backend API +// @version 2.0 +// @description Modular REST API with Blog, Account, and Settings apps +// @termsOfService http://swagger.io/terms/ + +// @contact.name API Support +// @contact.email support@beyhan.com + +// @license.name MIT +// @license.url https://opensource.org/licenses/MIT + +// @host localhost:8080 +// @BasePath / + +// @securityDefinitions.apikey BearerAuth +// @in header +// @name Authorization +// @description Type "Bearer" followed by a space and JWT token. + +func main() { + config.LoadConfig() + + migrateFlag := flag.Bool("migrate", false, "Run database migrations (AutoMigrate)") + seedFlag := flag.Bool("seed", false, "Run database seeds (may include creating admin user)") + migrateOnlyFlag := flag.Bool("migrate-only", false, "Run migrations and exit (don't start server)") + flag.Parse() + + database.ConnectDB() + + // If seed flag passed, run full seed (includes migrations) immediately regardless of env + if *seedFlag { + database.SeedAll() + log.Println("Seeding complete") + return + } + + // Determine environment: prefer config.AppConfig.Env (set by LoadConfig), else fallback to APP_ENV env var + env := "development" + if config.AppConfig != nil && config.AppConfig.Env != "" { + env = config.AppConfig.Env + } else if v := os.Getenv("APP_ENV"); v != "" { + env = v + } + + // Migration handling + shouldMigrate := *migrateFlag || *migrateOnlyFlag || env == "development" + if shouldMigrate { + if err := database.Migrate(database.DB); err != nil { + log.Fatalf("Migration Yapılamadı: %v", err) + } + log.Println("Migration complete") + } + + // If migrate-only flag, exit after migration + if *migrateOnlyFlag { + return + } + + forceSeed := os.Getenv("FORCE_SEED") + if strings.ToLower(forceSeed) == "true" { + database.SeedAll() + log.Println("Seeding complete") + } + + // Setup Redis + database.ConnectRedis() + if err := database.FlushAll(); err != nil { + log.Printf("Warning: Failed to flush Redis cache: %v", err) + } + + // Print banner + fmt.Println(` + ╔════════════════════════════════════════════════════════╗ + ║ ║ + ║ ██████╗ ███████╗██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ║ + ║ ██╔══██╗██╔════╝╚██╗ ██╔╝██║ ██║██╔══██╗████╗ ██║ ║ + ║ ██████╔╝█████╗ ╚████╔╝ ███████║███████║██╔██╗ ██║ ║ + ║ ██╔══██╗██╔══╝ ╚██╔╝ ██╔══██║██╔══██║██║╚██╗██║ ║ + ║ ██████╔╝███████╗ ██║ ██║ ██║██║ ██║██║ ╚████║ ║ + ║ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ║ + ║ ║ + ║ ██████╗ █████╗ ██████╗██╗ ██╗ ║ + ║ ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝ ║ + ║ ██████╔╝███████║██║ █████╔╝ ║ + ║ ██╔══██╗██╔══██║██║ ██╔═██╗ ║ + ║ ██████╔╝██║ ██║╚██████╗██║ ██╗ ║ + ║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ║ + ║ BACKEND ║ + ║ ║ + ╚════════════════════════════════════════════════════════╝ + `) + fmt.Println(" Go Backend | v2.0.0 | " + utils.ColorGreen + "Running" + utils.ColorReset) + fmt.Println() + + // Initialize Gin + if env == "production" { + gin.SetMode(gin.ReleaseMode) + } + + r := gin.Default() + + // Disable automatic redirects to prevent infinite loops with SPA routing + r.RedirectTrailingSlash = false + r.RedirectFixedPath = false + + // Swagger endpoint + r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) + + // Setup routes from modular apps + routes.SetupRoutes(r) + + // Register Web Handlers (Frontend / Admin Panel) + // admin_panel.RegisterHandlers(r) + + trustedEnv := strings.TrimSpace(os.Getenv("TRUSTED_PROXIES")) + var trusted []string + if trustedEnv != "" { + for _, s := range strings.Split(trustedEnv, ",") { + if t := strings.TrimSpace(s); t != "" { + trusted = append(trusted, t) + } + } + } else { + // Nil ile çağırmak, "hiçbir proxy'ye güvenme" davranışını sağlar + trusted = nil + } + + // Set trusted proxies on Gin router to avoid the "trusted all proxies" warning. + // If `trusted` is nil, no proxies will be trusted. This will error if the list contains an invalid entry. + if err := r.SetTrustedProxies(trusted); err != nil { + log.Fatalf("Failed to set trusted proxies: %v", err) + } + log.Printf("Trusted proxies configured: %v", trusted) + + // Get port from config + port := config.AppConfig.Port + if port == "" { + port = "8080" + } + + // Start server + log.Printf("🚀 Server starting on port %s", port) + log.Printf("📚 Swagger UI: http://localhost:%s/swagger/index.html", port) + log.Printf("🌐 API Base: http://localhost:%s/api/v1", port) + + if err := r.Run(":" + port); err != nil { + log.Fatalf("Failed to start server: %v", err) + } +} diff --git a/pkg/utils/colors.go b/pkg/utils/colors.go new file mode 100644 index 0000000..3895ffa --- /dev/null +++ b/pkg/utils/colors.go @@ -0,0 +1,12 @@ +package utils + +const ( + ColorReset = "\033[0m" + ColorRed = "\033[31m" + ColorGreen = "\033[32m" + ColorYellow = "\033[33m" + ColorBlue = "\033[34m" + ColorPurple = "\033[35m" + ColorCyan = "\033[36m" + ColorWhite = "\033[37m" +) diff --git a/pkg/utils/db_utils.go b/pkg/utils/db_utils.go new file mode 100644 index 0000000..9595eec --- /dev/null +++ b/pkg/utils/db_utils.go @@ -0,0 +1,19 @@ +package utils + +import ( + "errors" + "strings" + + "github.com/jackc/pgx/v5/pgconn" +) + +// IsDuplicateKeyError checks if the error is a PostgreSQL duplicate key violation +func IsDuplicateKeyError(err error) bool { + var pgErr *pgconn.PgError + if errors.As(err, &pgErr) { + // 23505 is the PostgreSQL error code for unique_violation + return pgErr.Code == "23505" + } + // Fallback for other drivers or if error wrapping is different + return strings.Contains(err.Error(), "duplicate key value violates unique constraint") +} diff --git a/pkg/utils/email.go b/pkg/utils/email.go new file mode 100644 index 0000000..57874fb --- /dev/null +++ b/pkg/utils/email.go @@ -0,0 +1,112 @@ +package utils + +import ( + "fmt" + "gobeyhan/config" + "net/smtp" +) + +func SendVerificationEmail(toEmail, token string) error { + // Get config + host := config.AppConfig.EmailHost + port := config.AppConfig.EmailPort + from := config.AppConfig.EmailFrom + if from == "" { + from = "noreply@gauth.local" + } + + // Construct verification link + // Assuming frontend handles verification at /verify-email?token=... + // Or backend endpoint directly: /api/v1/auth/verify-email?token=... + // Let's use APP_URL from config + verifyLink := fmt.Sprintf("%s/v1/auth/verify-email?token=%s", config.AppConfig.AppURL, token) + + // Email content + subject := "Subject: Verify your email address\n" + mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + body := fmt.Sprintf(` + + +

Welcome to GAuth-Central!

+

Please click the link below to verify your email address:

+

Verify Email

+

Or copy and paste this link: %s

+ + + `, verifyLink, verifyLink) + + msg := []byte(subject + mime + body) + + // Address + addr := fmt.Sprintf("%s:%s", host, port) + + // Auth (if needed) + var auth smtp.Auth + if config.AppConfig.EmailHostUser != "" && config.AppConfig.EmailHostPassword != "" { + auth = smtp.PlainAuth("", config.AppConfig.EmailHostUser, config.AppConfig.EmailHostPassword, host) + } + + // Send email + if err := smtp.SendMail(addr, auth, from, []string{toEmail}, msg); err != nil { + return err + } + + return nil +} + +func SendContactEmail(name, email, subject, message, ip string) error { + // Get config + host := config.AppConfig.EmailHost + port := config.AppConfig.EmailPort + from := config.AppConfig.EmailFrom + if from == "" { + from = "noreply@gauth.local" + } + + // Typically, contact form emails are sent TO the admin, not the user who filled the form. + // However, the original code seemed to imply sending it somewhere. + // Let's assume we send it to a configured admin email or the same 'from' address for now. + // Or maybe we send a confirmation to the user? + // The original python code `send_contact_email` likely sent it to the site admins. + // Let's send it to the configured "EmailFrom" address (acting as admin) for this example. + toEmail := config.AppConfig.EmailFrom + if toEmail == "" { + toEmail = "admin@gauth.local" + } + + // Email content + emailSubject := fmt.Sprintf("Subject: New Contact Message: %s\n", subject) + mime := "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n" + body := fmt.Sprintf(` + + +

New Contact Message

+

Name: %s

+

Email: %s

+

IP: %s

+

Subject: %s

+
+

Message:

+

%s

+ + + `, name, email, ip, subject, message) + + msg := []byte(emailSubject + mime + body) + + // Address + addr := fmt.Sprintf("%s:%s", host, port) + + // Auth (if needed) + var auth smtp.Auth + if config.AppConfig.EmailHostUser != "" && config.AppConfig.EmailHostPassword != "" { + auth = smtp.PlainAuth("", config.AppConfig.EmailHostUser, config.AppConfig.EmailHostPassword, host) + } + + // Send email + if err := smtp.SendMail(addr, auth, from, []string{toEmail}, msg); err != nil { + return err + } + + return nil +} diff --git a/pkg/utils/image_processor.go b/pkg/utils/image_processor.go new file mode 100644 index 0000000..96823cc --- /dev/null +++ b/pkg/utils/image_processor.go @@ -0,0 +1,137 @@ +package utils + +import ( + "fmt" + "image" + "image/jpeg" + "image/png" + "mime/multipart" + "os" + "path/filepath" + "strings" + "time" + + "gobeyhan/config" + + "github.com/chai2010/webp" + "github.com/disintegration/imaging" +) + +type ImageOptions struct { + Width int + Height int + Quality float32 // 1-100 (WebP uses float32) + Format string // "webp", "jpg", "png" + Mode string // "cover", "contain", "resize" +} + +// SaveOptimizedImage processes and saves an image +// Returns the relative path to the saved file (e.g., "/uploads/avatars/filename.webp") +func SaveOptimizedImage(fileHeader *multipart.FileHeader, uploadDir string, userID string, opts *ImageOptions) (string, error) { + // If opts is nil, use defaults from config + if opts == nil { + opts = &ImageOptions{ + Width: config.AppConfig.AvatarWidth, + Height: config.AppConfig.AvatarHeight, + Quality: float32(config.AppConfig.AvatarQuality), + Format: config.AppConfig.AvatarFormat, + Mode: config.AppConfig.AvatarMode, + } + } + + // Open the file + srcFile, err := fileHeader.Open() + if err != nil { + return "", fmt.Errorf("failed to open file: %v", err) + } + defer srcFile.Close() + + // Decode image + img, _, err := image.Decode(srcFile) + if err != nil { + return "", fmt.Errorf("failed to decode image: %v", err) + } + + // Resize logic + if opts.Width > 0 || opts.Height > 0 { + switch strings.ToLower(opts.Mode) { + case "cover": + // Fill requires both dimensions to be effective for cropping. + // If one is missing, we fall back to Resize which preserves aspect ratio. + if opts.Width > 0 && opts.Height > 0 { + img = imaging.Fill(img, opts.Width, opts.Height, imaging.Center, imaging.Lanczos) + } else { + img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos) + } + case "contain": + // Fit fits the image within the box, preserving aspect ratio. + // If one dimension is 0, Fit might behave like Resize(w, h) if implementation allows, + // but imaging.Fit usually expects a box. + // If one is 0, we assume the user wants to limit the other dimension. + if opts.Width > 0 && opts.Height > 0 { + img = imaging.Fit(img, opts.Width, opts.Height, imaging.Lanczos) + } else { + img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos) + } + default: + // "resize" or empty: Resize preserves aspect ratio if one arg is 0. + // If both are provided, it stretches unless we use Fill/Fit. + // imaging.Resize stretches if both are non-zero. + img = imaging.Resize(img, opts.Width, opts.Height, imaging.Lanczos) + } + } + + // Determine output format and filename + ext := "." + strings.ToLower(opts.Format) + if opts.Format == "" { + ext = ".webp" // Default to WebP + opts.Format = "webp" + } + + // Generate filename + filename := fmt.Sprintf("%s_%d%s", userID, time.Now().UnixNano(), ext) + + // Ensure directory exists + if err := os.MkdirAll(uploadDir, 0755); err != nil { + return "", fmt.Errorf("failed to create directory: %v", err) + } + + fullPath := filepath.Join(uploadDir, filename) + outFile, err := os.Create(fullPath) + if err != nil { + return "", fmt.Errorf("failed to create output file: %v", err) + } + defer outFile.Close() + + // Encode + switch strings.ToLower(opts.Format) { + case "jpg", "jpeg": + quality := int(opts.Quality) + if quality < 1 || quality > 100 { + quality = 90 + } + err = jpeg.Encode(outFile, img, &jpeg.Options{Quality: quality}) + case "png": + err = png.Encode(outFile, img) // PNG is lossless + case "webp", "": + err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality}) + default: + // Fallback to WebP + err = webp.Encode(outFile, img, &webp.Options{Lossless: false, Quality: opts.Quality}) + } + + if err != nil { + return "", fmt.Errorf("failed to encode image: %v", err) + } + + // Return relative path + relPath := filepath.Join(uploadDir, filename) + if strings.HasPrefix(relPath, ".") { + relPath = relPath[1:] + } + if !strings.HasPrefix(relPath, "/") { + relPath = "/" + relPath + } + + return relPath, nil +} diff --git a/pkg/utils/password.go b/pkg/utils/password.go new file mode 100644 index 0000000..d490df9 --- /dev/null +++ b/pkg/utils/password.go @@ -0,0 +1,15 @@ +package utils + +import ( + "golang.org/x/crypto/bcrypt" +) + +func HashPassword(password string) (string, error) { + bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + return string(bytes), err +} + +func CheckPasswordHash(password, hash string) bool { + err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) + return err == nil +} diff --git a/pkg/utils/token.go b/pkg/utils/token.go new file mode 100644 index 0000000..724f383 --- /dev/null +++ b/pkg/utils/token.go @@ -0,0 +1,15 @@ +package utils + +import ( + "crypto/rand" + "encoding/hex" +) + +// GenerateSecureToken returns a cryptographically random hex string (e.g. for email verification). +func GenerateSecureToken(byteLength int) (string, error) { + b := make([]byte, byteLength) + if _, err := rand.Read(b); err != nil { + return "", err + } + return hex.EncodeToString(b), nil +} diff --git a/seed_force.log b/seed_force.log new file mode 100644 index 0000000..68e1350 --- /dev/null +++ b/seed_force.log @@ -0,0 +1,1505 @@ +2026/02/10 20:06:26 Connected to Database successfully +2026/02/10 20:06:26 Skipping uuid extension: not PostgreSQL dialect +2026/02/10 20:06:26 Skipping user_name migration: not PostgreSQL dialect + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.119ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[4.227ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.257ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'users' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:26  +[1.311ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.341ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[107.802ms] [rows:-] SELECT * FROM `users` LIMIT 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[5.041ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'users' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:26  +[1.454ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[2.108ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[3.443ms] [rows:4] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'users' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:26  +[1.328ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[1.780ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[3.690ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'users' AND constraint_name = 'uni_users_email' + +2026/02/10 20:06:26  +[1.231ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[1.997ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[3.439ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.317ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.945ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.602ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.254ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.922ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.429ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_deleted_at' + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.359ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.925ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.678ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email_verify_token' + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.294ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.907ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.652ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'roles' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:26  +[1.442ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.904ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.601ms] [rows:-] SELECT * FROM `roles` LIMIT 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.105ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'roles' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:26  +[1.360ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[1.858ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[1.788ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'roles' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:26  +[1.126ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[1.807ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[2.169ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'roles' AND constraint_name = 'uni_roles_name' + +2026/02/10 20:06:26  +[1.298ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26  +[2.159ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26  +[1.600ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.555ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.841ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:26 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.695ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[104.593ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.410ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.646ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'user_roles' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.310ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.844ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.974ms] [rows:-] SELECT * FROM `user_roles` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.129ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'user_roles' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.317ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.861ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.069ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_user' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.416ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.917ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.889ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_role' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.199ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.840ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.860ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.411ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.854ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.087ms] [rows:-] SELECT * FROM `social_accounts` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.035ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.409ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.115ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.078ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'social_accounts' AND constraint_name = 'fk_users_social_accounts' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.473ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.113ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.912ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[106.009ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.050ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.191ms] [rows:-] SELECT * FROM `permissions` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.037ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'permissions' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27  +[1.516ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.950ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.756ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'permissions' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:27  +[1.311ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.963ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.866ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'permissions' AND constraint_name = 'uni_permissions_name' + +2026/02/10 20:06:27  +[1.410ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.896ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.611ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.275ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.935ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.770ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.282ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[105.279ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.970ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.475ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.326ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.100ms] [rows:-] SELECT * FROM `role_permissions` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[105.080ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.230ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.888ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.064ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_role' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.290ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.057ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.209ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_permission' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.681ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.987ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.767ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[81.210ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.288ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.504ms] [rows:-] SELECT * FROM `cors_whitelists` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.555ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27  +[1.492ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[2.297ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.795ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_whitelists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:27  +[1.319ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.956ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[2.107ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_whitelists' AND constraint_name = 'uni_cors_whitelists_origin' + +2026/02/10 20:06:27  +[1.172ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.881ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.600ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.361ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.965ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.577ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.147ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.897ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.654ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.446ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.984ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.349ms] [rows:-] SELECT * FROM `cors_blacklists` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.301ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27  +[1.310ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.911ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[2.152ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_blacklists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:27  +[1.379ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.845ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[2.135ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_blacklists' AND constraint_name = 'uni_cors_blacklists_origin' + +2026/02/10 20:06:27  +[1.278ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.856ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.672ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.305ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.904ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.776ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.373ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.140ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.675ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.235ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.012ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.627ms] [rows:-] SELECT * FROM `rate_limit_settings` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.080ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27  +[1.304ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.823ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.952ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'rate_limit_settings' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:27  +[1.507ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[2.061ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.981ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND constraint_name = 'uni_rate_limit_settings_name' + +2026/02/10 20:06:27  +[1.353ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.837ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.667ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.340ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.106ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.723ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.422ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.994ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.733ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'contacts' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.339ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.975ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.878ms] [rows:-] SELECT * FROM `contacts` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.167ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'contacts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.660ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[37.351ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.080ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'contacts' AND constraint_name = 'fk_contacts_user' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.286ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.898ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.621ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.386ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.921ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.098ms] [rows:-] SELECT * FROM `tags` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.037ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.326ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.118ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.683ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.512ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.036ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.095ms] [rows:-] SELECT * FROM `post_categories` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.008ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_categories' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.296ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.836ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.976ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_categories' AND constraint_name = 'fk_post_categories_children' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.287ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.024ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.564ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND index_name = 'idx_post_categories_order' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.414ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.925ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.572ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND index_name = 'idx_post_category_slug_parent' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.269ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.196ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.729ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'posts' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.478ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[4.594ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.246ms] [rows:-] SELECT * FROM `posts` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.124ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'posts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27  +[1.403ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.955ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.907ms] [rows:3] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'posts' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:27  +[1.378ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.988ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[2.055ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'uni_posts_slug' + +2026/02/10 20:06:27  +[1.417ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27  +[1.888ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27  +[1.857ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_slug' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.346ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.894ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[97.275ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'fk_posts_children' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.339ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.894ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.569ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_slug' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.229ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.957ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.729ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_post_categories' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.434ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.000ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.166ms] [rows:-] SELECT * FROM `post_post_categories` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.042ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_post_categories' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.482ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.953ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.237ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_categories' AND constraint_name = 'fk_post_post_categories_post_category' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.479ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.236ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.137ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_categories' AND constraint_name = 'fk_post_post_categories_post' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.455ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.979ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.844ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.487ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.123ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.368ms] [rows:-] SELECT * FROM `post_tags` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.889ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.260ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.980ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.647ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_post_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.376ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.199ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.923ms] [rows:-] SELECT * FROM `post_post_tags` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.539ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_post_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.288ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.196ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.911ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_tags' AND constraint_name = 'fk_post_post_tags_post' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.230ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.892ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.888ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_tags' AND constraint_name = 'fk_post_post_tags_post_tag' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.395ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.750ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.562ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_comments' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:27  +[1.275ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.812ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.125ms] [rows:-] SELECT * FROM `post_comments` LIMIT 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.558ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_comments' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.336ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.904ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.103ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_post_comments_user' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.356ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.885ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.981ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_post_comments_children' + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.305ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:27 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.024ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.965ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_posts_comments' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.366ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.846ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.737ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_comments' AND index_name = 'idx_post_comment_slug_parent' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.474ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.930ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.620ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.399ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.854ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.145ms] [rows:-] SELECT * FROM `post_category_views` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.368ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.277ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.782ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.065ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_category_views' AND constraint_name = 'fk_post_category_views_category' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.439ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.992ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.798ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' AND index_name = 'idx_post_category_views' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.496ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.068ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.830ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'homes' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.712ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.982ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.379ms] [rows:-] SELECT * FROM `homes` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.390ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'homes' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28  +[1.342ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[2.033ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.688ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'homes' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:28  +[1.125ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.973ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[2.098ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'homes' AND constraint_name = 'uni_homes_slug' + +2026/02/10 20:06:28  +[1.370ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[107.828ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[106.206ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'homes' AND index_name = 'idx_homes_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.051ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.074ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.651ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'homes' AND index_name = 'idx_homes_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.600ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.127ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.830ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'home_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.417ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.825ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.748ms] [rows:-] SELECT * FROM `home_tags` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.002ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'home_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.531ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.084ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.959ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'home_tags' AND constraint_name = 'fk_home_tags_home' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.283ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.828ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.879ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'home_tags' AND constraint_name = 'fk_home_tags_tag' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.237ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.075ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.536ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'about' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.186ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.957ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.359ms] [rows:-] SELECT * FROM `about` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.502ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'about' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28  +[1.668ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[2.046ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.680ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'about' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:28  +[1.353ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.961ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.953ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'about' AND constraint_name = 'uni_about_slug' + +2026/02/10 20:06:28  +[1.341ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.840ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.846ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'about' AND index_name = 'idx_about_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.198ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.891ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.643ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'about' AND index_name = 'idx_about_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.384ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.994ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.479ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'services' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.443ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.050ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.002ms] [rows:-] SELECT * FROM `services` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.028ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'services' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28  +[1.178ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.726ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.613ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'services' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:06:28  +[1.347ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.911ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.947ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'services' AND constraint_name = 'uni_services_slug' + +2026/02/10 20:06:28  +[1.394ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28  +[1.781ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28  +[1.676ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'services' AND index_name = 'idx_services_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.092ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.942ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.625ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'services' AND index_name = 'idx_services_slug' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.397ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.010ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.639ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'services_title' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.425ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.005ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.027ms] [rows:-] SELECT * FROM `services_title` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.133ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'services_title' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.344ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.842ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.668ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'resumes' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.444ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.867ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.303ms] [rows:-] SELECT * FROM `resumes` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.132ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'resumes' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.230ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.680ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.827ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'educations' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.539ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.819ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.121ms] [rows:-] SELECT * FROM `educations` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.321ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'educations' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.483ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.165ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.281ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'educations' AND constraint_name = 'fk_resumes_educations' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.534ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.934ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.692ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'experience' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.368ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.138ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.477ms] [rows:-] SELECT * FROM `experience` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.029ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'experience' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.497ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.907ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.950ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'experience' AND constraint_name = 'fk_resumes_experiences' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.338ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.923ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.832ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'skills' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.435ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.847ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.102ms] [rows:-] SELECT * FROM `skills` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.952ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'skills' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.411ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.888ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.048ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'skills' AND constraint_name = 'fk_resumes_skills' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.540ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.888ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.739ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'knowledges' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.390ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.991ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.979ms] [rows:-] SELECT * FROM `knowledges` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.937ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'knowledges' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.408ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.126ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.849ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'knowledges' AND constraint_name = 'fk_resumes_knowledges' + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.419ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.898ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.651ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'mane_menu' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.223ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.300ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.332ms] [rows:-] SELECT * FROM `mane_menu` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.197ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'mane_menu' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.486ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.056ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.624ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.394ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.040ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.282ms] [rows:-] SELECT * FROM `settings` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.384ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'settings' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.423ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.121ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[105.916ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'banners' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.472ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.158ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.605ms] [rows:-] SELECT * FROM `banners` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.282ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'banners' ORDER BY ORDINAL_POSITION + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.388ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.922ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[1.839ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'site_settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:06:28  +[1.445ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[2.171ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.424ms] [rows:-] SELECT * FROM `site_settings` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:79 +[3.138ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'site_settings' ORDER BY ORDINAL_POSITION +2026/02/10 20:06:28 Database Migration Completed + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:213 +[4.027ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'user:read' AND `permissions`.`description` = 'Can read user data' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:213 +[1.771ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'user:write' AND `permissions`.`description` = 'Can modify user data' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:213 +[1.813ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'admin:access' AND `permissions`.`description` = 'Can access admin panel' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:219 +[3.318ms] [rows:1] SELECT * FROM `roles` WHERE `roles`.`name` = 'admin' AND `roles`.`description` = 'Default admin role' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:219 +[1.683ms] [rows:1] SELECT * FROM `roles` WHERE `roles`.`name` = 'user' AND `roles`.`description` = 'Default user role' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:224 +[3.600ms] [rows:3] SELECT * FROM `role_permissions` WHERE `role_permissions`.`role_id` = 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:224 +[3.430ms] [rows:3] SELECT * FROM `permissions` WHERE `permissions`.`id` IN (1,2,3) + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:224 +[14.491ms] [rows:1] SELECT * FROM `roles` WHERE name = 'admin' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:228 +[3.372ms] [rows:3] SELECT * FROM `permissions` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:231 +[6.469ms] [rows:3] INSERT INTO `permissions` (`name`,`description`,`id`) VALUES ('user:read','Can read user data',1),('user:write','Can modify user data',2),('admin:access','Can access admin panel',3) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:231 +[4.648ms] [rows:0] INSERT INTO `role_permissions` (`role_id`,`permission_id`) VALUES (1,1),(1,2),(1,3) ON DUPLICATE KEY UPDATE `role_id`=`role_id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:231 +[7.717ms] [rows:0] DELETE FROM `role_permissions` WHERE `role_permissions`.`role_id` = 1 AND `role_permissions`.`permission_id` NOT IN (1,2,3) + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:237 +[1.777ms] [rows:1] SELECT * FROM `role_permissions` WHERE `role_permissions`.`role_id` = 2 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:237 +[3.326ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`id` = 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:237 +[6.885ms] [rows:1] SELECT * FROM `roles` WHERE name = 'user' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:240 +[3.291ms] [rows:1] SELECT * FROM `permissions` WHERE name IN ('user:read') + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:242 +[4.627ms] [rows:1] INSERT INTO `permissions` (`name`,`description`,`id`) VALUES ('user:read','Can read user data',1) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:242 +[4.295ms] [rows:0] INSERT INTO `role_permissions` (`role_id`,`permission_id`) VALUES (2,1) ON DUPLICATE KEY UPDATE `role_id`=`role_id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:242 +[7.753ms] [rows:0] DELETE FROM `role_permissions` WHERE `role_permissions`.`role_id` = 2 AND `role_permissions`.`permission_id` <> 1 +2026/02/10 20:06:28 Roles and Permissions seeded + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:252 +[3.146ms] [rows:1] SELECT count(*) FROM `cors_whitelists` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:278 +[3.097ms] [rows:1] SELECT count(*) FROM `rate_limit_settings` +Varsayılan Yönetici Yaratılıyor... +2026/02/10 20:06:28 Seeding default admin (email=admin@gauth.local, force=true) + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:328 +[3.733ms] [rows:1] SELECT * FROM `users` WHERE email = 'admin@gauth.local' ORDER BY `users`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:371 +[11.265ms] [rows:1] UPDATE `users` SET `email_verified`=true,`password`='$2a$10$euI065n8fjqOxPZSMSh50OWpxBCrro3ZV8Bsh.XPNFGXPj/9ChQDK',`user_name`='admin',`updated_at`='2026-02-10 20:06:28.839' WHERE `users`.`deleted_at` IS NULL AND `id` = 'f86dcaea-b856-4ff4-b1a0-9ab6535a93d5' +2026/02/10 20:06:28 ✅ Admin password reset (force=true). + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:384 +[1.925ms] [rows:1] SELECT * FROM `roles` WHERE name = 'admin' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:389 +[4.695ms] [rows:1] INSERT INTO `roles` (`name`,`description`,`id`) VALUES ('admin','Default admin role',1) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:389 +[4.237ms] [rows:0] INSERT INTO `user_roles` (`user_id`,`role_id`) VALUES ('f86dcaea-b856-4ff4-b1a0-9ab6535a93d5',1) ON DUPLICATE KEY UPDATE `user_id`=`user_id` + +2026/02/10 20:06:28 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:389 +[23.554ms] [rows:1] UPDATE `users` SET `updated_at`='2026-02-10 20:06:28.854' WHERE `users`.`deleted_at` IS NULL AND `id` = 'f86dcaea-b856-4ff4-b1a0-9ab6535a93d5' +2026/02/10 20:06:28 Admin role assigned +Varsayılan Yönetici Yaratıldı... diff --git a/seed_run.log b/seed_run.log new file mode 100644 index 0000000..48b48e9 --- /dev/null +++ b/seed_run.log @@ -0,0 +1,1499 @@ +2026/02/10 20:02:31 Connected to Database successfully +2026/02/10 20:02:31 Skipping uuid extension: not PostgreSQL dialect +2026/02/10 20:02:31 Skipping user_name migration: not PostgreSQL dialect + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.405ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.508ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.784ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'users' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[1.445ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.346ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.615ms] [rows:-] SELECT * FROM `users` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.915ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'users' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31  +[1.001ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.319ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[5.104ms] [rows:4] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'users' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:31  +[1.846ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.307ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[2.989ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'users' AND constraint_name = 'uni_users_email' + +2026/02/10 20:02:31  +[1.568ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.405ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[2.263ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.346ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.125ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.050ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[0.935ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.308ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.184ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_deleted_at' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.108ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.601ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.371ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email_verify_token' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[0.950ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.495ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.306ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'roles' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[1.860ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.466ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.258ms] [rows:-] SELECT * FROM `roles` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.637ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'roles' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31  +[0.991ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.258ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.218ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'roles' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:31  +[1.111ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[105.129ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[2.267ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'roles' AND constraint_name = 'uni_roles_name' + +2026/02/10 20:02:31  +[1.717ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.874ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.933ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[59.017ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.180ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.624ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.405ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.732ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.599ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'user_roles' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[1.570ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.949ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.950ms] [rows:-] SELECT * FROM `user_roles` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.975ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'user_roles' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.192ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.912ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.976ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_user' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.481ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.030ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.013ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_role' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.227ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.814ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.679ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[2.445ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.190ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.973ms] [rows:-] SELECT * FROM `social_accounts` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.700ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.799ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[57.904ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[5.238ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'social_accounts' AND constraint_name = 'fk_users_social_accounts' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.650ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.052ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.636ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[3.249ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.950ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.959ms] [rows:-] SELECT * FROM `permissions` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.888ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'permissions' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31  +[1.191ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.966ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.702ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'permissions' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:31  +[1.229ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[2.030ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.959ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'permissions' AND constraint_name = 'uni_permissions_name' + +2026/02/10 20:02:31  +[1.308ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.780ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.674ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.355ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.004ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.767ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.103ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.051ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.728ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[1.131ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.925ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.775ms] [rows:-] SELECT * FROM `role_permissions` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.270ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.105ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.971ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.213ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_role' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.165ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.014ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.000ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_permission' + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.208ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.936ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.912ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:31  +[1.593ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.059ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.029ms] [rows:-] SELECT * FROM `cors_whitelists` LIMIT 1 + +2026/02/10 20:02:31 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.024ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:31  +[1.339ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:31  +[1.889ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:31  +[1.774ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_whitelists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:31  +[1.335ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.881ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[2.100ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_whitelists' AND constraint_name = 'uni_cors_whitelists_origin' + +2026/02/10 20:02:32  +[1.201ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.962ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[104.930ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.499ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.880ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.563ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.348ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.760ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.762ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.509ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[104.715ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.006ms] [rows:-] SELECT * FROM `cors_blacklists` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.083ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32  +[1.206ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.961ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[3.067ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_blacklists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:32  +[1.262ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.739ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[1.994ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_blacklists' AND constraint_name = 'uni_cors_blacklists_origin' + +2026/02/10 20:02:32  +[1.298ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.945ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[1.540ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.267ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.790ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.899ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.218ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.855ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.631ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.340ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.988ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.470ms] [rows:-] SELECT * FROM `rate_limit_settings` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.993ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32  +[1.217ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[2.196ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[108.514ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'rate_limit_settings' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:32  +[1.611ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[2.026ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[2.292ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND constraint_name = 'uni_rate_limit_settings_name' + +2026/02/10 20:02:32  +[1.225ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[2.000ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[1.633ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.299ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.914ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.664ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.402ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.047ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.651ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'contacts' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.467ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.064ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.215ms] [rows:-] SELECT * FROM `contacts` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.270ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'contacts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.320ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.021ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.002ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'contacts' AND constraint_name = 'fk_contacts_user' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.314ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.830ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.716ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.349ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.027ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.276ms] [rows:-] SELECT * FROM `tags` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.690ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.575ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.147ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.024ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.400ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.029ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.799ms] [rows:-] SELECT * FROM `post_categories` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[108.347ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_categories' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.628ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.232ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.205ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_categories' AND constraint_name = 'fk_post_categories_children' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.352ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.029ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.733ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND index_name = 'idx_post_categories_order' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.362ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.087ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.729ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND index_name = 'idx_post_category_slug_parent' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.492ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.124ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[106.834ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'posts' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.489ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.187ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[110.957ms] [rows:-] SELECT * FROM `posts` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.386ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'posts' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32  +[2.753ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[2.445ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[3.190ms] [rows:3] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'posts' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:32  +[1.452ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.952ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[1.978ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'uni_posts_slug' + +2026/02/10 20:02:32  +[67.427ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32  +[1.921ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32  +[1.605ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_slug' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.401ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.797ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.434ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'fk_posts_children' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.733ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[32.744ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.769ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_slug' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.448ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.131ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.754ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_post_categories' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.334ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.205ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.686ms] [rows:-] SELECT * FROM `post_post_categories` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.612ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_post_categories' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.359ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.270ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.453ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_categories' AND constraint_name = 'fk_post_post_categories_post_category' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.013ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.973ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.353ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_categories' AND constraint_name = 'fk_post_post_categories_post' + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.261ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.129ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.139ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.634ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.190ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.229ms] [rows:-] SELECT * FROM `post_tags` LIMIT 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.791ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.271ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.442ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.132ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_post_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:32  +[1.925ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:32 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.354ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.299ms] [rows:-] SELECT * FROM `post_post_tags` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[110.273ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_post_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.458ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.697ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.212ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_tags' AND constraint_name = 'fk_post_post_tags_post' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.027ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.337ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.588ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_post_tags' AND constraint_name = 'fk_post_post_tags_post_tag' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.585ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.738ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.918ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_comments' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.050ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.327ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.756ms] [rows:-] SELECT * FROM `post_comments` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.270ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_comments' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.525ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.110ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.664ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_post_comments_children' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.406ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.826ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.488ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_posts_comments' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.403ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.707ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.045ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_comments' AND constraint_name = 'fk_post_comments_user' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.896ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.109ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.754ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_comments' AND index_name = 'idx_post_comment_slug_parent' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.270ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.456ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.254ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.648ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[107.267ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[6.883ms] [rows:-] SELECT * FROM `post_category_views` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[5.019ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.375ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.202ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.082ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_category_views' AND constraint_name = 'fk_post_category_views_category' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.337ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.058ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.672ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'post_category_views' AND index_name = 'idx_post_category_views' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.480ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.021ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.608ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'homes' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.594ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.113ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.921ms] [rows:-] SELECT * FROM `homes` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.668ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'homes' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33  +[2.974ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[3.059ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[2.352ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'homes' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:33  +[1.665ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[2.722ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[2.128ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'homes' AND constraint_name = 'uni_homes_slug' + +2026/02/10 20:02:33  +[1.641ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[2.073ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[2.124ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'homes' AND index_name = 'idx_homes_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.306ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.024ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.719ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'homes' AND index_name = 'idx_homes_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.458ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.494ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[107.474ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'home_tags' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.455ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.441ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.134ms] [rows:-] SELECT * FROM `home_tags` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.491ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'home_tags' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[106.164ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.327ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.238ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'home_tags' AND constraint_name = 'fk_home_tags_home' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.483ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.092ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.964ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'home_tags' AND constraint_name = 'fk_home_tags_tag' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[86.197ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.059ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.844ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'about' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.619ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.824ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.292ms] [rows:-] SELECT * FROM `about` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.299ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'about' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33  +[1.296ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[1.966ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[1.577ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'about' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:33  +[1.331ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[1.908ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[1.724ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'about' AND constraint_name = 'uni_about_slug' + +2026/02/10 20:02:33  +[1.271ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[2.002ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[1.661ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'about' AND index_name = 'idx_about_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.318ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.763ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.587ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'about' AND index_name = 'idx_about_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.166ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.808ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.617ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'services' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.356ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.925ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.856ms] [rows:-] SELECT * FROM `services` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.980ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'services' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33  +[1.059ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[2.035ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[1.786ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'services' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/10 20:02:33  +[1.319ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[2.040ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[2.043ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'services' AND constraint_name = 'uni_services_slug' + +2026/02/10 20:02:33  +[1.776ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33  +[107.224ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33  +[1.562ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'services' AND index_name = 'idx_services_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.192ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.915ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.559ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'services' AND index_name = 'idx_services_slug' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.184ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.755ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.712ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'services_title' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.396ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.253ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.898ms] [rows:-] SELECT * FROM `services_title` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.086ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'services_title' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.259ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.256ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.530ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'resumes' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.276ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.794ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.050ms] [rows:-] SELECT * FROM `resumes` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.034ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'resumes' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.154ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.982ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.530ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'educations' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.253ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.775ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.110ms] [rows:-] SELECT * FROM `educations` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.073ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'educations' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.271ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.991ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.951ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'educations' AND constraint_name = 'fk_resumes_educations' + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.547ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.843ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.585ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'experience' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:33  +[1.242ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.906ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.141ms] [rows:-] SELECT * FROM `experience` LIMIT 1 + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[4.131ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'experience' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.591ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[107.795ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.249ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'experience' AND constraint_name = 'fk_resumes_experiences' + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.786ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.041ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[105.926ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'skills' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.405ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.203ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.321ms] [rows:-] SELECT * FROM `skills` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[7.111ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'skills' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.165ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.053ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[108.144ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'skills' AND constraint_name = 'fk_resumes_skills' + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.520ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.139ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.722ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'knowledges' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.487ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.984ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.341ms] [rows:-] SELECT * FROM `knowledges` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.159ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'knowledges' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.456ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.020ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.938ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'knowledges' AND constraint_name = 'fk_resumes_knowledges' + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.227ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.826ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[106.974ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'mane_menu' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.504ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.255ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.314ms] [rows:-] SELECT * FROM `mane_menu` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[105.578ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'mane_menu' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.045ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.909ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.774ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.357ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.774ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.411ms] [rows:-] SELECT * FROM `settings` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.585ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'settings' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.309ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.860ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.753ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'banners' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.442ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.951ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.978ms] [rows:-] SELECT * FROM `banners` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[2.953ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'banners' ORDER BY ORDINAL_POSITION + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.802ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[77.933ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.661ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'site_settings' AND table_type = 'BASE TABLE' + +2026/02/10 20:02:34  +[1.383ms] [rows:-] SELECT DATABASE() + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[1.955ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.140ms] [rows:-] SELECT * FROM `site_settings` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:96 +[3.811ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'site_settings' ORDER BY ORDINAL_POSITION +2026/02/10 20:02:34 Database Migration Completed + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:230 +[3.100ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'user:read' AND `permissions`.`description` = 'Can read user data' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:230 +[1.599ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'user:write' AND `permissions`.`description` = 'Can modify user data' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:230 +[1.533ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`name` = 'admin:access' AND `permissions`.`description` = 'Can access admin panel' ORDER BY `permissions`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:236 +[3.036ms] [rows:1] SELECT * FROM `roles` WHERE `roles`.`name` = 'admin' AND `roles`.`description` = 'Default admin role' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:236 +[1.733ms] [rows:1] SELECT * FROM `roles` WHERE `roles`.`name` = 'user' AND `roles`.`description` = 'Default user role' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:241 +[3.154ms] [rows:3] SELECT * FROM `role_permissions` WHERE `role_permissions`.`role_id` = 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:241 +[3.073ms] [rows:3] SELECT * FROM `permissions` WHERE `permissions`.`id` IN (1,2,3) + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:241 +[9.728ms] [rows:1] SELECT * FROM `roles` WHERE name = 'admin' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:245 +[2.863ms] [rows:3] SELECT * FROM `permissions` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:248 +[4.880ms] [rows:3] INSERT INTO `permissions` (`name`,`description`,`id`) VALUES ('user:read','Can read user data',1),('user:write','Can modify user data',2),('admin:access','Can access admin panel',3) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:248 +[4.489ms] [rows:0] INSERT INTO `role_permissions` (`role_id`,`permission_id`) VALUES (1,1),(1,2),(1,3) ON DUPLICATE KEY UPDATE `role_id`=`role_id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:248 +[8.110ms] [rows:0] DELETE FROM `role_permissions` WHERE `role_permissions`.`role_id` = 1 AND `role_permissions`.`permission_id` NOT IN (1,2,3) + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:252 +[1.693ms] [rows:1] SELECT * FROM `role_permissions` WHERE `role_permissions`.`role_id` = 2 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:252 +[9.062ms] [rows:1] SELECT * FROM `permissions` WHERE `permissions`.`id` = 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:252 +[12.602ms] [rows:1] SELECT * FROM `roles` WHERE name = 'user' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:255 +[2.725ms] [rows:1] SELECT * FROM `permissions` WHERE name IN ('user:read') + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:257 +[5.019ms] [rows:1] INSERT INTO `permissions` (`name`,`description`,`id`) VALUES ('user:read','Can read user data',1) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:257 +[4.157ms] [rows:0] INSERT INTO `role_permissions` (`role_id`,`permission_id`) VALUES (2,1) ON DUPLICATE KEY UPDATE `role_id`=`role_id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:257 +[7.241ms] [rows:0] DELETE FROM `role_permissions` WHERE `role_permissions`.`role_id` = 2 AND `role_permissions`.`permission_id` <> 1 +2026/02/10 20:02:34 Roles and Permissions seeded + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:265 +[2.894ms] [rows:1] SELECT count(*) FROM `cors_whitelists` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:291 +[3.069ms] [rows:1] SELECT count(*) FROM `rate_limit_settings` +Varsayılan Yönetici Yaratılıyor... + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:329 +[3.554ms] [rows:1] SELECT * FROM `users` WHERE email = 'beyhan@beyhan.dev' ORDER BY `users`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:370 +[1.516ms] [rows:1] SELECT * FROM `roles` WHERE name = 'admin' ORDER BY `roles`.`id` LIMIT 1 + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:375 +[4.913ms] [rows:1] INSERT INTO `roles` (`name`,`description`,`id`) VALUES ('admin','Default admin role',1) ON DUPLICATE KEY UPDATE `id`=`id` RETURNING `id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:375 +[4.532ms] [rows:0] INSERT INTO `user_roles` (`user_id`,`role_id`) VALUES ('4100057c-514f-4fcc-a3b6-f6fe4e737dc0',1) ON DUPLICATE KEY UPDATE `user_id`=`user_id` + +2026/02/10 20:02:34 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/db.go:375 +[24.403ms] [rows:1] UPDATE `users` SET `updated_at`='2026-02-10 20:02:34.745' WHERE `users`.`deleted_at` IS NULL AND `id` = '4100057c-514f-4fcc-a3b6-f6fe4e737dc0' +Varsayılan Yönetici Yaratıldı... diff --git a/server.log b/server.log new file mode 100644 index 0000000..935ca98 --- /dev/null +++ b/server.log @@ -0,0 +1,1235 @@ +2026/02/12 03:51:08 Connected to Database successfully +2026/02/12 03:51:08 UUID extension step skipped for MySQL (not required) + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.087ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.892ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.507ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'users' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.106ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.793ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.345ms] [rows:-] SELECT * FROM `users` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.693ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'users' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[2.053ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.799ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[5.655ms] [rows:4] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'users' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.567ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[1.970ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[4.387ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'users' AND constraint_name = 'uni_users_email' + +2026/02/12 03:51:08  +[1.867ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[1.589ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[3.591ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.684ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.578ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.485ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.065ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.873ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.884ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_deleted_at' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.047ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.987ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.811ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'users' AND index_name = 'idx_users_email_verify_token' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.786ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.503ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.171ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'roles' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.090ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.773ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.824ms] [rows:-] SELECT * FROM `roles` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.419ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'roles' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[2.197ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.679ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[3.085ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'roles' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.986ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.008ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.878ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'roles' AND constraint_name = 'uni_roles_name' + +2026/02/12 03:51:08  +[1.914ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.973ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.049ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.564ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.911ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.023ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'roles' AND index_name = 'idx_roles_name' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.835ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.410ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.053ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'user_roles' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.001ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.345ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.326ms] [rows:-] SELECT * FROM `user_roles` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.979ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'user_roles' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.880ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.999ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.510ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_user' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.980ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.733ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.926ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'user_roles' AND constraint_name = 'fk_user_roles_role' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.778ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.841ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.830ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.276ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.091ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.608ms] [rows:-] SELECT * FROM `social_accounts` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.900ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.028ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.617ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.145ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'social_accounts' AND constraint_name = 'fk_users_social_accounts' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.542ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.228ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.773ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'social_accounts' AND index_name = 'idx_social_accounts_user_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[7.102ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.335ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.011ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.768ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.691ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[5.145ms] [rows:-] SELECT * FROM `permissions` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.785ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'permissions' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[2.314ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.361ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.353ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'permissions' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.511ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.623ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.633ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'permissions' AND constraint_name = 'uni_permissions_name' + +2026/02/12 03:51:08  +[1.593ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.546ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.317ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.667ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.714ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.118ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'permissions' AND index_name = 'idx_permissions_name' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.876ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.934ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.747ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.478ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.206ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[11.258ms] [rows:-] SELECT * FROM `role_permissions` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.450ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'role_permissions' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.288ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.091ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.344ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_role' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.643ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.054ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.304ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'role_permissions' AND constraint_name = 'fk_role_permissions_permission' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.304ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.957ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.654ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'categories' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.517ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.037ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.764ms] [rows:-] SELECT * FROM `categories` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.150ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'categories' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.146ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.096ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.292ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'categories' AND constraint_name = 'fk_categories_children' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.471ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.893ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.868ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'categories' AND index_name = 'idx_categories_is_active' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.178ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.845ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.185ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'categories' AND index_name = 'idx_categories_order' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.879ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.283ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.893ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'categories' AND index_name = 'idx_categories_slug' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.051ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.249ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.620ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'categories' AND index_name = 'idx_categories_parent_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.643ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.033ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.149ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'tags' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.943ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.788ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.912ms] [rows:-] SELECT * FROM `tags` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.614ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'tags' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.915ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.573ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.056ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'tags' AND index_name = 'idx_tags_slug' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.239ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.433ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.095ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'tags' AND index_name = 'idx_tags_is_active' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.655ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.388ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.485ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'posts' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.904ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.964ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.733ms] [rows:-] SELECT * FROM `posts` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.803ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'posts' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.824ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.031ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.795ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'fk_posts_children' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.777ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.143ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.683ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'posts' AND constraint_name = 'fk_posts_user' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.614ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.892ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.016ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_user_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.391ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.328ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.493ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_slug' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.364ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.545ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.903ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_is_active' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.873ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.042ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.867ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_is_front' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.019ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.698ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.220ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'posts' AND index_name = 'idx_posts_parent_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.075ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.289ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.680ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_tags' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.976ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.337ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[6.476ms] [rows:-] SELECT * FROM `post_tags` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.601ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_tags' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.209ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.819ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.590ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_tags' AND constraint_name = 'fk_post_tags_post' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.468ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.474ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.034ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_tags' AND constraint_name = 'fk_post_tags_tag' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.875ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.595ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.352ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'post_categories' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.387ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.820ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.446ms] [rows:-] SELECT * FROM `post_categories` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.217ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'post_categories' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.842ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.866ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.139ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_categories' AND constraint_name = 'fk_post_categories_post' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.750ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.680ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.072ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'post_categories' AND constraint_name = 'fk_post_categories_category' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.891ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.346ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.437ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'category_views' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.161ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.524ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.293ms] [rows:-] SELECT * FROM `category_views` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.844ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'category_views' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.384ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.024ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.644ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'category_views' AND constraint_name = 'fk_category_views_category' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.936ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.019ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.965ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'category_views' AND index_name = 'idx_category_views_category_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.726ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.521ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.548ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'category_views' AND index_name = 'idx_category_views_ip_address' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.786ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.972ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.431ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'comments' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.346ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.934ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[5.828ms] [rows:-] SELECT * FROM `comments` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.447ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'comments' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.565ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.484ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.562ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'comments' AND constraint_name = 'fk_comments_product' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.936ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.401ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.448ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'comments' AND constraint_name = 'fk_comments_children' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.016ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.089ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.290ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'comments' AND index_name = 'idx_comments_user_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.302ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.690ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.238ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'comments' AND index_name = 'idx_comments_product_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.944ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.420ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.123ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'comments' AND index_name = 'idx_comments_is_active' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.019ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.198ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.691ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'comments' AND index_name = 'idx_comments_slug' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.809ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.096ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.858ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'comments' AND index_name = 'idx_comments_parent_id' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.808ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.856ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.908ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.222ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.466ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[4.054ms] [rows:-] SELECT * FROM `cors_whitelists` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.813ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[2.038ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.700ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[1.886ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_whitelists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.920ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.007ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.836ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_whitelists' AND constraint_name = 'uni_cors_whitelists_origin' + +2026/02/12 03:51:08  +[1.848ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[1.929ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[1.814ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.916ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.959ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.611ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_whitelists' AND index_name = 'idx_cors_whitelists_origin' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.977ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.036ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.069ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[1.631ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.161ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.918ms] [rows:-] SELECT * FROM `cors_blacklists` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[5.843ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[1.848ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[3.028ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.026ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'cors_blacklists' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.768ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[3.067ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[2.389ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'cors_blacklists' AND constraint_name = 'uni_cors_blacklists_origin' + +2026/02/12 03:51:08  +[2.437ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.185ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[1.880ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.914ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.735ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.911ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'cors_blacklists' AND index_name = 'idx_cors_blacklists_origin' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.595ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.721ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.945ms] [rows:-] SELECT count(*) FROM information_schema.tables WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND table_type = 'BASE TABLE' + +2026/02/12 03:51:08  +[2.016ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[2.911ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.759ms] [rows:-] SELECT * FROM `rate_limit_settings` LIMIT 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[3.651ms] [rows:-] SELECT column_name, column_default, is_nullable = 'YES', data_type, character_maximum_length, column_type, column_key, extra, column_comment, numeric_precision, numeric_scale , datetime_precision FROM information_schema.columns WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' ORDER BY ORDINAL_POSITION + +2026/02/12 03:51:08  +[3.120ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[1.895ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[1.829ms] [rows:2] +SELECT + TABLE_NAME, + COLUMN_NAME, + INDEX_NAME, + NON_UNIQUE +FROM + information_schema.STATISTICS +WHERE + TABLE_SCHEMA = 'beyhango' + AND TABLE_NAME = 'rate_limit_settings' +ORDER BY + INDEX_NAME, + SEQ_IN_INDEX + +2026/02/12 03:51:08  +[1.739ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[2.604ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[3.042ms] [rows:-] SELECT count(*) FROM INFORMATION_SCHEMA.table_constraints WHERE constraint_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND constraint_name = 'uni_rate_limit_settings_name' + +2026/02/12 03:51:08  +[2.368ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08  +[3.696ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08  +[1.821ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.776ms] [rows:-] SELECT DATABASE() + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.961ms] [rows:1] SELECT SCHEMA_NAME from Information_schema.SCHEMATA where SCHEMA_NAME LIKE 'beyhango%' ORDER BY SCHEMA_NAME='beyhango' DESC,SCHEMA_NAME limit 1 + +2026/02/12 03:51:08 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/database/migrate.go:12 +[1.796ms] [rows:-] SELECT count(*) FROM information_schema.statistics WHERE table_schema = 'beyhango' AND table_name = 'rate_limit_settings' AND index_name = 'idx_rate_limit_settings_name' +2026/02/12 03:51:08 Migration complete +2026/02/12 03:51:08 Connected to Redis successfully +2026/02/12 03:51:08 🧹 Clearing Redis Cache... + + ╔════════════════════════════════════════════════════════╗ + ║ ║ + ║ ██████╗ ███████╗██╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ║ + ║ ██╔══██╗██╔════╝╚██╗ ██╔╝██║ ██║██╔══██╗████╗ ██║ ║ + ║ ██████╔╝█████╗ ╚████╔╝ ███████║███████║██╔██╗ ██║ ║ + ║ ██╔══██╗██╔══╝ ╚██╔╝ ██╔══██║██╔══██║██║╚██╗██║ ║ + ║ ██████╔╝███████╗ ██║ ██║ ██║██║ ██║██║ ╚████║ ║ + ║ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ║ + ║ ║ + ║ ██████╗ █████╗ ██████╗██╗ ██╗ ║ + ║ ██╔══██╗██╔══██╗██╔════╝██║ ██╔╝ ║ + ║ ██████╔╝███████║██║ █████╔╝ ║ + ║ ██╔══██╗██╔══██║██║ ██╔═██╗ ║ + ║ ██████╔╝██║ ██║╚██████╗██║ ██╗ ║ + ║ ╚═════╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝ ║ + ║ BACKEND ║ + ║ ║ + ╚════════════════════════════════════════════════════════╝ + + Go Backend | v2.0.0 | Running + +[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached. + +[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production. + - using env: export GIN_MODE=release + - using code: gin.SetMode(gin.ReleaseMode) + +[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (3 handlers) +[GIN-debug] GET /admin/login --> gobeyhan/internal/handler/admin.(*Handler).LoginPage-fm (3 handlers) +[GIN-debug] POST /admin/login --> gobeyhan/internal/handler/admin.(*Handler).LoginPost-fm (3 handlers) +[GIN-debug] GET /admin --> gobeyhan/app/routes.SetupRoutes.func1 (3 handlers) +[GIN-debug] GET /admin/dashboard --> gobeyhan/internal/handler/admin.(*Handler).Dashboard-fm (3 handlers) +[GIN-debug] GET /admin/users --> gobeyhan/internal/handler/admin.(*UserHandler).List-fm (3 handlers) +[GIN-debug] GET /admin/users/new --> gobeyhan/internal/handler/admin.(*UserHandler).New-fm (3 handlers) +[GIN-debug] POST /admin/users --> gobeyhan/internal/handler/admin.(*UserHandler).Create-fm (3 handlers) +[GIN-debug] GET /admin/users/:id/edit --> gobeyhan/internal/handler/admin.(*UserHandler).Edit-fm (3 handlers) +[GIN-debug] POST /admin/users/:id --> gobeyhan/internal/handler/admin.(*UserHandler).Update-fm (3 handlers) +[GIN-debug] POST /admin/users/:id/delete --> gobeyhan/internal/handler/admin.(*UserHandler).Delete-fm (3 handlers) +2026/02/12 03:51:08 DEBUG: Registering settings routes... +[GIN-debug] GET /admin/test-settings --> gobeyhan/app/routes.SetupRoutes.func2 (3 handlers) +[GIN-debug] GET /admin/settings/whitelist --> gobeyhan/internal/handler/admin.(*SettingsHandler).ListWhitelist-fm (3 handlers) +[GIN-debug] GET /admin/settings/whitelist/new --> gobeyhan/internal/handler/admin.(*SettingsHandler).NewWhitelist-fm (3 handlers) +[GIN-debug] POST /admin/settings/whitelist --> gobeyhan/internal/handler/admin.(*SettingsHandler).CreateWhitelist-fm (3 handlers) +[GIN-debug] GET /admin/settings/whitelist/:id/edit --> gobeyhan/internal/handler/admin.(*SettingsHandler).EditWhitelist-fm (3 handlers) +[GIN-debug] POST /admin/settings/whitelist/:id/delete --> gobeyhan/internal/handler/admin.(*SettingsHandler).DeleteWhitelist-fm (3 handlers) +[GIN-debug] GET /admin/settings/blacklist --> gobeyhan/internal/handler/admin.(*SettingsHandler).ListBlacklist-fm (3 handlers) +[GIN-debug] GET /admin/settings/blacklist/new --> gobeyhan/internal/handler/admin.(*SettingsHandler).NewBlacklist-fm (3 handlers) +[GIN-debug] POST /admin/settings/blacklist --> gobeyhan/internal/handler/admin.(*SettingsHandler).CreateBlacklist-fm (3 handlers) +[GIN-debug] POST /admin/settings/blacklist/:id/delete --> gobeyhan/internal/handler/admin.(*SettingsHandler).DeleteBlacklist-fm (3 handlers) +[GIN-debug] GET /admin/settings/rate-limits --> gobeyhan/internal/handler/admin.(*SettingsHandler).ListRateLimits-fm (3 handlers) +[GIN-debug] GET /admin/settings/rate-limits/:id/edit --> gobeyhan/internal/handler/admin.(*SettingsHandler).EditRateLimit-fm (3 handlers) +[GIN-debug] POST /admin/settings/rate-limits/:id --> gobeyhan/internal/handler/admin.(*SettingsHandler).UpdateRateLimit-fm (3 handlers) +[GIN-debug] POST /api/v1/auth/register --> gobeyhan/app/account/handlers.(*AuthHandler).Register-fm (4 handlers) +[GIN-debug] POST /api/v1/auth/login --> gobeyhan/app/account/handlers.(*AuthHandler).Login-fm (4 handlers) +[GIN-debug] POST /api/v1/auth/refresh --> gobeyhan/app/account/handlers.(*AuthHandler).RefreshToken-fm (4 handlers) +[GIN-debug] POST /api/v1/auth/logout --> gobeyhan/app/account/handlers.(*AuthHandler).Logout-fm (4 handlers) +[GIN-debug] GET /api/v1/auth/google --> gobeyhan/app/account/handlers.(*OAuthHandler).GoogleLogin-fm (4 handlers) +[GIN-debug] GET /api/v1/auth/google/callback --> gobeyhan/app/account/handlers.(*OAuthHandler).GoogleCallback-fm (4 handlers) +[GIN-debug] GET /api/v1/auth/github --> gobeyhan/app/account/handlers.(*OAuthHandler).GithubLogin-fm (4 handlers) +[GIN-debug] GET /api/v1/auth/github/callback --> gobeyhan/app/account/handlers.(*OAuthHandler).GithubCallback-fm (4 handlers) +[GIN-debug] GET /api/v1/auth/me --> gobeyhan/app/account/handlers.(*AuthHandler).GetCurrentUser-fm (5 handlers) +[GIN-debug] GET /api/v1/categories --> gobeyhan/app/blog/handlers.(*CategoryHandler).GetAllCategories-fm (4 handlers) +[GIN-debug] GET /api/v1/categories/:slug --> gobeyhan/app/blog/handlers.(*CategoryHandler).GetCategoryBySlug-fm (4 handlers) +[GIN-debug] POST /api/v1/categories/:id/view --> gobeyhan/app/blog/handlers.(*CategoryViewHandler).TrackCategoryView-fm (4 handlers) +[GIN-debug] GET /api/v1/tags --> gobeyhan/app/blog/handlers.(*TagHandler).GetAllTags-fm (4 handlers) +[GIN-debug] GET /api/v1/tags/:slug --> gobeyhan/app/blog/handlers.(*TagHandler).GetTagBySlug-fm (4 handlers) +[GIN-debug] GET /api/v1/posts --> gobeyhan/app/blog/handlers.(*PostHandler).GetAllPosts-fm (4 handlers) +[GIN-debug] GET /api/v1/posts/:slug --> gobeyhan/app/blog/handlers.(*PostHandler).GetPostBySlug-fm (4 handlers) +[GIN-debug] GET /api/v1/comments/post/:postId --> gobeyhan/app/blog/handlers.(*CommentHandler).GetPostComments-fm (4 handlers) +[GIN-debug] GET /api/v1/admin/categories --> gobeyhan/app/blog/handlers.(*CategoryHandler).AdminGetAllCategories-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/categories/:id --> gobeyhan/app/blog/handlers.(*CategoryHandler).GetCategoryByID-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/categories --> gobeyhan/app/blog/handlers.(*CategoryHandler).CreateCategory-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/categories/:id --> gobeyhan/app/blog/handlers.(*CategoryHandler).UpdateCategory-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/categories/:id --> gobeyhan/app/blog/handlers.(*CategoryHandler).DeleteCategory-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/categories/:id/views --> gobeyhan/app/blog/handlers.(*CategoryViewHandler).GetCategoryViewStats-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/tags --> gobeyhan/app/blog/handlers.(*TagHandler).AdminGetAllTags-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/tags/:id --> gobeyhan/app/blog/handlers.(*TagHandler).GetTagByID-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/tags --> gobeyhan/app/blog/handlers.(*TagHandler).CreateTag-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/tags/:id --> gobeyhan/app/blog/handlers.(*TagHandler).UpdateTag-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/tags/:id --> gobeyhan/app/blog/handlers.(*TagHandler).DeleteTag-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/posts --> gobeyhan/app/blog/handlers.(*PostHandler).AdminGetAllPosts-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/posts/:id --> gobeyhan/app/blog/handlers.(*PostHandler).GetPostByID-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/posts --> gobeyhan/app/blog/handlers.(*PostHandler).CreatePost-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/posts/:id --> gobeyhan/app/blog/handlers.(*PostHandler).UpdatePost-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/posts/:id --> gobeyhan/app/blog/handlers.(*PostHandler).DeletePost-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/comments --> gobeyhan/app/blog/handlers.(*CommentHandler).AdminGetAllComments-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/comments/:id --> gobeyhan/app/blog/handlers.(*CommentHandler).AdminGetCommentByID-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/comments/:id --> gobeyhan/app/blog/handlers.(*CommentHandler).AdminUpdateComment-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/comments/:id --> gobeyhan/app/blog/handlers.(*CommentHandler).AdminDeleteComment-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/category-views --> gobeyhan/app/blog/handlers.(*CategoryViewHandler).AdminGetAllCategoryViews-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/users --> gobeyhan/app/account/handlers.(*UserHandler).AdminGetAllUsers-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/users/:id --> gobeyhan/app/account/handlers.(*UserHandler).AdminGetUserByID-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/users --> gobeyhan/app/account/handlers.(*UserHandler).AdminCreateUser-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/users/:id --> gobeyhan/app/account/handlers.(*UserHandler).AdminUpdateUser-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/users/:id --> gobeyhan/app/account/handlers.(*UserHandler).AdminDeleteUser-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/users/:id/restore --> gobeyhan/app/account/handlers.(*UserHandler).AdminRestoreUser-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/users/:id/roles --> gobeyhan/app/account/handlers.(*UserHandler).AdminAssignRole-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/users/:id/roles/:role_id --> gobeyhan/app/account/handlers.(*UserHandler).AdminRemoveRole-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/roles --> gobeyhan/app/account/handlers.(*RoleHandler).AdminGetAllRoles-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/roles/:id --> gobeyhan/app/account/handlers.(*RoleHandler).AdminGetRoleByID-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/roles --> gobeyhan/app/account/handlers.(*RoleHandler).AdminCreateRole-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/roles/:id --> gobeyhan/app/account/handlers.(*RoleHandler).AdminUpdateRole-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/roles/:id --> gobeyhan/app/account/handlers.(*RoleHandler).AdminDeleteRole-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/permissions --> gobeyhan/app/account/handlers.(*PermissionHandler).AdminGetAllPermissions-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/permissions --> gobeyhan/app/account/handlers.(*PermissionHandler).AdminCreatePermission-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/cors/whitelist --> gobeyhan/app/settings/handlers.(*SettingsHandler).GetAllWhitelist-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/cors/whitelist --> gobeyhan/app/settings/handlers.(*SettingsHandler).CreateWhitelist-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/cors/whitelist/:id --> gobeyhan/app/settings/handlers.(*SettingsHandler).UpdateWhitelist-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/cors/whitelist/:id --> gobeyhan/app/settings/handlers.(*SettingsHandler).DeleteWhitelist-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/cors/blacklist --> gobeyhan/app/settings/handlers.(*SettingsHandler).GetAllBlacklist-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/cors/blacklist --> gobeyhan/app/settings/handlers.(*SettingsHandler).CreateBlacklist-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/cors/blacklist/:id --> gobeyhan/app/settings/handlers.(*SettingsHandler).UpdateBlacklist-fm (6 handlers) +[GIN-debug] DELETE /api/v1/admin/cors/blacklist/:id --> gobeyhan/app/settings/handlers.(*SettingsHandler).DeleteBlacklist-fm (6 handlers) +[GIN-debug] POST /api/v1/admin/cors/cache/invalidate --> gobeyhan/app/settings/handlers.(*SettingsHandler).InvalidateCorsCache-fm (6 handlers) +[GIN-debug] GET /api/v1/admin/rate-limits --> gobeyhan/app/settings/handlers.(*SettingsHandler).GetAllRateLimits-fm (6 handlers) +[GIN-debug] PUT /api/v1/admin/rate-limits/:id --> gobeyhan/app/settings/handlers.(*SettingsHandler).UpdateRateLimit-fm (6 handlers) +2026/02/12 03:51:08 Trusted proxies configured: [127.0.0.1 10.0.0.0/8] +2026/02/12 03:51:08 🚀 Server starting on port 8080 +2026/02/12 03:51:08 📚 Swagger UI: http://localhost:8080/swagger/index.html +2026/02/12 03:51:08 🌐 API Base: http://localhost:8080/api/v1 +[GIN-debug] Listening and serving HTTP on :8080 +[GIN] 2026/02/12 - 03:51:20 | 200 | 18.343µs | ::1 | GET "/admin/test-settings" + +2026/02/12 03:51:22 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[4.138ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:22 | 200 | 4.422593ms | ::1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:43 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/account/services/user_service.go:29 +[4.083ms] [rows:1] SELECT count(*) FROM `users` WHERE `users`.`deleted_at` IS NULL + +2026/02/12 03:51:43 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/account/services/user_service.go:35 +[3.270ms] [rows:4] SELECT * FROM `user_roles` WHERE `user_roles`.`user_id` IN (15,14,12,2) + +2026/02/12 03:51:43 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/account/services/user_service.go:35 +[107.878ms] [rows:2] SELECT * FROM `roles` WHERE `roles`.`id` IN (1,2) + +2026/02/12 03:51:43 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/account/services/user_service.go:35 +[107.988ms] [rows:0] SELECT * FROM `social_accounts` WHERE `social_accounts`.`user_id` IN (15,14,12,2) + +2026/02/12 03:51:43 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/account/services/user_service.go:35 SLOW SQL >= 200ms +[223.435ms] [rows:4] SELECT * FROM `users` WHERE `users`.`deleted_at` IS NULL ORDER BY created_at DESC LIMIT 100 +[GIN] 2026/02/12 - 03:51:43 | 200 | 227.77566ms | 127.0.0.1 | GET "/admin/users" + +2026/02/12 03:51:46 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.322ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:46 | 200 | 2.509268ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:47 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.296ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:47 | 200 | 2.487643ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:48 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.330ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:48 | 200 | 2.461762ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:49 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[4.538ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:49 | 200 | 4.779768ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:51 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.897ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:51 | 200 | 3.05517ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:51:53 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[3.706ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:53 | 200 | 4.077533ms | 127.0.0.1 | GET "/admin/settings/blacklist" + +2026/02/12 03:51:55 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[5.136ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:51:55 | 200 | 5.332719ms | 127.0.0.1 | GET "/admin/settings/rate-limits" + +2026/02/12 03:51:59 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.110ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:51:59 | 200 | 2.250209ms | 127.0.0.1 | GET "/admin/settings/whitelist" +[GIN] 2026/02/12 - 03:52:04 | 200 | 83.301µs | 127.0.0.1 | GET "/admin/settings/whitelist/new" + +2026/02/12 03:52:12 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:55 record not found +[7.527ms] [rows:0] SELECT * FROM `cors_whitelists` WHERE LOWER(origin) = LOWER('10.0.1.12') ORDER BY `cors_whitelists`.`id` LIMIT 1 + +2026/02/12 03:52:12 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:78 +[47.652ms] [rows:1] INSERT INTO `cors_whitelists` (`origin`,`description`,`is_active`,`created_by`,`created_at`,`updated_at`) VALUES ('10.0.1.12','10.0.1.12',true,'','2026-02-12 03:52:12.495','2026-02-12 03:52:12.495') RETURNING `id` +2026/02/12 03:52:12 cors_cache_invalidated +[GIN] 2026/02/12 - 03:52:12 | 303 | 58.965358ms | 127.0.0.1 | POST "/admin/settings/whitelist" + +2026/02/12 03:52:12 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[1.887ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:52:12 | 200 | 2.024854ms | 127.0.0.1 | GET "/admin/settings/whitelist" +[GIN] 2026/02/12 - 03:52:16 | 200 | 71.343µs | 127.0.0.1 | GET "/admin/settings/whitelist/new" + +2026/02/12 03:52:21 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[2.183ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:52:21 | 200 | 2.355452ms | 127.0.0.1 | GET "/admin/settings/blacklist" +[GIN] 2026/02/12 - 03:52:23 | 200 | 49.296µs | 127.0.0.1 | GET "/admin/settings/blacklist/new" + +2026/02/12 03:52:24 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[1.927ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:52:24 | 200 | 2.076877ms | 127.0.0.1 | GET "/admin/settings/rate-limits" +[GIN] 2026/02/12 - 03:52:27 | 200 | 7.073µs | 127.0.0.1 | GET "/admin/settings/rate-limits/3/edit" + +2026/02/12 03:52:33 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[1.663ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:52:33 | 200 | 1.828226ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:52:35 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[2.298ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:52:35 | 200 | 2.700668ms | 127.0.0.1 | GET "/admin/settings/blacklist" + +2026/02/12 03:52:36 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.298ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:52:36 | 200 | 2.459586ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:53:41 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[1.551ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:53:41 | 200 | 1.793096ms | 127.0.0.1 | GET "/admin/settings/rate-limits" +[GIN] 2026/02/12 - 03:53:44 | 200 | 21.502µs | 127.0.0.1 | GET "/admin/settings/rate-limits/3/edit" + +2026/02/12 03:53:47 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.301ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:53:47 | 200 | 2.485484ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:53:51 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:100 +[18.813ms] [rows:1] DELETE FROM `cors_whitelists` WHERE id = '3' +2026/02/12 03:53:51 cors_cache_invalidated +[GIN] 2026/02/12 - 03:53:51 | 303 | 23.38236ms | 127.0.0.1 | POST "/admin/settings/whitelist/3/delete" + +2026/02/12 03:53:51 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[1.913ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:53:51 | 200 | 2.045206ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:54:56 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.150ms] [rows:2] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:54:56 | 200 | 2.298813ms | 127.0.0.1 | GET "/admin/settings/whitelist" +[GIN] 2026/02/12 - 03:55:00 | 200 | 48.512µs | 127.0.0.1 | GET "/admin/settings/whitelist/new" + +2026/02/12 03:55:06 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:55 record not found +[3.369ms] [rows:0] SELECT * FROM `cors_whitelists` WHERE LOWER(origin) = LOWER('10.0.1.12') ORDER BY `cors_whitelists`.`id` LIMIT 1 + +2026/02/12 03:55:06 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:78 +[17.204ms] [rows:1] INSERT INTO `cors_whitelists` (`origin`,`description`,`is_active`,`created_by`,`created_at`,`updated_at`) VALUES ('10.0.1.12','10.0.1.12',true,'','2026-02-12 03:55:06.541','2026-02-12 03:55:06.541') RETURNING `id` +2026/02/12 03:55:06 cors_cache_invalidated +[GIN] 2026/02/12 - 03:55:06 | 303 | 26.231659ms | 127.0.0.1 | POST "/admin/settings/whitelist" + +2026/02/12 03:55:06 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.222ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:55:06 | 200 | 2.4176ms | 127.0.0.1 | GET "/admin/settings/whitelist" + +2026/02/12 03:55:11 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[2.474ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:55:11 | 200 | 2.588477ms | 127.0.0.1 | GET "/admin/settings/blacklist" + +2026/02/12 03:55:13 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[2.435ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:55:13 | 200 | 2.583778ms | 127.0.0.1 | GET "/admin/settings/rate-limits" +[GIN] 2026/02/12 - 03:55:17 | 200 | 7.741µs | 127.0.0.1 | GET "/admin/settings/rate-limits/3/edit" + +2026/02/12 03:56:45 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[3.843ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:56:45 | 200 | 3.991653ms | 127.0.0.1 | GET "/admin/settings/rate-limits" +[GIN] 2026/02/12 - 03:56:48 | 200 | 9.789µs | 127.0.0.1 | GET "/admin/settings/rate-limits/3/edit" + +2026/02/12 03:56:51 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:173 +[2.196ms] [rows:3] SELECT * FROM `rate_limit_settings` ORDER BY name ASC +[GIN] 2026/02/12 - 03:56:51 | 200 | 2.356002ms | 127.0.0.1 | GET "/admin/settings/rate-limits" + +2026/02/12 03:56:52 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[1.999ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:56:52 | 200 | 2.119785ms | 127.0.0.1 | GET "/admin/settings/blacklist" + +2026/02/12 03:56:55 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:114 +[1.844ms] [rows:0] SELECT * FROM `cors_blacklists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:56:55 | 200 | 1.957659ms | 127.0.0.1 | GET "/admin/settings/blacklist" + +2026/02/12 03:56:57 /Users/beyhan/Desktop/Projeler/Go/gobeyhan/app/settings/services/settings_service.go:29 +[2.149ms] [rows:3] SELECT * FROM `cors_whitelists` WHERE is_active = true ORDER BY created_at DESC +[GIN] 2026/02/12 - 03:56:57 | 200 | 2.288286ms | 127.0.0.1 | GET "/admin/settings/whitelist" diff --git a/update_log.txt b/update_log.txt new file mode 100644 index 0000000..938a42f --- /dev/null +++ b/update_log.txt @@ -0,0 +1 @@ +# This is a dummy file to update user. diff --git a/uploads/blog/post_1770861638665070000.webp b/uploads/blog/post_1770861638665070000.webp new file mode 100644 index 0000000..4994895 Binary files /dev/null and b/uploads/blog/post_1770861638665070000.webp differ diff --git a/uploads/posts/blog_1770860097523301000.webp b/uploads/posts/blog_1770860097523301000.webp new file mode 100644 index 0000000..639311c Binary files /dev/null and b/uploads/posts/blog_1770860097523301000.webp differ diff --git a/views/admin/blog/blog.templ b/views/admin/blog/blog.templ new file mode 100644 index 0000000..f0ce765 --- /dev/null +++ b/views/admin/blog/blog.templ @@ -0,0 +1,403 @@ +package blog + +import ( + "gobeyhan/database/models" + "fmt" +) + +templ List(posts []models.Post) { + @Layout("Blog Posts") { +
+
+

Blog Posts

+ Add Post +
+ +
+ + + + + + + + + + + + + for _, post := range posts { + + + + + + + + + } + +
IDTitleCategoriesStatusCreatedActions
{ fmt.Sprintf("%d", post.ID) } +
{ post.Title }
+
+ if len(post.Categories) > 0 { + for i, cat := range post.Categories { + if i > 0 { + , + } + { cat.Title } + } + } else { + - + } + + if post.IsActive { + Active + } else { + Inactive + } + if post.IsFront { + Front + } + + { post.CreatedAt.Format("2006-01-02") } + + Edit +
+ +
+
+
+
+ } +} + +templ Create(categories []models.Category, tags []models.Tag, errors map[string]string) { + @Layout("Create Blog Post") { +
+
+

Create New Post

+
+
+ + + if errors["title"] != "" { +

{ errors["title"] }

+ } +
+ +
+ + +

Leave blank to generate automatically from title.

+
+ +
+ +
+ + if errors["content"] != "" { +

{ errors["content"] }

+ } +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+ if len(categories) > 0 { + for _, cat := range categories { + + } + } else { +

No active categories found. Add one first.

+ } +
+
+ +
+ +
+ if len(tags) > 0 { + for _, tag := range tags { + + } + } else { +

No active tags found. Add one first.

+ } +
+
+ +
+ + + +
+ +
+ Cancel + +
+
+ +
+
+ } +} + +templ Edit(post *models.Post, categories []models.Category, tags []models.Tag, errors map[string]string) { + @Layout("Edit Blog Post") { +
+
+

Edit Post: { post.Title }

+
+
+ + + if errors["title"] != "" { +

{ errors["title"] }

+ } +
+ +
+ + +

Leave blank to keep existing or regenerate from title.

+
+ +
+ +
@templ.Raw(post.Content)
+ + if errors["content"] != "" { +

{ errors["content"] }

+ } +
+ +
+ + +
+ +
+
+ + + if post.Image != "" { +
+

Current Preview:

+ +
+ } +
+
+ + +
+
+ +
+ +
+ for _, cat := range categories { + + } +
+
+ +
+ +
+ for _, tag := range tags { + + } +
+
+ +
+ + + +
+ +
+ Cancel + +
+
+ +
+
+ } +} + +func isCategorySelected(categories []*models.Category, id uint64) bool { + for _, cat := range categories { + if cat.ID == id { + return true + } + } + return false +} + +func isTagSelected(tags []*models.Tag, id uint64) bool { + for _, tag := range tags { + if tag.ID == id { + return true + } + } + return false +} diff --git a/views/admin/blog/blog_templ.go b/views/admin/blog/blog_templ.go new file mode 100644 index 0000000..a68036d --- /dev/null +++ b/views/admin/blog/blog_templ.go @@ -0,0 +1,749 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package blog + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" +) + +func List(posts []models.Post) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Blog Posts

Add Post
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, post := range posts { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
IDTitleCategoriesStatusCreatedActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", post.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 31, Col: 122} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(post.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 33, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(post.Categories) > 0 { + for i, cat := range post.Categories { + if i > 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, ", ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(cat.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 41, Col: 121} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if post.IsActive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "Active ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "Inactive ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + if post.IsFront { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "Front") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(post.CreatedAt.Format("2006-01-02")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 58, Col: 73} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('Delete Post', 'Are you sure you want to delete this post?', 'delete-blog-%d')", post.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Blog Posts").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func Create(categories []models.Category, tags []models.Tag, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 21, "

Create New Post

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["title"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(errors["title"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 86, Col: 82} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

Leave blank to generate automatically from title.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["content"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(errors["content"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 102, Col: 84} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 26, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(categories) > 0 { + for _, cat := range categories { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "

No active categories found. Add one first.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(tags) > 0 { + for _, tag := range tags { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 36, "

No active tags found. Add one first.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Create Blog Post").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func Edit(post *models.Post, categories []models.Category, tags []models.Tag, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var19 := templ.GetChildren(ctx) + if templ_7745c5c3_Var19 == nil { + templ_7745c5c3_Var19 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var20 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 38, "

Edit Post: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var21 string + templ_7745c5c3_Var21, templ_7745c5c3_Err = templ.JoinStringErrs(post.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 230, Col: 93} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var21)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["title"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var24 string + templ_7745c5c3_Var24, templ_7745c5c3_Err = templ.JoinStringErrs(errors["title"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 237, Col: 82} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var24)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "

Leave blank to keep existing or regenerate from title.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.Raw(post.Content).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 46, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["content"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var27 string + templ_7745c5c3_Var27, templ_7745c5c3_Err = templ.JoinStringErrs(errors["content"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/blog.templ`, Line: 253, Col: 84} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var27)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 50, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if post.Image != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "

Current Preview:

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, cat := range categories { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tag := range tags { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 62, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 67, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Edit Blog Post").Render(templ.WithChildren(ctx, templ_7745c5c3_Var20), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func isCategorySelected(categories []*models.Category, id uint64) bool { + for _, cat := range categories { + if cat.ID == id { + return true + } + } + return false +} + +func isTagSelected(tags []*models.Tag, id uint64) bool { + for _, tag := range tags { + if tag.ID == id { + return true + } + } + return false +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/blog/category.templ b/views/admin/blog/category.templ new file mode 100644 index 0000000..3ba9bf8 --- /dev/null +++ b/views/admin/blog/category.templ @@ -0,0 +1,150 @@ +package blog + +import ( + "gobeyhan/database/models" + "fmt" +) + +templ CategoryList(items []models.Category) { + @Layout("Blog Categories") { +
+

Categories

+ Add Category +
+ +
+ + + + + + + + + + + + + for _, cat := range items { + + + + + + + + + } + +
IDTitleSlugOrderActiveActions
{ fmt.Sprintf("%d", cat.ID) }{ cat.Title }{ cat.Slug }{ fmt.Sprintf("%d", cat.Order) } + if cat.IsActive { + Active + } else { + Inactive + } + + Edit +
+ +
+
+
+ } +} + +templ CategoryForm(cat *models.Category, categories []models.Category, errors map[string]string) { + @Layout(ifElse(cat.ID == 0, "Create Category", "Edit Category")) { +
+

+ if cat.ID == 0 { + Create New Category + } else { + Edit Category: { cat.Title } + } +

+
+
+ + + if errors["title"] != "" { +

{ errors["title"] }

+ } +
+ +
+ + +
+ +
+ + +
+ +
+
+ + +
+
+ +
+
+ +
+ + +
+ +
+ + +
+ +
+ Cancel + +
+
+ +
+ } +} + +func ifElse(cond bool, a, b string) string { + if cond { + return a + } + return b +} diff --git a/views/admin/blog/category_templ.go b/views/admin/blog/category_templ.go new file mode 100644 index 0000000..0dd6106 --- /dev/null +++ b/views/admin/blog/category_templ.go @@ -0,0 +1,448 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package blog + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" +) + +func CategoryList(items []models.Category) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Categories

Add Category
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, cat := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
IDTitleSlugOrderActiveActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", cat.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 30, Col: 117} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(cat.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 31, Col: 113} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(cat.Slug) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 32, Col: 100} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", cat.Order)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 33, Col: 120} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if cat.IsActive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Active") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Inactive") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('Delete Category', 'Delete this category and all associations?', 'delete-cat-%d')", cat.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Blog Categories").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func CategoryForm(cat *models.Category, categories []models.Category, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if cat.ID == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "Create New Category") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "Edit Category: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(cat.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 62, Col: 46} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["title"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 22, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(errors["title"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/category.templ`, Line: 71, Col: 78} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout(ifElse(cat.ID == 0, "Create Category", "Edit Category")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func ifElse(cond bool, a, b string) string { + if cond { + return a + } + return b +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/blog/comment.templ b/views/admin/blog/comment.templ new file mode 100644 index 0000000..0ed6006 --- /dev/null +++ b/views/admin/blog/comment.templ @@ -0,0 +1,96 @@ +package blog + +import ( + "gobeyhan/database/models" + "fmt" +) + +templ CommentList(items []models.Comment) { + @Layout("Blog Comments") { +
+

Comments

+
+ +
+ + + + + + + + + + + + + for _, comment := range items { + + + + + + + + + } + +
IDPostAuthorCommentStatusActions
{ fmt.Sprintf("%d", comment.ID) } +
{ comment.Product.Title }
+
+ User ID: { fmt.Sprintf("%d", comment.UserID) } + +
{ comment.Body }
+
+ if comment.IsActive { + Approved + } else { + Pending + } + + Edit +
+ +
+
+
+ } +} + +templ CommentForm(comment *models.Comment, errors map[string]string) { + @Layout("Edit Comment") { +
+

Edit Comment

+
+
+
+ +

{ fmt.Sprintf("%d", comment.UserID) }

+
+
+ +
+ + +
+ +
+ +
+ +
+ Cancel + +
+
+
+ } +} diff --git a/views/admin/blog/comment_templ.go b/views/admin/blog/comment_templ.go new file mode 100644 index 0000000..f2b563b --- /dev/null +++ b/views/admin/blog/comment_templ.go @@ -0,0 +1,292 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package blog + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" +) + +func CommentList(items []models.Comment) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Comments

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, comment := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
IDPostAuthorCommentStatusActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", comment.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/comment.templ`, Line: 29, Col: 121} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(comment.Product.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/comment.templ`, Line: 31, Col: 86} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
User ID: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", comment.UserID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/comment.templ`, Line: 34, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(comment.Body) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/comment.templ`, Line: 37, Col: 81} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if comment.IsActive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Approved") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Pending") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('Delete Comment', 'Are you sure you want to delete this comment?', 'delete-comment-%d')", comment.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Blog Comments").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func CommentForm(comment *models.Comment, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Edit Comment

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", comment.UserID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/comment.templ`, Line: 68, Col: 127} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Edit Comment").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/blog/layout.templ b/views/admin/blog/layout.templ new file mode 100644 index 0000000..5680540 --- /dev/null +++ b/views/admin/blog/layout.templ @@ -0,0 +1,70 @@ +package blog + +import ( + "gobeyhan/views/admin" +) + +templ Layout(title string) { + @admin.Layout(title) { +
+
+ +
+ { children... } +
+ + } +} diff --git a/views/admin/blog/layout_templ.go b/views/admin/blog/layout_templ.go new file mode 100644 index 0000000..5204617 --- /dev/null +++ b/views/admin/blog/layout_templ.go @@ -0,0 +1,70 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package blog + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "gobeyhan/views/admin" +) + +func Layout(title string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = admin.Layout(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/blog/tag.templ b/views/admin/blog/tag.templ new file mode 100644 index 0000000..1f7491e --- /dev/null +++ b/views/admin/blog/tag.templ @@ -0,0 +1,87 @@ +package blog + +import ( + "gobeyhan/database/models" + "fmt" +) + +templ TagList(items []models.Tag) { + @Layout("Blog Tags") { +
+

Tags

+ Add Tag +
+ +
+ + + + + + + + + + + for _, tag := range items { + + + + + + + } + +
IDTagSlugActions
{ fmt.Sprintf("%d", tag.ID) }{ tag.Tag }{ tag.Slug } + Edit +
+ +
+
+
+ } +} + +templ TagForm(tag *models.Tag, errors map[string]string) { + @Layout(ifElse(tag.ID == 0, "Create Tag", "Edit Tag")) { +
+

+ if tag.ID == 0 { + Create New Tag + } else { + Edit Tag: { tag.Tag } + } +

+
+
+ + + if errors["tag"] != "" { +

{ errors["tag"] }

+ } +
+ +
+ + +
+ +
+ Cancel + +
+
+ +
+ } +} diff --git a/views/admin/blog/tag_templ.go b/views/admin/blog/tag_templ.go new file mode 100644 index 0000000..2e1c298 --- /dev/null +++ b/views/admin/blog/tag_templ.go @@ -0,0 +1,316 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package blog + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" +) + +func TagList(items []models.Tag) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, tag := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
IDTagSlugActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", tag.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/tag.templ`, Line: 28, Col: 117} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Tag) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/tag.templ`, Line: 29, Col: 111} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Slug) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/tag.templ`, Line: 30, Col: 100} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('Delete Tag', 'Delete this tag and remove it from all posts?', 'delete-tag-%d')", tag.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Blog Tags").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func TagForm(tag *models.Tag, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var10 := templ.GetChildren(ctx) + if templ_7745c5c3_Var10 == nil { + templ_7745c5c3_Var10 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var11 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if tag.ID == 0 { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "Create New Tag") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "Edit Tag: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(tag.Tag) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/tag.templ`, Line: 52, Col: 39} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["tag"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(errors["tag"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/blog/tag.templ`, Line: 61, Col: 76} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout(ifElse(tag.ID == 0, "Create Tag", "Edit Tag")).Render(templ.WithChildren(ctx, templ_7745c5c3_Var11), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/dashboard.templ b/views/admin/dashboard.templ new file mode 100644 index 0000000..e2f3a7d --- /dev/null +++ b/views/admin/dashboard.templ @@ -0,0 +1,13 @@ +package admin + +templ Dashboard() { + @Layout("Dashboard") { +
+
+
+

Dashboard Content

+
+
+
+ } +} diff --git a/views/admin/dashboard_templ.go b/views/admin/dashboard_templ.go new file mode 100644 index 0000000..9a75bb8 --- /dev/null +++ b/views/admin/dashboard_templ.go @@ -0,0 +1,58 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package admin + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Dashboard() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Dashboard Content

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Dashboard").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/layout.templ b/views/admin/layout.templ new file mode 100644 index 0000000..b092ee2 --- /dev/null +++ b/views/admin/layout.templ @@ -0,0 +1,54 @@ +package admin + +templ Layout(title string) { + + + + + + { title } - Admin Panel + + + + + + + + + +
+ + +
+
+ { children... } +
+
+
+ + +} diff --git a/views/admin/layout_templ.go b/views/admin/layout_templ.go new file mode 100644 index 0000000..820b363 --- /dev/null +++ b/views/admin/layout_templ.go @@ -0,0 +1,61 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package admin + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Layout(title string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/layout.templ`, Line: 9, Col: 16} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, " - Admin Panel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/login.templ b/views/admin/login.templ new file mode 100644 index 0000000..c0dfe2d --- /dev/null +++ b/views/admin/login.templ @@ -0,0 +1,37 @@ +package admin + +import "gobeyhan/views/components" + +templ Login() { + @Layout("Login") { +
+
+

Sign in to your account

+
+ +
+
+ @components.Input(components.InputProps{ + Label: "Email address", + Name: "email", + Type: "email", + Placeholder: "admin@example.com", + }) + + @components.Input(components.InputProps{ + Label: "Password", + Name: "password", + Type: "password", + }) + +
+ @components.Button(components.ButtonProps{ + Type: "submit", + Label: "Sign in", + }) +
+
+
+
+ } +} diff --git a/views/admin/login_templ.go b/views/admin/login_templ.go new file mode 100644 index 0000000..fe10763 --- /dev/null +++ b/views/admin/login_templ.go @@ -0,0 +1,92 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package admin + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "gobeyhan/views/components" + +func Login() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Sign in to your account

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Input(components.InputProps{ + Label: "Email address", + Name: "email", + Type: "email", + Placeholder: "admin@example.com", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Input(components.InputProps{ + Label: "Password", + Name: "password", + Type: "password", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Button(components.ButtonProps{ + Type: "submit", + Label: "Sign in", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Login").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/settings/settings.templ b/views/admin/settings/settings.templ new file mode 100644 index 0000000..471553a --- /dev/null +++ b/views/admin/settings/settings.templ @@ -0,0 +1,292 @@ +package settings + +import ( + "gobeyhan/views/admin" + "gobeyhan/database/models" + "fmt" +) + +templ Layout(title string) { + @admin.Layout(title) { +
+ + { children... } +
+ + } +} + +templ WhitelistList(items []models.CorsWhitelist) { + @Layout("CORS Whitelist") { +
+

CORS Whitelist

+ Add Origin +
+ +
+ + + + + + + + + + + for _, item := range items { + + + + + + + } + +
OriginDescriptionActiveActions
{ item.Origin }{ item.Description } + if item.IsActive { + Active + } else { + Inactive + } + + Edit +
+ +
+
+
+ } +} + +templ WhitelistCreate(errors map[string]string) { + @Layout("Add Whitelist Origin") { +
+

Add Allowed Origin

+
+
+ + + if errors["origin"] != "" { +

{ errors["origin"] }

+ } +
+
+ + +
+
+ Cancel + +
+
+
+ } +} + +templ WhitelistEdit(item *models.CorsWhitelist, errors map[string]string) { + @Layout("Edit Whitelist Origin") { +
+

Edit Allowed Origin

+
+
+ + + if errors["origin"] != "" { +

{ errors["origin"] }

+ } +
+
+ + +
+
+ Cancel + +
+
+
+ } +} + +templ BlacklistList(items []models.CorsBlacklist) { + @Layout("CORS Blacklist") { +
+

CORS Blacklist

+ Block Origin +
+ +
+ + + + + + + + + + + for _, item := range items { + + + + + + + } + +
OriginReasonActiveActions
{ item.Origin }{ item.Reason } + if item.IsActive { + Active + } else { + Inactive + } + + Edit +
+ +
+
+
+ } +} + + +templ BlacklistCreate(errors map[string]string) { + @Layout("Block Origin") { +
+

Block Origin

+
+
+ + + if errors["origin"] != "" { +

{ errors["origin"] }

+ } +
+
+ + +
+
+ Cancel + +
+
+
+ } +} + +templ BlacklistEdit(item *models.CorsBlacklist, errors map[string]string) { + @Layout("Edit Blocked Origin") { +
+

Edit Blocked Origin

+
+
+ + + if errors["origin"] != "" { +

{ errors["origin"] }

+ } +
+
+ + +
+
+ Cancel + +
+
+
+ } +} + +templ RateLimitList(items []models.RateLimitSetting) { + @Layout("Rate Limits") { +
+

Rate Limits

+
+ +
+ + + + + + + + + + + + for _, item := range items { + + + + + + + + } + +
NameDescriptionMax RequestsWindow (s)Actions
{ item.Name }{ item.Description }{ fmt.Sprintf("%d", item.MaxRequests) }{ fmt.Sprintf("%v", item.WindowSeconds) } + Edit + if item.Name != "api" { +
+ +
+ } +
+
+ } +} + +templ RateLimitEdit(item *models.RateLimitSetting, errors map[string]string) { + @Layout("Edit Rate Limit") { +
+

Edit Rate Limit: { item.Name }

+
+
+ + +
+
+ + +
+
+ + +
+
+ Cancel + +
+
+
+ } +} diff --git a/views/admin/settings/settings_templ.go b/views/admin/settings/settings_templ.go new file mode 100644 index 0000000..7b8362f --- /dev/null +++ b/views/admin/settings/settings_templ.go @@ -0,0 +1,1025 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package settings + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" + "gobeyhan/views/admin" +) + +func Layout(title string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = admin.Layout(title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func WhitelistList(items []models.CorsWhitelist) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var3 := templ.GetChildren(ctx) + if templ_7745c5c3_Var3 == nil { + templ_7745c5c3_Var3 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var4 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

CORS Whitelist

Add Origin
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
OriginDescriptionActiveActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(item.Origin) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 61, Col: 115} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(item.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 62, Col: 108} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if item.IsActive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Active") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Inactive") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('delete-whitelist-%d')", item.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("CORS Whitelist").Render(templ.WithChildren(ctx, templ_7745c5c3_Var4), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func WhitelistCreate(errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var11 := templ.GetChildren(ctx) + if templ_7745c5c3_Var11 == nil { + templ_7745c5c3_Var11 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var12 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 16, "

Add Allowed Origin

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["origin"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 17, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(errors["origin"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 93, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 18, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 19, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Add Whitelist Origin").Render(templ.WithChildren(ctx, templ_7745c5c3_Var12), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func WhitelistEdit(item *models.CorsWhitelist, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var14 := templ.GetChildren(ctx) + if templ_7745c5c3_Var14 == nil { + templ_7745c5c3_Var14 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var15 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 20, "

Edit Allowed Origin

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["origin"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 23, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var18 string + templ_7745c5c3_Var18, templ_7745c5c3_Err = templ.JoinStringErrs(errors["origin"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 118, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var18)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 24, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 25, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Edit Whitelist Origin").Render(templ.WithChildren(ctx, templ_7745c5c3_Var15), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func BlacklistList(items []models.CorsBlacklist) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var20 := templ.GetChildren(ctx) + if templ_7745c5c3_Var20 == nil { + templ_7745c5c3_Var20 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var21 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 27, "

CORS Blacklist

Block Origin
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 28, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 39, "
OriginReasonActiveActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var22 string + templ_7745c5c3_Var22, templ_7745c5c3_Err = templ.JoinStringErrs(item.Origin) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 154, Col: 115} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var22)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 29, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var23 string + templ_7745c5c3_Var23, templ_7745c5c3_Err = templ.JoinStringErrs(item.Reason) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 155, Col: 103} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var23)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 30, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if item.IsActive { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 31, "Active") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 32, "Inactive") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 33, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('delete-blacklist-%d')", item.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 37, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("CORS Blacklist").Render(templ.WithChildren(ctx, templ_7745c5c3_Var21), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func BlacklistCreate(errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var28 := templ.GetChildren(ctx) + if templ_7745c5c3_Var28 == nil { + templ_7745c5c3_Var28 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var29 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 40, "

Block Origin

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["origin"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 41, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var30 string + templ_7745c5c3_Var30, templ_7745c5c3_Err = templ.JoinStringErrs(errors["origin"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 187, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var30)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 42, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 43, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Block Origin").Render(templ.WithChildren(ctx, templ_7745c5c3_Var29), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func BlacklistEdit(item *models.CorsBlacklist, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var31 := templ.GetChildren(ctx) + if templ_7745c5c3_Var31 == nil { + templ_7745c5c3_Var31 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var32 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 44, "

Edit Blocked Origin

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if errors["origin"] != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 47, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var35 string + templ_7745c5c3_Var35, templ_7745c5c3_Err = templ.JoinStringErrs(errors["origin"]) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 212, Col: 79} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var35)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 48, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 49, "
Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Edit Blocked Origin").Render(templ.WithChildren(ctx, templ_7745c5c3_Var32), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func RateLimitList(items []models.RateLimitSetting) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var37 := templ.GetChildren(ctx) + if templ_7745c5c3_Var37 == nil { + templ_7745c5c3_Var37 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var38 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 51, "

Rate Limits

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range items { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 52, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 64, "
NameDescriptionMax RequestsWindow (s)Actions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var39 string + templ_7745c5c3_Var39, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 248, Col: 113} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var39)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 53, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var40 string + templ_7745c5c3_Var40, templ_7745c5c3_Err = templ.JoinStringErrs(item.Description) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 249, Col: 108} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var40)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 54, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var41 string + templ_7745c5c3_Var41, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", item.MaxRequests)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 250, Col: 127} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var41)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 55, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var42 string + templ_7745c5c3_Var42, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%v", item.WindowSeconds)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 251, Col: 129} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var42)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 56, "Edit ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if item.Name != "api" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 58, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete('delete-ratelimit-%d')", item.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 61, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 63, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Rate Limits").Render(templ.WithChildren(ctx, templ_7745c5c3_Var38), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +func RateLimitEdit(item *models.RateLimitSetting, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var47 := templ.GetChildren(ctx) + if templ_7745c5c3_Var47 == nil { + templ_7745c5c3_Var47 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var48 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 65, "

Edit Rate Limit: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var49 string + templ_7745c5c3_Var49, templ_7745c5c3_Err = templ.JoinStringErrs(item.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/settings/settings.templ`, Line: 271, Col: 94} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var49)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 66, "

Cancel
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = Layout("Edit Rate Limit").Render(templ.WithChildren(ctx, templ_7745c5c3_Var48), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/user/create.templ b/views/admin/user/create.templ new file mode 100644 index 0000000..2b0039a --- /dev/null +++ b/views/admin/user/create.templ @@ -0,0 +1,21 @@ +package user + +import ( + "gobeyhan/views/admin" + "gobeyhan/database/models" +) + +templ Create(roles []models.Role, errors map[string]string) { + @admin.Layout("Create User") { +
+

Create New User

+ @Form(FormProps{ + User: models.User{}, + Roles: roles, + Action: "/admin/users", + IsEdit: false, + Errors: errors, + }) +
+ } +} diff --git a/views/admin/user/create_templ.go b/views/admin/user/create_templ.go new file mode 100644 index 0000000..40266c8 --- /dev/null +++ b/views/admin/user/create_templ.go @@ -0,0 +1,77 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package user + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "gobeyhan/database/models" + "gobeyhan/views/admin" +) + +func Create(roles []models.Role, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Create New User

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Form(FormProps{ + User: models.User{}, + Roles: roles, + Action: "/admin/users", + IsEdit: false, + Errors: errors, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = admin.Layout("Create User").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/user/edit.templ b/views/admin/user/edit.templ new file mode 100644 index 0000000..3cc7642 --- /dev/null +++ b/views/admin/user/edit.templ @@ -0,0 +1,22 @@ +package user + +import ( + "gobeyhan/views/admin" + "gobeyhan/database/models" + "fmt" +) + +templ Edit(user models.User, roles []models.Role, errors map[string]string) { + @admin.Layout("Edit User") { +
+

Edit User

+ @Form(FormProps{ + User: user, + Roles: roles, + Action: fmt.Sprintf("/admin/users/%d", user.ID), + IsEdit: true, + Errors: errors, + }) +
+ } +} diff --git a/views/admin/user/edit_templ.go b/views/admin/user/edit_templ.go new file mode 100644 index 0000000..59a1f89 --- /dev/null +++ b/views/admin/user/edit_templ.go @@ -0,0 +1,78 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package user + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" + "gobeyhan/views/admin" +) + +func Edit(user models.User, roles []models.Role, errors map[string]string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Edit User

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = Form(FormProps{ + User: user, + Roles: roles, + Action: fmt.Sprintf("/admin/users/%d", user.ID), + IsEdit: true, + Errors: errors, + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = admin.Layout("Edit User").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/user/form.templ b/views/admin/user/form.templ new file mode 100644 index 0000000..27aa492 --- /dev/null +++ b/views/admin/user/form.templ @@ -0,0 +1,77 @@ +package user + +import ( + "gobeyhan/database/models" + "gobeyhan/views/components" + "fmt" +) + +type FormProps struct { + User models.User + Roles []models.Role + Action string + IsEdit bool + Errors map[string]string +} + +templ Form(props FormProps) { +
+ @components.Input(components.InputProps{ + Label: "Username", + Name: "username", + Type: "text", + Value: props.User.UserName, + Error: props.Errors["username"], + }) + + @components.Input(components.InputProps{ + Label: "Email", + Name: "email", + Type: "email", + Value: props.User.Email, + Error: props.Errors["email"], + }) + +
+ + +
+ +
+ + +
+ + if !props.IsEdit { + @components.Input(components.InputProps{ + Label: "Password", + Name: "password", + Type: "password", + Error: props.Errors["password"], + }) + } else { +
+ + +
+ } + +
+ Cancel + @components.Button(components.ButtonProps{ + Type: "submit", + Label: "Save", + Class: "w-auto", + }) +
+
+} diff --git a/views/admin/user/form_templ.go b/views/admin/user/form_templ.go new file mode 100644 index 0000000..64654c9 --- /dev/null +++ b/views/admin/user/form_templ.go @@ -0,0 +1,179 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package user + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" + "gobeyhan/views/components" +) + +type FormProps struct { + User models.User + Roles []models.Role + Action string + IsEdit bool + Errors map[string]string +} + +func Form(props FormProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Input(components.InputProps{ + Label: "Username", + Name: "username", + Type: "text", + Value: props.User.UserName, + Error: props.Errors["username"], + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Input(components.InputProps{ + Label: "Email", + Name: "email", + Type: "email", + Value: props.User.Email, + Error: props.Errors["email"], + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if !props.IsEdit { + templ_7745c5c3_Err = components.Input(components.InputProps{ + Label: "Password", + Name: "password", + Type: "password", + Error: props.Errors["password"], + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
Cancel") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = components.Button(components.ButtonProps{ + Type: "submit", + Label: "Save", + Class: "w-auto", + }).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 14, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/admin/user/list.templ b/views/admin/user/list.templ new file mode 100644 index 0000000..2d9f11a --- /dev/null +++ b/views/admin/user/list.templ @@ -0,0 +1,71 @@ +package user + +import ( + "gobeyhan/database/models" + "gobeyhan/views/admin" + "fmt" +) + +templ List(users []models.User) { + @admin.Layout("User Management") { +
+
+

Users

+ Add User +
+ +
+ + + + + + + + + + + + for _, user := range users { + + + + + + + + } + +
IDNameEmailRoleActions
{ fmt.Sprintf("%d", user.ID) }{ user.UserName }{ user.Email } + if len(user.Roles) > 0 { + { user.Roles[0].Name } + } else { + - + } + + Edit +
+ +
+
+
+
+ + } +} diff --git a/views/admin/user/list_templ.go b/views/admin/user/list_templ.go new file mode 100644 index 0000000..6ecbe4a --- /dev/null +++ b/views/admin/user/list_templ.go @@ -0,0 +1,189 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package user + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import ( + "fmt" + "gobeyhan/database/models" + "gobeyhan/views/admin" +) + +func List(users []models.User) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, user := range users { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 13, "
IDNameEmailRoleActions
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprintf("%d", user.ID)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/user/list.templ`, Line: 31, Col: 98} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(user.UserName) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/user/list.templ`, Line: 32, Col: 97} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(user.Email) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/user/list.templ`, Line: 33, Col: 82} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if len(user.Roles) > 0 { + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(user.Roles[0].Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/admin/user/list.templ`, Line: 36, Col: 60} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "-") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "Edit
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ.RenderScriptItems(ctx, templ_7745c5c3_Buffer, templ.ComponentScript{Call: fmt.Sprintf("confirmDelete(%d)", user.ID)}) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = admin.Layout("User Management").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/components/button.templ b/views/components/button.templ new file mode 100644 index 0000000..6c6fbbe --- /dev/null +++ b/views/components/button.templ @@ -0,0 +1,16 @@ +package components + +type ButtonProps struct { + Type string // submit, button, reset + Label string + Class string +} + +templ Button(props ButtonProps) { + +} diff --git a/views/components/button_templ.go b/views/components/button_templ.go new file mode 100644 index 0000000..f4eaf57 --- /dev/null +++ b/views/components/button_templ.go @@ -0,0 +1,90 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type ButtonProps struct { + Type string // submit, button, reset + Label string + Class string +} + +func Button(props ButtonProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + var templ_7745c5c3_Var2 = []any{"flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600 " + props.Class} + templ_7745c5c3_Err = templ.RenderCSSItems(ctx, templ_7745c5c3_Buffer, templ_7745c5c3_Var2...) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/views/components/input.templ b/views/components/input.templ new file mode 100644 index 0000000..0206202 --- /dev/null +++ b/views/components/input.templ @@ -0,0 +1,27 @@ +package components + +type InputProps struct { + Label string + Name string + Type string + Placeholder string + Value string + Error string +} + +templ Input(props InputProps) { +
+ + + if props.Error != "" { +

{ props.Error }

+ } +
+} diff --git a/views/components/input_templ.go b/views/components/input_templ.go new file mode 100644 index 0000000..2775df5 --- /dev/null +++ b/views/components/input_templ.go @@ -0,0 +1,163 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.977 +package components + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type InputProps struct { + Label string + Name string + Type string + Placeholder string + Value string + Error string +} + +func Input(props InputProps) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if props.Error != "" { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(props.Error) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `views/components/input.templ`, Line: 24, Col: 62} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/yapilacak.txt b/yapilacak.txt new file mode 100644 index 0000000..e69de29