🚀 배포용: PDF 뷰어 개선 및 서적별 UI 데본씽크 스타일 적용
✨ 주요 개선사항: - PDF API 500 에러 수정 (한글 파일명 UTF-8 인코딩 처리) - PDF 뷰어 기능 완전 구현 (PDF.js 통합, 네비게이션, 확대/축소) - 서적별 문서 그룹화 UI 데본씽크 스타일로 개선 - PDF Manager 페이지 서적별 보기 기능 추가 - Alpine.js 로드 순서 최적화로 JavaScript 에러 해결 🎨 UI/UX 개선: - 확장/축소 가능한 아코디언 스타일 서적 목록 - 간결하고 직관적인 데본씽크 스타일 인터페이스 - PDF 상태 표시 (HTML 연결, 서적 분류) - 반응형 디자인 및 부드러운 애니메이션 🔧 기술적 개선: - PDF.js 워커 설정 및 토큰 인증 처리 - 서적별 PDF 자동 그룹화 로직 - Alpine.js 컴포넌트 초기화 최적화
This commit is contained in:
50
backend/migrations/004_add_books_table.sql
Normal file
50
backend/migrations/004_add_books_table.sql
Normal file
@@ -0,0 +1,50 @@
|
||||
-- 서적 테이블 및 관계 추가
|
||||
-- 2025-08-22: 서적 그룹화 기능 추가
|
||||
|
||||
-- 서적 테이블 생성
|
||||
CREATE TABLE IF NOT EXISTS books (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(500) NOT NULL,
|
||||
author VARCHAR(200),
|
||||
publisher VARCHAR(200),
|
||||
isbn VARCHAR(20) UNIQUE,
|
||||
description TEXT,
|
||||
language VARCHAR(10) DEFAULT 'ko',
|
||||
total_pages INTEGER DEFAULT 0,
|
||||
cover_image_path VARCHAR(500),
|
||||
is_public BOOLEAN DEFAULT true,
|
||||
tags VARCHAR(1000),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_books_title ON books(title);
|
||||
CREATE INDEX IF NOT EXISTS idx_books_author ON books(author);
|
||||
CREATE INDEX IF NOT EXISTS idx_books_created_at ON books(created_at);
|
||||
|
||||
-- documents 테이블에 book_id 컬럼 추가
|
||||
ALTER TABLE documents ADD COLUMN IF NOT EXISTS book_id UUID;
|
||||
|
||||
-- 외래키 제약조건 추가
|
||||
ALTER TABLE documents ADD CONSTRAINT IF NOT EXISTS fk_documents_book_id
|
||||
FOREIGN KEY (book_id) REFERENCES books(id) ON DELETE SET NULL;
|
||||
|
||||
-- book_id 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_documents_book_id ON documents(book_id);
|
||||
|
||||
-- 업데이트 트리거 함수 생성 (updated_at 자동 업데이트)
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
-- books 테이블에 업데이트 트리거 추가
|
||||
DROP TRIGGER IF EXISTS update_books_updated_at ON books;
|
||||
CREATE TRIGGER update_books_updated_at
|
||||
BEFORE UPDATE ON books
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
12
backend/migrations/005_add_matched_pdf_id.sql
Normal file
12
backend/migrations/005_add_matched_pdf_id.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- 문서에 PDF 매칭 필드 추가
|
||||
-- Migration: 005_add_matched_pdf_id.sql
|
||||
|
||||
-- matched_pdf_id 컬럼 추가
|
||||
ALTER TABLE documents
|
||||
ADD COLUMN matched_pdf_id UUID REFERENCES documents(id);
|
||||
|
||||
-- 인덱스 추가 (성능 향상)
|
||||
CREATE INDEX idx_documents_matched_pdf_id ON documents(matched_pdf_id);
|
||||
|
||||
-- 코멘트 추가
|
||||
COMMENT ON COLUMN documents.matched_pdf_id IS '매칭된 PDF 문서 ID (HTML 문서에 연결된 원본 PDF)';
|
||||
9
backend/migrations/006_make_html_path_nullable.sql
Normal file
9
backend/migrations/006_make_html_path_nullable.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
-- HTML 경로를 nullable로 변경 (PDF만 업로드하는 경우 대응)
|
||||
-- Migration: 006_make_html_path_nullable.sql
|
||||
|
||||
-- html_path 컬럼을 nullable로 변경
|
||||
ALTER TABLE documents
|
||||
ALTER COLUMN html_path DROP NOT NULL;
|
||||
|
||||
-- 코멘트 업데이트
|
||||
COMMENT ON COLUMN documents.html_path IS 'HTML 파일 경로 (PDF만 업로드하는 경우 null 가능)';
|
||||
34
backend/migrations/007_add_document_links.sql
Normal file
34
backend/migrations/007_add_document_links.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- 문서 링크 테이블 생성
|
||||
CREATE TABLE document_links (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
source_document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
target_document_id UUID NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
|
||||
selected_text TEXT NOT NULL,
|
||||
start_offset INTEGER NOT NULL,
|
||||
end_offset INTEGER NOT NULL,
|
||||
link_text VARCHAR(500),
|
||||
description TEXT,
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX idx_document_links_source_document_id ON document_links(source_document_id);
|
||||
CREATE INDEX idx_document_links_target_document_id ON document_links(target_document_id);
|
||||
CREATE INDEX idx_document_links_created_by ON document_links(created_by);
|
||||
CREATE INDEX idx_document_links_start_offset ON document_links(start_offset);
|
||||
|
||||
-- 업데이트 트리거 생성
|
||||
CREATE OR REPLACE FUNCTION update_document_links_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_update_document_links_updated_at
|
||||
BEFORE UPDATE ON document_links
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_document_links_updated_at();
|
||||
24
backend/migrations/008_enhance_document_links.sql
Normal file
24
backend/migrations/008_enhance_document_links.sql
Normal file
@@ -0,0 +1,24 @@
|
||||
-- 문서 링크 테이블에 고급 기능을 위한 컬럼 추가
|
||||
|
||||
-- 도착점 텍스트 정보 컬럼 추가
|
||||
ALTER TABLE document_links
|
||||
ADD COLUMN target_text TEXT,
|
||||
ADD COLUMN target_start_offset INTEGER,
|
||||
ADD COLUMN target_end_offset INTEGER;
|
||||
|
||||
-- 링크 타입 컬럼 추가 (기본값: document)
|
||||
ALTER TABLE document_links
|
||||
ADD COLUMN link_type VARCHAR(20) DEFAULT 'document' NOT NULL;
|
||||
|
||||
-- 기존 데이터의 link_type을 'document'로 설정 (이미 기본값이지만 명시적으로)
|
||||
UPDATE document_links SET link_type = 'document' WHERE link_type IS NULL;
|
||||
|
||||
-- 인덱스 추가 (성능 향상)
|
||||
CREATE INDEX idx_document_links_link_type ON document_links(link_type);
|
||||
CREATE INDEX idx_document_links_target_offset ON document_links(target_document_id, target_start_offset, target_end_offset);
|
||||
|
||||
-- 코멘트 추가
|
||||
COMMENT ON COLUMN document_links.target_text IS '대상 문서에서 선택된 텍스트';
|
||||
COMMENT ON COLUMN document_links.target_start_offset IS '대상 문서에서 텍스트 시작 위치';
|
||||
COMMENT ON COLUMN document_links.target_end_offset IS '대상 문서에서 텍스트 끝 위치';
|
||||
COMMENT ON COLUMN document_links.link_type IS '링크 타입: document(전체 문서) 또는 text_fragment(특정 텍스트 부분)';
|
||||
81
backend/migrations/009_create_notes_system.sql
Normal file
81
backend/migrations/009_create_notes_system.sql
Normal file
@@ -0,0 +1,81 @@
|
||||
-- 노트 관리 시스템 생성
|
||||
-- 009_create_notes_system.sql
|
||||
|
||||
-- 노트 문서 테이블
|
||||
CREATE TABLE IF NOT EXISTS notes_documents (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(500) NOT NULL,
|
||||
content TEXT, -- 마크다운 내용
|
||||
html_content TEXT, -- 변환된 HTML 내용
|
||||
note_type VARCHAR(50) DEFAULT 'note', -- note, research, summary, idea 등
|
||||
tags TEXT[] DEFAULT '{}', -- 태그 배열
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
is_published BOOLEAN DEFAULT false, -- 공개 여부
|
||||
parent_note_id UUID REFERENCES notes_documents(id) ON DELETE SET NULL, -- 계층 구조
|
||||
sort_order INTEGER DEFAULT 0, -- 정렬 순서
|
||||
word_count INTEGER DEFAULT 0, -- 단어 수
|
||||
reading_time INTEGER DEFAULT 0, -- 예상 읽기 시간 (분)
|
||||
|
||||
-- 인덱스
|
||||
CONSTRAINT notes_documents_title_check CHECK (char_length(title) > 0)
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_created_by ON notes_documents(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_created_at ON notes_documents(created_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_note_type ON notes_documents(note_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_parent_note_id ON notes_documents(parent_note_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_tags ON notes_documents USING GIN(tags);
|
||||
CREATE INDEX IF NOT EXISTS idx_notes_documents_is_published ON notes_documents(is_published);
|
||||
|
||||
-- 업데이트 시간 자동 갱신 트리거
|
||||
CREATE OR REPLACE FUNCTION update_notes_documents_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_update_notes_documents_updated_at
|
||||
BEFORE UPDATE ON notes_documents
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_notes_documents_updated_at();
|
||||
|
||||
-- 기존 document_links 테이블에 노트 지원 추가
|
||||
-- (이미 존재하는 테이블이므로 ALTER 사용)
|
||||
DO $$
|
||||
BEGIN
|
||||
-- source_type, target_type 컬럼이 없다면 추가
|
||||
IF NOT EXISTS (
|
||||
SELECT 1 FROM information_schema.columns
|
||||
WHERE table_name = 'document_links' AND column_name = 'source_type'
|
||||
) THEN
|
||||
ALTER TABLE document_links
|
||||
ADD COLUMN source_type VARCHAR(20) DEFAULT 'document',
|
||||
ADD COLUMN target_type VARCHAR(20) DEFAULT 'document';
|
||||
|
||||
-- 기존 데이터는 모두 'document' 타입으로 설정
|
||||
UPDATE document_links SET source_type = 'document', target_type = 'document';
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
-- 노트 관련 링크를 위한 인덱스
|
||||
CREATE INDEX IF NOT EXISTS idx_document_links_source_type ON document_links(source_type);
|
||||
CREATE INDEX IF NOT EXISTS idx_document_links_target_type ON document_links(target_type);
|
||||
|
||||
-- 샘플 노트 타입 데이터
|
||||
INSERT INTO notes_documents (title, content, html_content, note_type, tags, created_by, is_published)
|
||||
VALUES
|
||||
('노트 시스템 사용법',
|
||||
'# 노트 시스템 사용법\n\n## 기본 기능\n- 마크다운으로 노트 작성\n- HTML로 자동 변환\n- 태그 기반 분류\n\n## 고급 기능\n- 서적과 링크 연결\n- 계층 구조 지원\n- 내보내기 기능',
|
||||
'<h1>노트 시스템 사용법</h1><h2>기본 기능</h2><ul><li>마크다운으로 노트 작성</li><li>HTML로 자동 변환</li><li>태그 기반 분류</li></ul><h2>고급 기능</h2><ul><li>서적과 링크 연결</li><li>계층 구조 지원</li><li>내보내기 기능</li></ul>',
|
||||
'guide',
|
||||
ARRAY['가이드', '사용법', '시스템'],
|
||||
'Administrator',
|
||||
true)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
COMMIT;
|
||||
25
backend/migrations/010_create_notebooks.sql
Normal file
25
backend/migrations/010_create_notebooks.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- 노트북 시스템 생성
|
||||
CREATE TABLE notebooks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
title VARCHAR(500) NOT NULL,
|
||||
description TEXT,
|
||||
color VARCHAR(7) DEFAULT '#3B82F6', -- 헥스 컬러 코드
|
||||
icon VARCHAR(50) DEFAULT 'book', -- FontAwesome 아이콘 이름
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW(),
|
||||
created_by VARCHAR(100) NOT NULL,
|
||||
is_active BOOLEAN DEFAULT true,
|
||||
sort_order INTEGER DEFAULT 0
|
||||
);
|
||||
|
||||
-- 노트북-노트 관계 테이블 (기존 notes_documents의 parent_note_id 대신 사용)
|
||||
ALTER TABLE notes_documents ADD COLUMN notebook_id UUID REFERENCES notebooks(id);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX idx_notebooks_created_by ON notebooks(created_by);
|
||||
CREATE INDEX idx_notebooks_created_at ON notebooks(created_at);
|
||||
CREATE INDEX idx_notes_notebook_id ON notes_documents(notebook_id);
|
||||
|
||||
-- 기본 노트북 생성 (기존 노트들을 위한)
|
||||
INSERT INTO notebooks (title, description, created_by, color, icon)
|
||||
VALUES ('기본 노트북', '분류되지 않은 노트들', 'admin@test.com', '#6B7280', 'sticky-note');
|
||||
48
backend/migrations/011_create_note_highlights_and_notes.sql
Normal file
48
backend/migrations/011_create_note_highlights_and_notes.sql
Normal file
@@ -0,0 +1,48 @@
|
||||
-- 노트용 하이라이트 테이블 생성
|
||||
CREATE TABLE note_highlights (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
note_id UUID NOT NULL REFERENCES notes_documents(id) ON DELETE CASCADE,
|
||||
start_offset INTEGER NOT NULL,
|
||||
end_offset INTEGER NOT NULL,
|
||||
selected_text TEXT NOT NULL,
|
||||
highlight_color VARCHAR(50) NOT NULL DEFAULT '#FFFF00',
|
||||
highlight_type VARCHAR(50) NOT NULL DEFAULT 'highlight',
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_by VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
-- 노트용 메모 테이블 생성
|
||||
CREATE TABLE note_notes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
note_id UUID NOT NULL REFERENCES notes_documents(id) ON DELETE CASCADE,
|
||||
highlight_id UUID REFERENCES note_highlights(id) ON DELETE CASCADE,
|
||||
content TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
created_by VARCHAR(100) NOT NULL
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX ix_note_highlights_note_id ON note_highlights (note_id);
|
||||
CREATE INDEX ix_note_highlights_created_by ON note_highlights (created_by);
|
||||
CREATE INDEX ix_note_notes_note_id ON note_notes (note_id);
|
||||
CREATE INDEX ix_note_notes_highlight_id ON note_notes (highlight_id);
|
||||
CREATE INDEX ix_note_notes_created_by ON note_notes (created_by);
|
||||
|
||||
-- updated_at 자동 업데이트 트리거
|
||||
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ language 'plpgsql';
|
||||
|
||||
CREATE TRIGGER update_note_highlights_updated_at
|
||||
BEFORE UPDATE ON note_highlights
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
CREATE TRIGGER update_note_notes_updated_at
|
||||
BEFORE UPDATE ON note_notes
|
||||
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
||||
75
backend/migrations/011_create_note_links.sql
Normal file
75
backend/migrations/011_create_note_links.sql
Normal file
@@ -0,0 +1,75 @@
|
||||
-- 노트 링크 테이블 생성
|
||||
-- 노트 문서 간 또는 노트-문서 간 링크를 관리하는 테이블
|
||||
|
||||
CREATE TABLE IF NOT EXISTS note_links (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
|
||||
-- 링크 출발점 (노트 또는 문서 중 하나)
|
||||
source_note_id UUID REFERENCES notes_documents(id) ON DELETE CASCADE,
|
||||
source_document_id UUID REFERENCES documents(id) ON DELETE CASCADE,
|
||||
|
||||
-- 링크 도착점 (노트 또는 문서 중 하나)
|
||||
target_note_id UUID REFERENCES notes_documents(id) ON DELETE CASCADE,
|
||||
target_document_id UUID REFERENCES documents(id) ON DELETE CASCADE,
|
||||
|
||||
-- 출발점 텍스트 정보
|
||||
selected_text TEXT NOT NULL,
|
||||
start_offset INTEGER NOT NULL,
|
||||
end_offset INTEGER NOT NULL,
|
||||
|
||||
-- 도착점 텍스트 정보 (선택사항)
|
||||
target_text TEXT,
|
||||
target_start_offset INTEGER,
|
||||
target_end_offset INTEGER,
|
||||
|
||||
-- 링크 메타데이터
|
||||
link_text VARCHAR(500),
|
||||
description TEXT,
|
||||
link_type VARCHAR(20) DEFAULT 'note' NOT NULL,
|
||||
|
||||
-- 생성자 및 시간 정보
|
||||
created_by UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
|
||||
updated_at TIMESTAMP WITH TIME ZONE,
|
||||
|
||||
-- 제약 조건
|
||||
CONSTRAINT note_links_source_check CHECK (
|
||||
(source_note_id IS NOT NULL AND source_document_id IS NULL) OR
|
||||
(source_note_id IS NULL AND source_document_id IS NOT NULL)
|
||||
),
|
||||
CONSTRAINT note_links_target_check CHECK (
|
||||
(target_note_id IS NOT NULL AND target_document_id IS NULL) OR
|
||||
(target_note_id IS NULL AND target_document_id IS NOT NULL)
|
||||
)
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_source_note ON note_links(source_note_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_source_document ON note_links(source_document_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_target_note ON note_links(target_note_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_target_document ON note_links(target_document_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_created_by ON note_links(created_by);
|
||||
CREATE INDEX IF NOT EXISTS idx_note_links_created_at ON note_links(created_at);
|
||||
|
||||
-- updated_at 자동 업데이트 트리거
|
||||
CREATE OR REPLACE FUNCTION update_note_links_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER trigger_note_links_updated_at
|
||||
BEFORE UPDATE ON note_links
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_note_links_updated_at();
|
||||
|
||||
-- 코멘트 추가
|
||||
COMMENT ON TABLE note_links IS '노트 문서 간 링크 관리 테이블';
|
||||
COMMENT ON COLUMN note_links.source_note_id IS '출발점 노트 ID (노트에서 시작하는 링크)';
|
||||
COMMENT ON COLUMN note_links.source_document_id IS '출발점 문서 ID (문서에서 시작하는 링크)';
|
||||
COMMENT ON COLUMN note_links.target_note_id IS '도착점 노트 ID';
|
||||
COMMENT ON COLUMN note_links.target_document_id IS '도착점 문서 ID';
|
||||
COMMENT ON COLUMN note_links.link_type IS '링크 타입: note, document, text_fragment';
|
||||
|
||||
Reference in New Issue
Block a user