REFACTOR(repo): simplify project structure

- Move services/nextjs to nextjs/
- Move deploy/docker/Dockerfile.prod to Dockerfile
- Add GitHub Actions workflows (ci.yml, build.yml)
- Remove deploy/, services/, scripts/ folders
This commit is contained in:
2026-01-05 02:29:10 +09:00
commit e82ea71c22
205 changed files with 21304 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
import { NextRequest, NextResponse } from 'next/server';
import { uploadFileServer, deleteFile } from '@/shared/lib/s3';
import { cookies } from 'next/headers';
import { verifyToken } from '@/shared/lib/auth';
// File size limits (in bytes)
const MAX_FILE_SIZE = {
image: 10 * 1024 * 1024, // 10MB
audio: 50 * 1024 * 1024, // 50MB
document: 20 * 1024 * 1024, // 20MB
};
// Allowed file types
const ALLOWED_TYPES = {
image: ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'],
audio: ['audio/mpeg', 'audio/wav', 'audio/ogg', 'audio/mp4', 'audio/webm'],
document: [
'application/pdf',
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/plain',
'text/csv',
],
};
export async function POST(request: NextRequest) {
try {
// Verify authentication - check cookie
const cookieStore = await cookies();
const cookieToken = cookieStore.get('auth-token')?.value;
const token = cookieToken;
if (!token) {
console.error('[Upload] No token found in Authorization header or cookie');
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const decoded = verifyToken(token);
if (!decoded) {
console.error('[Upload] Token verification failed');
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 }
);
}
console.log('[Upload] Token verified successfully for user:', decoded.userId);
// Parse form data
const formData = await request.formData();
const file = formData.get('file') as File;
const fileType = formData.get('type') as 'images' | 'audio' | 'documents';
if (!file) {
return NextResponse.json(
{ error: 'No file provided' },
{ status: 400 }
);
}
// Determine folder based on file type
let folder: 'images' | 'audio' | 'documents' = 'documents';
let maxSize = MAX_FILE_SIZE.document;
let allowedTypes = ALLOWED_TYPES.document;
if (fileType === 'images') {
folder = 'images';
maxSize = MAX_FILE_SIZE.image;
allowedTypes = ALLOWED_TYPES.image;
} else if (fileType === 'audio') {
folder = 'audio';
maxSize = MAX_FILE_SIZE.audio;
allowedTypes = ALLOWED_TYPES.audio;
} else if (file.type.startsWith('image/')) {
folder = 'images';
maxSize = MAX_FILE_SIZE.image;
allowedTypes = ALLOWED_TYPES.image;
} else if (file.type.startsWith('audio/')) {
folder = 'audio';
maxSize = MAX_FILE_SIZE.audio;
allowedTypes = ALLOWED_TYPES.audio;
}
// Validate file size
if (file.size > maxSize) {
return NextResponse.json(
{ error: `File size exceeds maximum limit of ${maxSize / 1024 / 1024}MB` },
{ status: 400 }
);
}
// Validate file type
if (!allowedTypes.includes(file.type)) {
return NextResponse.json(
{ error: `File type ${file.type} is not allowed` },
{ status: 400 }
);
}
// Upload file to S3
const { url, path } = await uploadFileServer(file, file.name, file.type, folder);
return NextResponse.json({
success: true,
url,
path,
fileName: file.name,
fileSize: file.size,
fileType: file.type,
});
} catch (error) {
console.error('Upload error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to upload file' },
{ status: 500 }
);
}
}
// DELETE /api/upload - Delete a file from storage
export async function DELETE(request: NextRequest) {
try {
// Verify authentication
const authHeader = request.headers.get('Authorization');
const cookieStore = await cookies();
const cookieToken = cookieStore.get('auth-token')?.value;
const token = authHeader?.startsWith('Bearer ')
? authHeader.substring(7)
: cookieToken;
if (!token) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
const decoded = verifyToken(token);
if (!decoded) {
return NextResponse.json(
{ error: 'Invalid token' },
{ status: 401 }
);
}
const { searchParams } = new URL(request.url);
const filePath = searchParams.get('path');
if (!filePath) {
return NextResponse.json(
{ error: 'File path is required' },
{ status: 400 }
);
}
// Delete file from S3
await deleteFile(filePath);
return NextResponse.json({
success: true,
message: 'File deleted successfully',
});
} catch (error) {
console.error('Delete file error:', error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : 'Failed to delete file' },
{ status: 500 }
);
}
}