first commit

This commit is contained in:
Beyhan Oğur
2026-04-26 22:07:47 +03:00
commit 5285a0dd86
522 changed files with 41738 additions and 0 deletions

153
server/api/auth/[...].ts Normal file
View File

@@ -0,0 +1,153 @@
// file: ~/server/api/auth/[...].ts
import { NuxtAuthHandler } from '#auth'
import CredentialsProvider from 'next-auth/providers/credentials';
import GithubProvider from 'next-auth/providers/github'
import GoogleProvider from 'next-auth/providers/google';
import type { JWT } from 'next-auth/jwt';
const config = useRuntimeConfig();
const API_BASE_URL = config.public.NUXT_PUBLIC_API_BASE || 'http://127.0.0.1:8080';
// Helper to refresh the access token
async function refreshAccessToken(token: JWT): Promise<JWT> {
try {
if (!token.refreshToken) {
throw new Error("No refresh token available");
}
const response = await fetch(`${API_BASE_URL}/api/v1/auth/refresh`, {
headers: { 'Content-Type': 'application/json' },
method: 'POST',
body: JSON.stringify({ refresh_token: token.refreshToken }),
});
const refreshedProperies = await response.json();
if (!response.ok) {
throw refreshedProperies;
}
return {
...token,
accessToken: refreshedProperies.access_token,
refreshToken: refreshedProperies.refresh_token ?? token.refreshToken, // Fallback to old refresh token
accessTokenExpires: Date.now() + 15 * 60 * 1000, // Varsayılan 15 dk, backend yanıtına göre ayarlanabilir
};
} catch (error) {
console.error('RefreshAccessTokenError', error);
return {
...token,
error: 'RefreshAccessTokenError',
};
}
}
export default NuxtAuthHandler({
secret: config.authSecret,
providers: [
// @ts-expect-error
GithubProvider.default({
clientId: config.githubClientId,
clientSecret: config.githubClientSecret
}),
// @ts-expect-error
GoogleProvider.default({
clientId: config.googleClientId,
clientSecret: config.googleClientSecret
}),
// @ts-expect-error
CredentialsProvider.default({
name: 'Credentials',
credentials: {
email: { label: 'Email', type: 'email' },
password: { label: 'Password', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) return null;
try {
const res = await fetch(`${API_BASE_URL}/api/v1/auth/login`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: credentials.email,
password: credentials.password
})
});
const user = await res.json();
// Backend dönüş formatı: { access_token, refresh_token, user: { ... } }
if (res.ok && user.access_token && user.user) {
return {
...user.user, // id, email, username, is_admin vb.
access_token: user.access_token,
refresh_token: user.refresh_token,
};
}
return null;
} catch (error) {
console.error('Login error:', error);
return null;
}
}
})
],
callbacks: {
async jwt({ token, user, account }) {
// Initial sign in
if (account && user) {
// Social login case handled here if needed, consistent with Credentials
// Eğer social login backend değişimi yapılacaksa burası da güncellenmeli.
// Şimdilik Credentials akışına odaklanıyoruz.
return {
...token,
accessToken: user.access_token,
refreshToken: user.refresh_token,
accessTokenExpires: Date.now() + 15 * 60 * 1000, // 15 dakika
id: user.id as number,
email: user.email as string,
username: user.username as string,
first_name: user.first_name as string,
last_name: user.last_name as string,
is_admin: user.is_admin as boolean,
// Diğer alanlar
};
}
// Return previous token if the access token has not expired yet
if (Date.now() < (token.accessTokenExpires as number)) {
return token;
}
// Access token has expired, try to update it
return refreshAccessToken(token);
},
async session({ session, token }) {
if (token) {
session.user.id = token.id;
session.user.email = token.email;
session.user.username = token.username;
session.user.first_name = token.first_name;
session.user.last_name = token.last_name;
session.user.is_admin = token.is_admin;
session.accessToken = token.accessToken;
session.refreshToken = token.refreshToken;
session.error = token.error;
}
return session;
}
},
pages: {
signIn: '/auth/login',
newUser: '/auth/register' // Opsiyonel
},
session: {
strategy: 'jwt'
},
// Debug only in development
debug: process.env.NODE_ENV === 'development',
})

View File

@@ -0,0 +1,62 @@
import sharp from 'sharp';
export default defineEventHandler(async (event) => {
const files = await readMultipartFormData(event);
if (!files || files.length === 0) {
throw createError({ statusCode: 400, statusMessage: 'Dosya yüklenmedi' });
}
// Dosyayı bul (adı 'file' olan)
const file = files.find(f => f.name === 'file');
if (!file || !file.filename) {
throw createError({ statusCode: 400, statusMessage: 'Geçersiz dosya' });
}
// Form alanlarından parametreleri al
const getField = (name: string) => {
const field = files.find(f => f.name === name);
return field ? field.data.toString() : null;
};
const format = getField('format') || 'avif';
const quality = parseInt(getField('quality') || '85', 10);
const width = getField('width') ? parseInt(getField('width')!, 10) : null;
const height = getField('height') ? parseInt(getField('height')!, 10) : null;
try {
let pipeline = sharp(file.data);
// Resize işlemi (en veya boy varsa)
if (width || height) {
pipeline = pipeline.resize(width, height, { fit: 'cover' });
}
// Format ve kalite ayarı
if (format === 'avif') {
pipeline = pipeline.avif({ quality });
} else if (format === 'webp') {
pipeline = pipeline.webp({ quality });
} else if (format === 'png') {
pipeline = pipeline.png({ quality });
} else if (format === 'jpeg' || format === 'jpg') {
pipeline = pipeline.jpeg({ quality });
} else {
// Varsayılan avif
pipeline = pipeline.avif({ quality });
}
const optimizedBuffer = await pipeline.toBuffer();
// Content-Type i ayarla
let contentType = 'image/avif';
if (format === 'webp') contentType = 'image/webp';
if (format === 'png') contentType = 'image/png';
if (format === 'jpeg' || format === 'jpg') contentType = 'image/jpeg';
setResponseHeader(event, 'Content-Type', contentType);
return optimizedBuffer;
} catch (error) {
console.error('Resim optimizasyonu hatası:', error);
throw createError({ statusCode: 500, statusMessage: 'Resim optimize edilemedi' });
}
});

58
server/api/upload.post.ts Normal file
View File

@@ -0,0 +1,58 @@
import sharp from 'sharp';
import { promises as fs } from 'fs';
import path from 'path';
export default defineEventHandler(async (event) => {
// Ensure user is admin
// Note: Assuming global middleware or checking specific context/auth here if strict security is needed.
// For now, relying on the fact that this is used within Admin context.
const files = await readMultipartFormData(event);
if (!files || files.length === 0) {
throw createError({ statusCode: 400, statusMessage: 'Dosya yüklenmedi' });
}
const file = files.find(f => f.name === 'file');
if (!file || !file.filename) {
throw createError({ statusCode: 400, statusMessage: 'Geçersiz dosya' });
}
// Check file type
const validTypes = ['image/jpeg', 'image/png', 'image/webp', 'image/avif', 'image/gif'];
if (!validTypes.includes(file.type || '')) {
throw createError({ statusCode: 400, statusMessage: 'Sadece resim dosyaları yüklenebilir (jpeg, png, webp, avif, gif)' });
}
try {
const uploadDir = path.join(process.cwd(), 'public', 'uploads');
// Ensure directory exists
try {
await fs.access(uploadDir);
} catch {
await fs.mkdir(uploadDir, { recursive: true });
}
const ext = path.extname(file.filename);
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
const filename = 'avatar-' + uniqueSuffix + ext; // Keeping original extension for simplicity unless converting
const filepath = path.join(uploadDir, filename);
// Process with Sharp (Optional: Resize to max 800x800 for avatars to save space)
await sharp(file.data)
.resize(800, 800, {
fit: 'inside',
withoutEnlargement: true
})
.toFile(filepath);
return {
url: '/uploads/' + filename,
message: 'Dosya başarıyla yüklendi'
};
} catch (error) {
console.error('Upload error:', error);
throw createError({ statusCode: 500, statusMessage: 'Dosya yüklenemedi' });
}
});

23
server/api/verify.post.ts Normal file
View File

@@ -0,0 +1,23 @@
export default defineEventHandler(async (event) => {
const body = await readBody(event)
const token = body.token
if (!token) {
throw createError({
statusCode: 422,
statusMessage: 'Token not provided.',
})
}
// NuxtTurnstile modülünün sağladığı sunucu fonksiyonu
const validation = await verifyTurnstileToken(token)
if (!validation.success) {
throw createError({
statusCode: 400,
statusMessage: 'Captcha validation failed.',
})
}
return { success: true }
})