Compare commits
14 Commits
83b162d2bd
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 72ec02306e | |||
| 37c3b5af9d | |||
| 1655006115 | |||
| 3cd7563c75 | |||
| 7a9a1fcbe8 | |||
| 6bd16ff68b | |||
| dc41178541 | |||
| 0792e5d2bc | |||
| cfd25ea1df | |||
| 15603fad41 | |||
| 0a22b1bf04 | |||
| 0029f06774 | |||
| 8edaa0e517 | |||
| a4bc12e1c4 |
@@ -1 +1 @@
|
|||||||
{"id":"dev","timestamp":1759249355595}
|
{"id":"dev","timestamp":1759251838389}
|
||||||
@@ -1 +1 @@
|
|||||||
{"id":"dev","timestamp":1759249355595,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|
{"id":"dev","timestamp":1759251838389,"matcher":{"static":{},"wildcard":{},"dynamic":{}},"prerendered":[]}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"date": "2025-09-30T16:22:39.361Z",
|
"date": "2025-09-30T17:04:01.442Z",
|
||||||
"preset": "nitro-dev",
|
"preset": "nitro-dev",
|
||||||
"framework": {
|
"framework": {
|
||||||
"name": "nuxt",
|
"name": "nuxt",
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"dev": {
|
"dev": {
|
||||||
"pid": 77277,
|
"pid": 77277,
|
||||||
"workerAddress": {
|
"workerAddress": {
|
||||||
"socketPath": "/var/folders/qw/cvgxs3ls7x742rc9zgwbzkh80000gn/T/nitro-worker-77277-1-1-2519.sock"
|
"socketPath": "/var/folders/qw/cvgxs3ls7x742rc9zgwbzkh80000gn/T/nitro-worker-77277-3-3-8923.sock"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
.nuxt/nuxt.d.ts
vendored
4
.nuxt/nuxt.d.ts
vendored
@@ -1,9 +1,9 @@
|
|||||||
// Generated by nuxi
|
// Generated by nuxi
|
||||||
/// <reference types="@vueuse/nuxt" />
|
|
||||||
/// <reference types="@nuxtjs/device" />
|
/// <reference types="@nuxtjs/device" />
|
||||||
/// <reference types="@nuxt/telemetry" />
|
|
||||||
/// <reference types="@pinia/nuxt" />
|
/// <reference types="@pinia/nuxt" />
|
||||||
/// <reference types="@nuxt/devtools" />
|
/// <reference types="@nuxt/devtools" />
|
||||||
|
/// <reference types="@nuxt/telemetry" />
|
||||||
|
/// <reference types="@vueuse/nuxt" />
|
||||||
/// <reference types="nuxt" />
|
/// <reference types="nuxt" />
|
||||||
/// <reference path="types/app-defaults.d.ts" />
|
/// <reference path="types/app-defaults.d.ts" />
|
||||||
/// <reference path="types/plugins.d.ts" />
|
/// <reference path="types/plugins.d.ts" />
|
||||||
|
|||||||
@@ -33,3 +33,9 @@ npm run dev
|
|||||||
```sh
|
```sh
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
# 🚀 자동 배포 테스트 - #오후
|
||||||
|
# 🚀 자동 배포 테스트 - 2025-10-01 02:00:14
|
||||||
|
# 🚀 자동 배포 테스트 - 2025-10-01 02:14:30
|
||||||
|
# 🎉 자동 배포 시스템 완성! - 2025-10-01 02:21:39
|
||||||
|
# 🧪 자동 배포 테스트 - 2025-10-01 02:34:11
|
||||||
|
# 🚀 자동 배포 재테스트 - 2025-10-01 02:42:33
|
||||||
|
|||||||
24
ecosystem.config.cjs
Normal file
24
ecosystem.config.cjs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "music-admin",
|
||||||
|
script: ".output/server/index.mjs",
|
||||||
|
cwd: "/var/www/music/music-admin",
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: "1G",
|
||||||
|
env: {
|
||||||
|
NODE_ENV: "production",
|
||||||
|
PORT: 3000,
|
||||||
|
DATABASE_URL:
|
||||||
|
"postgresql://musicuser:Tjqjqhdks$321@localhost:5432/musicdb",
|
||||||
|
JWT_SECRET: "vTEq9OTvIFs3ZbDaszLRL/ZiAEXziemX1Wh1GIeb+DI=",
|
||||||
|
},
|
||||||
|
error_file: "/var/log/music-admin-error.log",
|
||||||
|
out_file: "/var/log/music-admin-out.log",
|
||||||
|
log_file: "/var/log/music-admin.log",
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -12,8 +12,8 @@ module.exports = {
|
|||||||
NODE_ENV: "production",
|
NODE_ENV: "production",
|
||||||
PORT: 3000,
|
PORT: 3000,
|
||||||
DATABASE_URL:
|
DATABASE_URL:
|
||||||
"postgresql://username:password@localhost:5432/music_admin?schema=public",
|
"postgresql://musicuser:Tjqjqhdks$321@localhost:5432/musicdb",
|
||||||
JWT_SECRET: "your-jwt-secret-key-here",
|
JWT_SECRET: "vTEq9OTvIFs3ZbDaszLRL/ZiAEXziemX1Wh1GIeb+DI=",
|
||||||
},
|
},
|
||||||
error_file: "/var/log/music-admin-error.log",
|
error_file: "/var/log/music-admin-error.log",
|
||||||
out_file: "/var/log/music-admin-out.log",
|
out_file: "/var/log/music-admin-out.log",
|
||||||
|
|||||||
102
nuxt.config.ts
102
nuxt.config.ts
@@ -1,19 +1,21 @@
|
|||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from "node:url";
|
||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from "vite-svg-loader";
|
||||||
import vuetify from 'vite-plugin-vuetify'
|
import vuetify from "vite-plugin-vuetify";
|
||||||
|
|
||||||
// https://nuxt.com/docs/api/configuration/nuxt-config
|
// https://nuxt.com/docs/api/configuration/nuxt-config
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
app: {
|
app: {
|
||||||
head: {
|
head: {
|
||||||
titleTemplate: '%s - NuxtJS Admin Template',
|
titleTemplate: "%s - NuxtJS Admin Template",
|
||||||
title: 'Sneat',
|
title: "Sneat",
|
||||||
|
|
||||||
link: [{
|
link: [
|
||||||
rel: 'icon',
|
{
|
||||||
type: 'image/x-icon',
|
rel: "icon",
|
||||||
href: '/favicon.ico',
|
type: "image/x-icon",
|
||||||
}],
|
href: "/favicon.ico",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -22,29 +24,33 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
css: [
|
css: [
|
||||||
'@core/scss/template/index.scss',
|
"@core/scss/template/index.scss",
|
||||||
'@styles/styles.scss',
|
"@styles/styles.scss",
|
||||||
'@/plugins/iconify/icons.css',
|
"@/plugins/iconify/icons.css",
|
||||||
'@layouts/styles/index.scss',
|
"@layouts/styles/index.scss",
|
||||||
],
|
],
|
||||||
|
|
||||||
components: {
|
components: {
|
||||||
dirs: [{
|
dirs: [
|
||||||
path: '@/@core/components',
|
{
|
||||||
|
path: "@/@core/components",
|
||||||
pathPrefix: false,
|
pathPrefix: false,
|
||||||
}, {
|
},
|
||||||
path: '~/components/global',
|
{
|
||||||
|
path: "~/components/global",
|
||||||
global: true,
|
global: true,
|
||||||
}, {
|
},
|
||||||
path: '~/components',
|
{
|
||||||
|
path: "~/components",
|
||||||
pathPrefix: false,
|
pathPrefix: false,
|
||||||
}],
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: ['@/plugins/vuetify/index.ts', '@/plugins/iconify/index.ts'],
|
plugins: ["@/plugins/vuetify/index.ts", "@/plugins/iconify/index.ts"],
|
||||||
|
|
||||||
imports: {
|
imports: {
|
||||||
dirs: ['./@core/utils', './@core/composable/', './plugins/*/composables/*'],
|
dirs: ["./@core/utils", "./@core/composable/", "./plugins/*/composables/*"],
|
||||||
},
|
},
|
||||||
|
|
||||||
hooks: {},
|
hooks: {},
|
||||||
@@ -53,17 +59,20 @@ export default defineNuxtConfig({
|
|||||||
typedPages: true,
|
typedPages: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Node.js 22 호환성 설정
|
||||||
|
compatibilityDate: "2024-10-01",
|
||||||
|
|
||||||
typescript: {
|
typescript: {
|
||||||
tsConfig: {
|
tsConfig: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
paths: {
|
paths: {
|
||||||
'@/*': ['../*'],
|
"@/*": ["../*"],
|
||||||
'@layouts/*': ['../@layouts/*'],
|
"@layouts/*": ["../@layouts/*"],
|
||||||
'@layouts': ['../@layouts'],
|
"@layouts": ["../@layouts"],
|
||||||
'@core/*': ['../@core/*'],
|
"@core/*": ["../@core/*"],
|
||||||
'@core': ['../@core'],
|
"@core": ["../@core"],
|
||||||
'@images/*': ['../assets/images/*'],
|
"@images/*": ["../assets/images/*"],
|
||||||
'@styles/*': ['../assets/styles/*'],
|
"@styles/*": ["../assets/styles/*"],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -77,21 +86,24 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
vue: {
|
vue: {
|
||||||
compilerOptions: {
|
compilerOptions: {
|
||||||
isCustomElement: tag => tag === 'swiper-container' || tag === 'swiper-slide',
|
isCustomElement: (tag) =>
|
||||||
|
tag === "swiper-container" || tag === "swiper-slide",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
define: { 'process.env': {} },
|
define: { "process.env": {} },
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('.', import.meta.url)),
|
"@": fileURLToPath(new URL(".", import.meta.url)),
|
||||||
'@core': fileURLToPath(new URL('./@core', import.meta.url)),
|
"@core": fileURLToPath(new URL("./@core", import.meta.url)),
|
||||||
'@layouts': fileURLToPath(new URL('./@layouts', import.meta.url)),
|
"@layouts": fileURLToPath(new URL("./@layouts", import.meta.url)),
|
||||||
'@images': fileURLToPath(new URL('./assets/images/', import.meta.url)),
|
"@images": fileURLToPath(new URL("./assets/images/", import.meta.url)),
|
||||||
'@styles': fileURLToPath(new URL('./assets/styles/', import.meta.url)),
|
"@styles": fileURLToPath(new URL("./assets/styles/", import.meta.url)),
|
||||||
'@configured-variables': fileURLToPath(new URL('./assets/styles/variables/_template.scss', import.meta.url)),
|
"@configured-variables": fileURLToPath(
|
||||||
|
new URL("./assets/styles/variables/_template.scss", import.meta.url)
|
||||||
|
),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -100,25 +112,23 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: ['vuetify'],
|
exclude: ["vuetify"],
|
||||||
entries: [
|
entries: ["./**/*.vue"],
|
||||||
'./**/*.vue',
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
|
||||||
plugins: [
|
plugins: [
|
||||||
svgLoader(),
|
svgLoader(),
|
||||||
vuetify({
|
vuetify({
|
||||||
styles: {
|
styles: {
|
||||||
configFile: 'assets/styles/variables/_vuetify.scss',
|
configFile: "assets/styles/variables/_vuetify.scss",
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
transpile: ['vuetify'],
|
transpile: ["vuetify"],
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: ['@vueuse/nuxt', '@nuxtjs/device', '@pinia/nuxt'],
|
modules: ["@vueuse/nuxt", "@nuxtjs/device", "@pinia/nuxt"],
|
||||||
})
|
});
|
||||||
|
|||||||
113
pages/login.vue
113
pages/login.vue
@@ -1,87 +1,84 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AuthProvider from '@/views/pages/authentication/AuthProvider.vue'
|
import AuthProvider from "@/views/pages/authentication/AuthProvider.vue";
|
||||||
import logo from '@images/logo.svg?raw'
|
import logo from "@images/logo.svg?raw";
|
||||||
import authV1BottomShape from '@images/svg/auth-v1-bottom-shape.svg?url'
|
import authV1BottomShape from "@images/svg/auth-v1-bottom-shape.svg?url";
|
||||||
import authV1TopShape from '@images/svg/auth-v1-top-shape.svg?url'
|
import authV1TopShape from "@images/svg/auth-v1-top-shape.svg?url";
|
||||||
|
|
||||||
const form = ref({
|
const form = ref({
|
||||||
user_id: '',
|
user_id: "",
|
||||||
password: '',
|
password: "",
|
||||||
remember: false,
|
remember: false,
|
||||||
})
|
});
|
||||||
|
|
||||||
const isPasswordVisible = ref(false)
|
const isPasswordVisible = ref(false);
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false);
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref("");
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
// 컴포넌트 마운트 시 테스트
|
// 컴포넌트 마운트 시 테스트
|
||||||
onMounted(() => {
|
onMounted(() => {});
|
||||||
})
|
|
||||||
|
|
||||||
const handleLogin = async () => {
|
const handleLogin = async () => {
|
||||||
|
|
||||||
if (!form.value.user_id || !form.value.password) {
|
if (!form.value.user_id || !form.value.password) {
|
||||||
errorMessage.value = '아이디와 비밀번호를 입력해주세요.'
|
errorMessage.value = "아이디와 비밀번호를 입력해주세요.";
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading.value = true
|
isLoading.value = true;
|
||||||
errorMessage.value = ''
|
errorMessage.value = "";
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const response = await $fetch("/api/auth/login", {
|
||||||
const response = await $fetch('/api/auth/login', {
|
method: "POST",
|
||||||
method: 'POST',
|
|
||||||
body: {
|
body: {
|
||||||
user_id: form.value.user_id,
|
user_id: form.value.user_id,
|
||||||
password: form.value.password
|
password: form.value.password,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
|
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
|
||||||
// 토큰을 쿠키에 저장
|
// 토큰을 쿠키에 저장
|
||||||
const token = useCookie('auth-token', {
|
const token = useCookie("auth-token", {
|
||||||
maxAge: 60 * 60 * 24, // 24시간
|
maxAge: 60 * 60 * 24, // 24시간
|
||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'strict'
|
sameSite: "strict",
|
||||||
})
|
});
|
||||||
token.value = response.token
|
token.value = response.token;
|
||||||
|
|
||||||
// 사용자 정보를 쿠키에 저장
|
// 사용자 정보를 쿠키에 저장
|
||||||
const user = useCookie('user-info', {
|
const user = useCookie("user-info", {
|
||||||
maxAge: 60 * 60 * 24,
|
maxAge: 60 * 60 * 24,
|
||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'strict'
|
sameSite: "strict",
|
||||||
})
|
});
|
||||||
user.value = JSON.stringify(response.user)
|
user.value = JSON.stringify(response.user);
|
||||||
|
|
||||||
|
|
||||||
// 대시보드로 리다이렉션
|
// 대시보드로 리다이렉션
|
||||||
await router.push('/dashboard')
|
await router.push("/dashboard");
|
||||||
} else {
|
} else {
|
||||||
errorMessage.value = response.message || '로그인에 실패했습니다.'
|
errorMessage.value = response.message || "로그인에 실패했습니다.";
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error('Login error:', error)
|
console.error("Login error:", error);
|
||||||
console.error('Error details:', {
|
console.error("Error details:", {
|
||||||
statusCode: error.statusCode,
|
statusCode: error.statusCode,
|
||||||
statusMessage: error.statusMessage,
|
statusMessage: error.statusMessage,
|
||||||
data: error.data
|
data: error.data,
|
||||||
})
|
});
|
||||||
errorMessage.value = error.data?.statusMessage || error.statusMessage || '로그인 중 오류가 발생했습니다.'
|
errorMessage.value =
|
||||||
|
error.data?.statusMessage ||
|
||||||
|
error.statusMessage ||
|
||||||
|
"로그인 중 오류가 발생했습니다.";
|
||||||
} finally {
|
} finally {
|
||||||
isLoading.value = false
|
isLoading.value = false;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'blank',
|
layout: "blank",
|
||||||
middleware: 'guest'
|
middleware: "guest",
|
||||||
})
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -106,38 +103,28 @@ definePageMeta({
|
|||||||
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
|
:class="$vuetify.display.smAndUp ? 'pa-6' : 'pa-0'"
|
||||||
>
|
>
|
||||||
<VCardItem class="justify-center">
|
<VCardItem class="justify-center">
|
||||||
<NuxtLink
|
<NuxtLink to="/" class="app-logo">
|
||||||
to="/"
|
|
||||||
class="app-logo"
|
|
||||||
>
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
<!-- eslint-disable vue/no-v-html -->
|
||||||
<div
|
<!-- <div
|
||||||
class="d-flex"
|
class="d-flex"
|
||||||
v-html="logo"
|
v-html="logo"
|
||||||
/>
|
/>
|
||||||
<h1 class="app-logo-title">
|
<h1 class="app-logo-title">
|
||||||
sneat
|
sneat
|
||||||
</h1>
|
</h1> -->
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</VCardItem>
|
</VCardItem>
|
||||||
|
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<h4 class="text-h4 mb-1">
|
<h4 class="text-h4 mb-1">음악 관리 시스템👋🏻</h4>
|
||||||
음악 관리 시스템👋🏻
|
<p class="mb-0">계정에 로그인하여 관리 시스템을 시작하세요</p>
|
||||||
</h4>
|
|
||||||
<p class="mb-0">
|
|
||||||
계정에 로그인하여 관리 시스템을 시작하세요
|
|
||||||
</p>
|
|
||||||
</VCardText>
|
</VCardText>
|
||||||
|
|
||||||
<VCardText>
|
<VCardText>
|
||||||
<VForm @submit.prevent="handleLogin">
|
<VForm @submit.prevent="handleLogin">
|
||||||
<VRow>
|
<VRow>
|
||||||
<!-- 오류 메시지 -->
|
<!-- 오류 메시지 -->
|
||||||
<VCol
|
<VCol v-if="errorMessage" cols="12">
|
||||||
v-if="errorMessage"
|
|
||||||
cols="12"
|
|
||||||
>
|
|
||||||
<VAlert
|
<VAlert
|
||||||
type="error"
|
type="error"
|
||||||
variant="tonal"
|
variant="tonal"
|
||||||
|
|||||||
20
webhook-ecosystem.config.cjs
Normal file
20
webhook-ecosystem.config.cjs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
module.exports = {
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
name: "music-admin-webhook",
|
||||||
|
script: "webhook-server.cjs",
|
||||||
|
cwd: "/var/www/music/music-admin",
|
||||||
|
instances: 1,
|
||||||
|
autorestart: true,
|
||||||
|
watch: false,
|
||||||
|
max_memory_restart: "100M",
|
||||||
|
env: {
|
||||||
|
NODE_ENV: "production",
|
||||||
|
},
|
||||||
|
error_file: "/var/log/music-admin-webhook-error.log",
|
||||||
|
out_file: "/var/log/music-admin-webhook-out.log",
|
||||||
|
log_file: "/var/log/music-admin-webhook.log",
|
||||||
|
time: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
const express = require("express");
|
import express from "express";
|
||||||
const { exec } = require("child_process");
|
import { exec } from "child_process";
|
||||||
const crypto = require("crypto");
|
import crypto from "crypto";
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const PORT = 9000;
|
const PORT = 9000;
|
||||||
const SECRET = "your-webhook-secret-key"; // 보안을 위해 변경하세요
|
const SECRET = "vTEq9OTvIFs3ZbDaszLRL/ZiAEXziemX1Wh1GIeb+DI="; // 보안을 위해 변경하세요
|
||||||
const PROJECT_DIR = "/var/www/music/music-admin";
|
const PROJECT_DIR = "/var/www/music/music-admin";
|
||||||
|
|
||||||
// JSON 파싱 미들웨어
|
// JSON 파싱 미들웨어
|
||||||
Reference in New Issue
Block a user