first commit
This commit is contained in:
153
server/api/auth/[...].ts
Normal file
153
server/api/auth/[...].ts
Normal 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',
|
||||
})
|
||||
|
||||
62
server/api/optimize.post.ts
Normal file
62
server/api/optimize.post.ts
Normal 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
58
server/api/upload.post.ts
Normal 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
23
server/api/verify.post.ts
Normal 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 }
|
||||
})
|
||||
Reference in New Issue
Block a user