diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..f86113d --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 0000000..105ce2d --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..d56657a --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..1fbe102 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/wixon_blog.iml b/.idea/wixon_blog.iml new file mode 100644 index 0000000..b594273 --- /dev/null +++ b/.idea/wixon_blog.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app.py b/app.py new file mode 100644 index 0000000..3324a31 --- /dev/null +++ b/app.py @@ -0,0 +1,159 @@ +from flask import Flask, render_template, request, redirect, url_for, flash, session +import pymysql +from flask import session, g, jsonify +import bcrypt +from bs4 import BeautifulSoup +from werkzeug.utils import secure_filename +import os +import uuid + +UPLOAD_FOLDER = 'static/upload/img' # 경로를 Flask 앱 루트 기준으로 수정 +ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'} + +app = Flask(__name__) +app.secret_key = 'your secret key' +app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER + + +@app.before_request +def load_user(): + if 'user_info' in session: + g.is_login = True + g.user_info = session['user_info'] + + +def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + +@app.route('/upload_image', methods=['POST']) +def upload_image(): + if 'file' not in request.files: + return jsonify(success=False, message='No file part'), 400 + + file = request.files['file'] + + if file.filename == '': + return jsonify(success=False, message='No selected file'), 400 + + if file and allowed_file(file.filename): + # Secure the filename and keep its extension + filename = secure_filename(file.filename) + ext = filename.rsplit('.', 1)[1].lower() + + # Generate a random filename using uuid4 + random_filename = f'{uuid.uuid4().hex}.{ext}' + + file_path = os.path.join(app.config['UPLOAD_FOLDER'], random_filename) + + # Create directories if not exist + os.makedirs(os.path.dirname(file_path), exist_ok=True) + + file.save(file_path) + + # Generate the URL of the saved image file + file_url = url_for('static', filename='upload/img/' + random_filename) + + return jsonify(success=True, fileUrl=file_url), 200 + + return jsonify(success=False, message='File not allowed'), 400 + + +# MySQL 데이터베이스 연결 설정 +def connnect_db(): + return pymysql.connect( + host='wxnasso.synology.me', + user='wixon5', + password='Wixon2022@!', + database='wixon', + charset="utf8mb4", + cursorclass=pymysql.cursors.DictCursor, # DictCursor를 사용하여 딕셔너리 형태로 결과를 반환 + init_command='SET SQL_SAFE_UPDATES = 0;', + ) + + +def sql_execute(q, d, is_data=False, is_last_id=False): + data = None + last_id = None + with connnect_db().cursor(pymysql.cursors.DictCursor) as cursor: + try: + res = cursor.execute(q, d) + data = cursor.fetchall() if is_data else None + last_id = cursor.lastrowid if cursor.lastrowid != 0 else None + cursor.connection.commit() + except Exception as e: + print(e) + cursor.connection.rollback() + res = False + + return (res, last_id if is_last_id else data) if is_data or is_last_id else res + + +@app.route('/') +def index(): + # MySQL에서 모든 블로그 포스트를 가져옵니다. + query = "SELECT `title`, `thumbnail_img`, `contents` FROM `blog` WHERE `use_yn` = 'Y' ORDER BY `add_date` DESC;" + r, posts = sql_execute(query, (), is_data=True) + + # `index.html` 템플릿으로 데이터를 전달합니다. + return render_template('index.html', posts=posts) + + +@app.route('/login', methods=['GET', 'POST']) +def login(): + if request.method == 'POST': + username = request.form.get('username') + password = request.form.get('password').encode('utf-8') + + # DB에서 사용자 정보 가져오기 + res, user = sql_execute("SELECT * FROM member WHERE mb_id=%s", (username,), is_data=True) + if res and user and bcrypt.checkpw(password, user[0]['mb_passwd'].encode('utf-8')): + session['username'] = username + session['user_info'] = user[0] + return redirect(url_for('index')) + else: + flash('Username or password is incorrect') + return render_template('login.html') + + +@app.route('/logout') +def logout(): + session.clear() + g.is_login = False + g.user_info = None + + return redirect(url_for('index')) + + +@app.route('/write', methods=['GET', 'POST']) +def write(): + if request.method == 'POST': + title = request.form['title'] + category = request.form['category'] + contents = request.form['contents'] + + # 본문에서 첫 번째 이미지 추출 + soup = BeautifulSoup(contents, 'html.parser') + first_image = soup.find('img') + thumbnail_img = first_image['src'] if first_image else None + + # SQL 쿼리와 삽입할 데이터 설정 + query = "INSERT INTO blog (title, category, contents, thumbnail_img, use_yn) VALUES (%s, %s, %s, %s, 'Y')" + data = (title, category, contents, thumbnail_img) + + # 새로운 글 저장 + result, last_id = sql_execute(query, data, is_last_id=True) + + if result: + # 저장 성공시, 인덱스 페이지로 리다이렉트 + return redirect(url_for('index')) + else: + # 저장 실패시, 에러 메시지 반환 + return "Failed to write post", 500 + else: + return render_template('write.html') + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=8899) diff --git a/static/upload/img/d6dffe69d6f84a709962b653e7f6eb16.png b/static/upload/img/d6dffe69d6f84a709962b653e7f6eb16.png new file mode 100644 index 0000000..2e9266a Binary files /dev/null and b/static/upload/img/d6dffe69d6f84a709962b653e7f6eb16.png differ diff --git a/static/upload/img/e0cb20c7b5a34b46b117e06f94963996.png b/static/upload/img/e0cb20c7b5a34b46b117e06f94963996.png new file mode 100644 index 0000000..e73dfe2 Binary files /dev/null and b/static/upload/img/e0cb20c7b5a34b46b117e06f94963996.png differ diff --git a/static/upload/img/logo.png b/static/upload/img/logo.png new file mode 100644 index 0000000..e73dfe2 Binary files /dev/null and b/static/upload/img/logo.png differ diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..c3f236c --- /dev/null +++ b/templates/base.html @@ -0,0 +1,75 @@ + + + + Blog + + + + + + + + + + + + + + + + + + +{% block content %}{% endblock %} + + + + + diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..f35ec87 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+ {% for post in posts %} +
+
+ {{ post.title }} +
+
+

{{ post.title }}

+ +

{{ post.contents | striptags | truncate(100) }}

+ Read more +
+
+ {% endfor %} +
+
+ + +{% endblock %} diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 0000000..7fe4b45 --- /dev/null +++ b/templates/login.html @@ -0,0 +1,32 @@ +{% extends 'base.html' %} + +{% block content %} +
+

윅슨 블로그 로그인

+ {% with messages = get_flashed_messages() %} + {% if messages %} +
+ + {{ messages[0] }} +
+ {% endif %} + {% endwith %} +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+{% endblock %} diff --git a/templates/write.html b/templates/write.html new file mode 100644 index 0000000..327cfa2 --- /dev/null +++ b/templates/write.html @@ -0,0 +1,65 @@ +{% extends 'base.html' %} + +{% block content %} +
+

포스트 작성

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+ + +{% endblock %}