refactor. pre-deploy

This commit is contained in:
2025-10-29 20:22:35 +09:00
parent 18497d4343
commit 367e1c932e
113 changed files with 8245 additions and 67 deletions

View File

@@ -0,0 +1,19 @@
FROM node:20-alpine
WORKDIR /app
# Копирование package.json и package-lock.json
COPY package*.json ./
# Установка зависимостей
RUN npm install
# Копирование исходного кода
COPY . .
# Сборка приложения
RUN npm run build
EXPOSE 3000
CMD ["npm", "start"]

View File

@@ -0,0 +1,59 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'web',
port: '8000',
pathname: '/storage/**',
},
{
protocol: 'http',
hostname: '127.0.0.1',
port: '8000',
pathname: '/storage/**',
},
{
protocol: 'http',
hostname: 'localhost',
port: '8000',
pathname: '/storage/**',
},
],
},
async rewrites() {
return [
{
source: '/api/:path*',
destination: 'http://web:8000/api/:path*',
},
];
},
eslint: {
ignoreDuringBuilds: true,
},
typescript: {
ignoreBuildErrors: true,
},
trailingSlash: false,
skipTrailingSlashRedirect: true,
async headers() {
return [
{
source: '/api/:path*',
headers: [
{ key: 'Access-Control-Allow-Origin', value: '*' },
{ key: 'Access-Control-Allow-Methods', value: 'GET, POST, PUT, DELETE, OPTIONS' },
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type, Authorization' },
],
},
];
},
};
export default nextConfig;

View File

@@ -2,36 +2,55 @@
const nextConfig = {
images: {
remotePatterns: [
{
protocol: 'http',
hostname: 'web',
port: '8000', // where Django serves media
pathname: '/storage/**', // storage/avatars, images/link_groups, etc.
},
{
protocol: 'http',
hostname: '127.0.0.1',
port: '8000', // where Django serves media
pathname: '/storage/**', // storage/avatars, images/link_groups, etc.
},
{
protocol: 'http',
hostname: 'localhost',
port: '8000', // where Django serves media
pathname: '/storage/**', // storage/avatars, images/link_groups, etc.
},
],
},
// proxy all /api/* calls to Django
async rewrites() {
return [
{
source: '/api/:path*/',
destination: 'http://web:8000/api/:path*/',
},
{
source: '/api/:path*',
destination: 'http://127.0.0.1:8000/api/:path*',
destination: 'http://web:8000/api/:path*/',
},
];
},
experimental: {
// whitelist origins youll browse from in dev
allowedDevOrigins: [
'http://localhost:3001',
'http://127.0.0.1:3001',
'http://192.168.219.114:3001',
'http://0.0.0.0:3001',
'http://localhost:3000',
'http://192.168.219.114:3000',
],
eslint: {
// Предупреждение: это позволяет продакшн сборки завершаться успешно даже если
// ваш проект имеет ошибки ESLint.
ignoreDuringBuilds: true,
},
typescript: {
// Опасно: это позволяет продакшн сборки завершаться успешно даже если
// ваш проект имеет ошибки TypeScript.
ignoreBuildErrors: true,
},
// Добавляем настройки для правильной работы в development
trailingSlash: false,
skipTrailingSlashRedirect: true,
};
module.exports = nextConfig;

View File

@@ -22,7 +22,7 @@ export default function LoginPage() {
setApiError(null)
try {
const res = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/api/auth/login/`,
'/api/auth/login',
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },

View File

@@ -60,7 +60,7 @@ export default function DashboardPage() {
iconFile: null,
})
const API = process.env.NEXT_PUBLIC_API_URL ?? ''
const API = ''
useEffect(() => {
const token = localStorage.getItem('token')
@@ -70,9 +70,9 @@ export default function DashboardPage() {
}
// загружаем профиль, группы и ссылки
Promise.all([
fetch(`${API}/api/auth/user/`, { headers: { Authorization: `Bearer ${token}` } }),
fetch(`${API}/api/groups/`, { headers: { Authorization: `Bearer ${token}` } }),
fetch(`${API}/api/links/`, { headers: { Authorization: `Bearer ${token}` } }),
fetch('/api/auth/user', { headers: { Authorization: `Bearer ${token}` } }),
fetch('/api/groups', { headers: { Authorization: `Bearer ${token}` } }),
fetch('/api/links', { headers: { Authorization: `Bearer ${token}` } }),
])
.then(async ([uRes, gRes, lRes]) => {
if (!uRes.ok) throw new Error('Не удалось получить профиль')
@@ -98,8 +98,8 @@ export default function DashboardPage() {
async function reloadData() {
const token = localStorage.getItem('token')!
const [gRes, lRes] = await Promise.all([
fetch(`${API}/api/groups/`, { headers: { Authorization: `Bearer ${token}` } }),
fetch(`${API}/api/links/`, { headers: { Authorization: `Bearer ${token}` } }),
fetch('/api/groups', { headers: { Authorization: `Bearer ${token}` } }),
fetch('/api/links', { headers: { Authorization: `Bearer ${token}` } }),
])
const groupsData = await gRes.json()
const linksData = await lRes.json()
@@ -128,8 +128,8 @@ export default function DashboardPage() {
fd.append('name', groupForm.name)
if (groupForm.iconFile) fd.append('icon', groupForm.iconFile)
const url = groupModalMode === 'add'
? `${API}/api/groups/`
: `${API}/api/groups/${editingGroup?.id}/`
? '/api/groups'
: `/api/groups/${editingGroup?.id}`
const method = groupModalMode === 'add' ? 'POST' : 'PATCH'
await fetch(url, {
method,
@@ -142,7 +142,7 @@ export default function DashboardPage() {
async function handleDeleteGroup(grp: Group) {
if (!confirm(`Удалить группу "${grp.name}"?`)) return
const token = localStorage.getItem('token')!
await fetch(`${API}/api/groups/${grp.id}/`, {
await fetch(`/api/groups/${grp.id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})
@@ -171,8 +171,8 @@ export default function DashboardPage() {
if (linkForm.iconFile) fd.append('icon', linkForm.iconFile)
fd.append('group', String(currentGroupIdForLink))
const url = linkModalMode === 'add'
? `${API}/api/links/`
: `${API}/api/links/${editingLink?.id}/`
? '/api/links'
: `/api/links/${editingLink?.id}`
const method = linkModalMode === 'add' ? 'POST' : 'PATCH'
await fetch(url, {
method,
@@ -185,7 +185,7 @@ export default function DashboardPage() {
async function handleDeleteLink(link: LinkItem) {
if (!confirm(`Удалить ссылку "${link.title}"?`)) return
const token = localStorage.getItem('token')!
await fetch(`${API}/api/links/${link.id}/`, {
await fetch(`/api/links/${link.id}`, {
method: 'DELETE',
headers: { Authorization: `Bearer ${token}` },
})

View File

@@ -28,7 +28,7 @@ export default async function UserPage({
params: Promise<{ username: string }>
}) {
const { username } = await params
const API = process.env.NEXT_PUBLIC_API_URL
const API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8000'
const res = await fetch(`${API}/api/users/${username}/public`, {
cache: 'no-store',

View File

@@ -23,7 +23,7 @@ export function LayoutWrapper({ children }: { children: ReactNode }) {
useEffect(() => {
const token = localStorage.getItem('token')
if (token) {
fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/auth/user/`, {
fetch('/api/auth/user', {
headers: { Authorization: `Bearer ${token}` },
})
.then(res => {
@@ -82,7 +82,7 @@ export function LayoutWrapper({ children }: { children: ReactNode }) {
src={
user.avatar.startsWith('http')
? user.avatar
: `${process.env.NEXT_PUBLIC_API_URL}${user.avatar}`
: `http://localhost:8000${user.avatar}`
}
alt="Avatar"
width={32}