<repo_name>AlaricZeng/AlaricLightStory<file_sep>/constants.py
# Uploaded image path
UPLOAD_IMG_FOLDER = 'static/images/'
UPLOAD_ZOOM_IMG_FOLDER = 'static/images/thumbnails'
UPLOAD_STORY_FOLDER = 'static/stories/'
ALLOWED_IMG_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
ALLOWED_STORY_EXTENSIONS = set(['md'])
MARKDOWN_EXTENSION = '.md'

#Mysql config
MYSQL_USER = 'root'
MYSQL_PASSWORD = '<PASSWORD>'
MYSQL_DB = 'AlaricLightStory'
MYSQL_HOST = 'localhost'

#Admin logic
ADMIN_USER = 'AlaricZeng'
ADMIN_PASSWORD ='<PASSWORD>'

#Display picture
DISPLAY_PICTURE = "1"
NOT_DISPLAY_PICTURE = "0"<file_sep>/migrationScript/create_stories_table.sql
CREATE TABLE IF NOT EXISTS stories (
	id INT AUTO_INCREMENT,
	title VARCHAR(255) NOT NULL,
	created_at DATETIME,
	PRIMARY KEY(id)
);<file_sep>/migrationScript/create_photos_table.sql
CREATE TABLE IF NOT EXISTS photos (
	id INT AUTO_INCREMENT,
	name VARCHAR(255) NOT NULL,
	tag VARCHAR(50),
	linked_page_index VARCHAR(255),
	uploaded_at DATETIME,
	created_at DATETIME,
	iso VARCHAR(20),
	aperture VARCHAR(20),
	shutter_speed VARCHAR(20),
	display BOOLEAN,
	PRIMARY KEY(id)
);<file_sep>/static/stories/Olympic National Park Tour.md
# This is the story

```
Pass a code here
Code
```

* Item1
* Item2
* Item3

`Test`

![](static/images/DSC01482-M.jpg)<file_sep>/static/js/admin-component.js
new Vue({
	el: '#image-management-container',
	data: {
		select: null,
		storyTitles: ['None'],
		stories: {},
		imageTableHeaders: [
			{
				text: 'Pictures',
				align: 'left',
				sortable: false,
				value: 'url'
			},
			{
				text: 'Linked Story',
				align: 'left',
				value: 'story'
			},
			{
				text: 'Display?',
				align: 'left',
				value: 'display'
			},
			{ 
				text: 'Actions', 
				value: 'url', 
				sortable: false 
			}
		],
		imageInfo: [],
		editDialog:false,
		editedIndex: -1,
	    editedItem: {
	    	id: '',
	     	url: '',
	     	story: '',
	     	display: false
	    },
	    defaultItem: {
	    	id: '',
	    	url: '',
	      	story: '',
	      	display: false
	    },
	},
	mounted: function() {
		var that = this;

		$.get( "http://127.0.0.1:5000/admin/get_story_list")
			.done(function(data) {
				data.forEach(function(value, index) {
					that.stories[value[0]] = value[1]
					that.storyTitles.push(that.generateStoryTitle(value[0], value[1]));
				});
			});

		$.get( "http://127.0.0.1:5000/admin/get_image_list")
			.done(function(data) {
				data.forEach(function(value, index) {
					that.imageInfo.push({
						id: value[0],
						url: '/static/images/thumbnails/' + value[1],
						story: that.stories[value[2]] ? that.generateStoryTitle(value[2], that.stories[value[2]]) : 'none',
						display: value[3]
					});
				});
			});
	},
	methods: {
		submit: function() {
			$('#upload-image-form').submit();
		},

		editItem: function(item) {
      		this.editedIndex = this.imageInfo.indexOf(item);
      		this.editedItem = Object.assign({}, item);
      		this.editDialog = true;
      	},

      	deleteItem: function(item) {
      		const index = this.imageInfo.indexOf(item);
      		var confirmed = confirm('Are you sure you want to delete this item?');
      		var that = this;
      		if (confirmed) {
      			$.post( "http://127.0.0.1:5000/admin/delete_image", {id: item.id})
      			.done(function(data) {
					that.imageInfo.splice(index, 1)
				});
      		}
    	},

      	save: function() {
	 		if (this.editedIndex > -1) {
	 			var that = this;
	       		$.post( "http://127.0.0.1:5000/admin/edit_image_info", 
	       			{id: that.editedItem.id, storyTitle: that.editedItem.story, display: that.editedItem.display})
	       		.done(function(data) {
					Object.assign(that.imageInfo[that.editedIndex], that.editedItem);
				});
	      	} else {
	        	this.imageInfo.push(this.editedItem);
	      	}
	      	this.close();
	    },

	    close: function() {
      		this.editDialog = false;
      		setTimeout(() => {
        		this.editedItem = Object.assign({}, this.defaultItem);
        		this.editedIndex = -1;
      		}, 300);
    	},

    	generateStoryTitle: function(storyIndex, storyTitle) {
    		return storyIndex + ' - ' + storyTitle
    	}
	}
});


new Vue({
	el: '#story-management-container',
	data: {
		storyTableHeaders: [
			{
				text: 'Id',
				align: 'left',
				value: 'id'
			},
			{
				text: 'Title',
				align: 'left',
				value: 'title'
			},
			{ 
				text: 'Actions', 
				value: 'id', 
				sortable: false 
			}
		],
		storyInfo: [],
		editDialog:false,
		editedIndex: -1,
	    editedItem: {
	    	id: '',
	     	title: ''
	    },
	    defaultItem: {
	    	id: '',
	    	title: ''
	    },
	    rules: {
        	required: value => !!value || 'Required.'
        },
        uploadedStoryFile: '',
	    uploadedStoryTitle: '',
	    editedStoryFile: ''
	},
	mounted: function() {
		var that = this;

		$.get( "http://127.0.0.1:5000/admin/get_story_list")
			.done(function(data) {
				data.forEach(function(value, index) {
					that.storyInfo.push({
						id: value[0],
						title: value[1]
					})
				});
			});
	},
	computed: {
		uploadNewStoryFormIsValid: function() {
			return this.uploadedStoryTitle &&
					this.uploadedStoryFile;
		},
		uploadExistStoryFormIsValid: function() {
			return this.editedItem.title &&
					this.editedStoryFile;
		},
	},
	methods: {
		updateNewStoryFile: function(event) {
			this.uploadedStoryFile = event.target.files;
		},

		updateExistStoryFile: function(event) {
			this.editedStoryFile = event.target.files;
		},

		submit: function() {
			$('#upload-story-form').submit();
		},

		editItem: function(item) {
      		this.editedIndex = this.storyInfo.indexOf(item);
      		this.editedItem = Object.assign({}, item);
      		this.editDialog = true;
      	},

      	deleteItem: function(item) {
      		const index = this.storyInfo.indexOf(item);
      		var confirmed = confirm('Are you sure you want to delete this story?');
      		var that = this;
      		if (confirmed) {
      			$.post( "http://127.0.0.1:5000/admin/delete_story", {id: item.id})
      			.done(function(data) {
					that.storyInfo.splice(index, 1)
				});
      		}
    	},

      	save: function() {
	 		if (this.editedIndex > -1) {
	 			$('#edit-story-form').submit();
				Object.assign(that.storyInfo[that.editedIndex], that.editedItem);
	      	} else {
	        	this.storyInfo.push(this.editedItem);
	      	}
	      	this.close();
	    },

	    downloadItem: function(item) {
      		$.post( "http://127.0.0.1:5000/admin/download_story", {title: item.title})
      		.done(function(data) {
				window.location.href = data;
			});
    	},

	    close: function() {
      		this.editDialog = false;
      		setTimeout(() => {
      			$('#update-exist-file').val('');
      			this.editedStoryFile = '';
        		this.editedItem = Object.assign({}, this.defaultItem);
        		this.editedIndex = -1;
      		}, 300);
    	}
	}
});<file_sep>/AlaricLightStory.wsgi
import sys
sys.path.insert(0, '/var/www/html/AlaricLightStory')

from AlaricLightStory import app as application<file_sep>/try.py
import markdown
from flask import Flask
from flask import render_template
from flask import Markup

app = Flask(__name__)
@app.route('/')

def index():
  content = """
Chapter
=======

Section
-------

* Item 1
* Item 2
"""
  content = Markup(markdown.markdown(content))
  return render_template('index.html', **locals())

app.run(debug=True)<file_sep>/AlaricLightStory.py
from __future__ import print_function
import sys

import os
from flask import Flask, render_template, request, redirect, url_for, send_from_directory, jsonify, Markup, send_file
from flask_mysqldb import MySQL
from datetime import datetime
from werkzeug import secure_filename
import markdown2

from constants import * 

app = Flask(__name__)
app.config['MYSQL_USER'] = 'root'
app.config['MYSQL_PASSWORD'] = '<PASSWORD>'
app.config['MYSQL_DB'] = 'AlaricLightStory'
app.config['MYSQL_HOST'] = 'localhost'

app.config['UPLOAD_IMG_FOLDER'] = UPLOAD_IMG_FOLDER
app.config['UPLOAD_ZOOM_IMG_FOLDER'] = UPLOAD_ZOOM_IMG_FOLDER
app.config['UPLOAD_STORY_FOLDER'] = UPLOAD_STORY_FOLDER
app.config['ALLOWED_IMG_EXTENSIONS'] = ALLOWED_IMG_EXTENSIONS
app.config['ALLOWED_STORY_EXTENSIONS'] = ALLOWED_STORY_EXTENSIONS

mysql = MySQL(app)

def allowed_img_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in app.config['ALLOWED_IMG_EXTENSIONS']

def allowed_story_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in app.config['ALLOWED_STORY_EXTENSIONS']

@app.route('/')
def first_page():
	cur = mysql.connection.cursor()
	
	fetch_all_images_query = "SELECT name, tag, linked_page_index FROM photos WHERE display=True"
	cur.execute(fetch_all_images_query)
	all_images = cur.fetchall()
	
	fetch_all_stories_query = "SELECT id, title, created_at FROM stories"
	cur.execute(fetch_all_stories_query)
	all_stories = cur.fetchall()
	cur.close()

	return render_template('index.html', images = all_images, stories = all_stories)

@app.route('/stories')
def get_story_content():
	story_id = request.args.get("story_id")
	cur = mysql.connection.cursor()
	fetch_story_title_query = "SELECT title FROM stories WHERE id =" + story_id
	cur.execute(fetch_story_title_query)
	story_title = cur.fetchone()[0]

	fetch_next_story_title_query = "SELECT id, title FROM stories WHERE id >" + story_id + " ORDER BY id"
	cur.execute(fetch_next_story_title_query)
	next_story = cur.fetchone()
	if next_story:
		next_story_id = next_story[0]
		next_story_title = next_story[1]
	else:
		fetch_first_story_title_query = "SELECT id, title FROM stories ORDER BY id"
		cur.execute(fetch_first_story_title_query)
		next_story = cur.fetchone()
		next_story_id = next_story[0]
		next_story_title = next_story[1]
	cur.close()

	with open(UPLOAD_STORY_FOLDER + story_title + ".md", "r") as f:
		content = f.read()

	content = Markup(markdown2.markdown(content, extras=["fenced-code-blocks"]))
	return render_template("story.html", content = content, next_story_id = next_story_id, next_story_title = next_story_title)

@app.route('/display')
def display_photos():
	cur = mysql.connection.cursor()
	query = "SELECT name, tag FROM photos"
	cur.execute(query)
	data = cur.fetchall()
	cur.close()
	return jsonify(data)

@app.route('/login')
def login_page():
	return render_template('login.html')

@app.route('/admin', methods = ['POST'])
def admin_default():
	username = request.form.get('username')
	password = request.form.get('<PASSWORD>')
	is_logged_in = request.args.get('is_logged_in')
	if is_logged_in:
		return render_template('admin.html')
	elif username == ADMIN_USER and password == <PASSWORD>:
		return render_template('admin.html')
	else:
		return render_template('login.html')

@app.route('/admin/get_story_list')
def get_story_list():
	cur = mysql.connection.cursor()
	query = "SELECT id, title FROM stories"
	cur.execute(query)
	data = cur.fetchall()
	cur.close()
	return jsonify(data)

@app.route('/admin/get_image_list')
def get_image_list():
	cur = mysql.connection.cursor()
	query = "SELECT id, name, linked_page_index, display FROM photos"
	cur.execute(query)
	data = cur.fetchall()
	cur.close()
	return jsonify(data)

@app.route('/admin/edit_image_info', methods = ['POST'])
def edit_image_info():
	id = request.form.get('id')
	storyTitle = request.form.get('storyTitle')
	display = request.form.get('display')
	if (storyTitle == 'None'):
		linked_page_index = 'NULL'
	else:
		linked_page_index = storyTitle.split("-")[0].strip()
	cur = mysql.connection.cursor()
	query = "UPDATE photos SET linked_page_index = " + linked_page_index + ", display = " + display + " WHERE id = " + id;
	cur.execute(query)
	mysql.connection.commit()
	cur.close()
	return jsonify(id)

@app.route('/admin/delete_image', methods = ['POST'])
def delete_image():
	id = request.form.get('id')

	cur = mysql.connection.cursor()
	query = "DELETE FROM photos WHERE id = " + id
	cur.execute(query)
	mysql.connection.commit()
	cur.close()
	return jsonify(id)

@app.route('/admin/upload_image', methods = ['POST'])
def upload_image():
	file = request.files['file']
	tag = request.form.get('tag')
	zoom_file = request.files['zoom_file']
	linked_page = request.form.get('linked_page')
	if (linked_page == 'None'):
		linked_page_index = None
	else:
		linked_page_index = linked_page.split("-")[0].strip()
	if request.form.get('display') == "display":
		display = DISPLAY_PICTURE
	else:
		display = NOT_DISPLAY_PICTURE
	if file and allowed_img_file(file.filename) and zoom_file and allowed_img_file(zoom_file.filename):
		filename = secure_filename(file.filename)
		zoom_filename = secure_filename(zoom_file.filename)
		file.save(os.path.join(app.config['UPLOAD_IMG_FOLDER'], filename))
		zoom_file.save(os.path.join(app.config['UPLOAD_ZOOM_IMG_FOLDER'], zoom_filename))
		cur = mysql.connection.cursor()
		query = "INSERT INTO photos (name, tag, linked_page_index, created_at, display) VALUES('" + filename + "', '" + tag + "', '" + linked_page_index + "', '" + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "', '" + display + "')"
		cur.execute(query)
		mysql.connection.commit()
		cur.close()
	return redirect(url_for('admin_default', is_logged_in = True), code=307)

@app.route('/admin/upload_story', methods = ['POST'])
def upload_story():
	file = request.files['file']
	title = request.form.get('title')
	if file and allowed_story_file(file.filename):
		filename = title + MARKDOWN_EXTENSION
		file.save(os.path.join(app.config['UPLOAD_STORY_FOLDER'], filename))
		cur = mysql.connection.cursor()
		query = "INSERT INTO stories (title, created_at) VALUES('" + title + "', '" + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + "')"
		cur.execute(query)
		mysql.connection.commit()
		cur.close()
	return redirect(url_for('admin_default', is_logged_in = True), code=307)

@app.route('/admin/edit_story_info', methods = ['POST'])
def edit_story_info():
	id = request.form.get('id')
	file = request.files['file']
	title = request.form.get('title')
	if file and allowed_story_file(file.filename):
		filename = title + MARKDOWN_EXTENSION
		file.save(os.path.join(app.config['UPLOAD_STORY_FOLDER'], filename))
		cur = mysql.connection.cursor()
		query = "UPDATE stories SET title = '" + title + "' WHERE id = " + id;
		print(query, file=sys.stderr)
		cur.execute(query)
		mysql.connection.commit()
		cur.close()
	return redirect(url_for('admin_default', is_logged_in = True), code=307)

@app.route('/admin/delete_story', methods = ['POST'])
def delete_story():
	id = request.form.get('id')

	cur = mysql.connection.cursor()
	query = "UPDATE photos SET linked_page_index = NULL WHERE linked_page_index = " + id;
	cur.execute(query)
	query = "DELETE FROM stories WHERE id = " + id
	cur.execute(query)
	mysql.connection.commit()
	cur.close()
	return jsonify(id)

@app.route('/admin/download_story', methods = ['POST'])
def download_story():
	title = request.form.get('title')
	filename = title + MARKDOWN_EXTENSION

	file = os.path.join(app.config['UPLOAD_STORY_FOLDER'], filename)
	return file

if __name__ == '__main__':
  app.run()
