✨ 주요 변경사항: - 단일 docker-compose.yml로 통합 (로컬/시놀로지 환경 지원) - 시놀로지 볼륨 매핑 설정 (volume1: 이미지, volume3: 데이터) - 통합 배포 가이드 및 자동 배포 스크립트 추가 - 완전한 Memos 스타일 워크플로우 구현 🎯 새로운 기능: - 📝 메모 작성 (upload.html) - 이미지 업로드 지원 - 📥 수신함 (inbox.html) - 메모 편집 및 Todo/보드 변환 - ✅ Todo 목록 (todo-list.html) - 오늘 할 일 관리 - 📋 보드 (board.html) - 프로젝트 관리, 접기/펼치기, 이미지 지원 - 📚 아카이브 (archive.html) - 완료된 보드 보관 - 🔐 초기 설정 화면 - 관리자 계정 생성 🔧 기술적 개선: - 이미지 업로드/편집 완전 지원 - 반응형 디자인 및 모바일 최적화 - 보드 완료 후 자동 숨김 처리 - 메모 편집 시 제목 필드 제거 - 테스트 로그인 버튼 제거 (프로덕션 준비) - 과거 코드 정리 (TodoService, CalendarSyncService 등) 📦 배포 관련: - env.synology.example - 시놀로지 환경 설정 템플릿 - SYNOLOGY_DEPLOYMENT_GUIDE.md - 상세한 배포 가이드 - deploy-synology.sh - 원클릭 자동 배포 스크립트 - Nginx 정적 파일 서빙 및 이미지 프록시 설정 🗑️ 정리된 파일: - 사용하지 않는 HTML 페이지들 (dashboard, calendar, checklist 등) - 복잡한 통합 서비스들 (integrations 폴더) - 중복된 시놀로지 설정 파일들
214 lines
6.2 KiB
JavaScript
214 lines
6.2 KiB
JavaScript
/**
|
|
* API 통신 유틸리티
|
|
*/
|
|
|
|
// 환경에 따른 API URL 설정
|
|
const API_BASE_URL = window.location.hostname === 'localhost'
|
|
? 'http://localhost:9000/api' // 로컬 개발 환경
|
|
: `${window.location.protocol}//${window.location.hostname}:9000/api`; // 시놀로지 배포 환경
|
|
|
|
class ApiClient {
|
|
constructor() {
|
|
this.token = localStorage.getItem('authToken');
|
|
}
|
|
|
|
async request(endpoint, options = {}) {
|
|
const url = `${API_BASE_URL}${endpoint}`;
|
|
const config = {
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options.headers
|
|
},
|
|
...options
|
|
};
|
|
|
|
// 인증 토큰 추가
|
|
if (this.token) {
|
|
config.headers['Authorization'] = `Bearer ${this.token}`;
|
|
console.log('API 요청에 토큰 포함:', this.token.substring(0, 20) + '...');
|
|
} else {
|
|
console.warn('API 요청에 토큰이 없습니다!');
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(url, config);
|
|
|
|
if (!response.ok) {
|
|
if (response.status === 401) {
|
|
// 토큰 만료 시 로그아웃
|
|
console.error('인증 실패 - 토큰 제거 후 로그인 페이지로 이동');
|
|
// 토큰만 제거하고 페이지 리로드는 하지 않음
|
|
localStorage.removeItem('authToken');
|
|
localStorage.removeItem('currentUser');
|
|
// 무한 루프 방지: 이미 index.html이 아닌 경우만 리다이렉트
|
|
if (!window.location.pathname.endsWith('index.html') && window.location.pathname !== '/') {
|
|
window.location.href = 'index.html';
|
|
}
|
|
throw new Error('인증이 만료되었습니다. 다시 로그인해주세요.');
|
|
}
|
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
}
|
|
|
|
const contentType = response.headers.get('content-type');
|
|
if (contentType && contentType.includes('application/json')) {
|
|
return await response.json();
|
|
}
|
|
return await response.text();
|
|
} catch (error) {
|
|
console.error('API 요청 실패:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// GET 요청
|
|
async get(endpoint) {
|
|
// 토큰 재로드 (로그인 후 토큰이 업데이트된 경우)
|
|
this.token = localStorage.getItem('authToken');
|
|
return this.request(endpoint, { method: 'GET' });
|
|
}
|
|
|
|
// POST 요청
|
|
async post(endpoint, data) {
|
|
// 토큰 재로드
|
|
this.token = localStorage.getItem('authToken');
|
|
return this.request(endpoint, {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
// PUT 요청
|
|
async put(endpoint, data) {
|
|
// 토큰 재로드
|
|
this.token = localStorage.getItem('authToken');
|
|
return this.request(endpoint, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(data)
|
|
});
|
|
}
|
|
|
|
// DELETE 요청
|
|
async delete(endpoint) {
|
|
// 토큰 재로드
|
|
this.token = localStorage.getItem('authToken');
|
|
return this.request(endpoint, { method: 'DELETE' });
|
|
}
|
|
|
|
// 파일 업로드
|
|
async uploadFile(endpoint, formData) {
|
|
// 토큰 재로드
|
|
this.token = localStorage.getItem('authToken');
|
|
return this.request(endpoint, {
|
|
method: 'POST',
|
|
headers: {
|
|
// Content-Type을 설정하지 않음 (FormData가 자동으로 설정)
|
|
},
|
|
body: formData
|
|
});
|
|
}
|
|
|
|
// 토큰 설정
|
|
setToken(token) {
|
|
this.token = token;
|
|
localStorage.setItem('authToken', token);
|
|
}
|
|
|
|
// 로그아웃
|
|
logout() {
|
|
this.token = null;
|
|
localStorage.removeItem('authToken');
|
|
localStorage.removeItem('currentUser');
|
|
window.location.reload();
|
|
}
|
|
}
|
|
|
|
// 전역 API 클라이언트 인스턴스
|
|
const api = new ApiClient();
|
|
|
|
// 인증 관련 API
|
|
const AuthAPI = {
|
|
async login(username, password) {
|
|
const response = await api.post('/auth/login', {
|
|
username,
|
|
password
|
|
});
|
|
|
|
if (response.access_token) {
|
|
api.setToken(response.access_token);
|
|
// 사용자 정보가 있으면 저장, 없으면 기본값 사용
|
|
const user = response.user || { username: 'hyungi', full_name: 'Administrator' };
|
|
localStorage.setItem('currentUser', JSON.stringify(user));
|
|
}
|
|
|
|
return response;
|
|
},
|
|
|
|
async logout() {
|
|
try {
|
|
await api.post('/auth/logout');
|
|
} catch (error) {
|
|
console.error('로그아웃 API 호출 실패:', error);
|
|
} finally {
|
|
api.logout();
|
|
}
|
|
},
|
|
|
|
async getCurrentUser() {
|
|
return api.get('/auth/me');
|
|
}
|
|
};
|
|
|
|
// Todo 관련 API
|
|
const TodoAPI = {
|
|
async getTodos(status = null, category = null) {
|
|
let url = '/todos/';
|
|
const params = new URLSearchParams();
|
|
|
|
if (status && status !== 'all') params.append('status', status);
|
|
if (category && category !== 'all') params.append('category', category);
|
|
|
|
if (params.toString()) {
|
|
url += '?' + params.toString();
|
|
}
|
|
|
|
return api.get(url);
|
|
},
|
|
|
|
async createTodo(todoData) {
|
|
return api.post('/todos/', todoData);
|
|
},
|
|
|
|
async updateTodo(id, todoData) {
|
|
return api.put(`/todos/${id}/`, todoData);
|
|
},
|
|
|
|
async deleteTodo(id) {
|
|
return api.delete(`/todos/${id}/`);
|
|
},
|
|
|
|
async completeTodo(id) {
|
|
return api.put(`/todos/${id}/`, { status: 'completed' });
|
|
},
|
|
|
|
async getTodayTodos() {
|
|
return api.get('/calendar/today/');
|
|
},
|
|
|
|
async getTodoById(id) {
|
|
return api.get(`/todos/${id}/`);
|
|
},
|
|
|
|
async uploadImage(imageFile) {
|
|
const formData = new FormData();
|
|
formData.append('image', imageFile);
|
|
return api.uploadFile('/todos/upload-image', formData);
|
|
}
|
|
};
|
|
|
|
// 전역으로 사용 가능하도록 export
|
|
window.api = api;
|
|
window.AuthAPI = AuthAPI;
|
|
window.TodoAPI = TodoAPI;
|
|
|
|
|