diff --git a/README.md b/README.md deleted file mode 100644 index 591ae0ed..00000000 --- a/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Proman - -It is not mandatory but feel free to use the skeleton code which you find in this repo. Also, we provided some design materials, like an example layout; if you like, you can freely use it. diff --git a/data/boards.csv b/data/boards.csv index b8d2ca77..47e3e2ee 100644 --- a/data/boards.csv +++ b/data/boards.csv @@ -1,3 +1,3 @@ id,title 1,"Board 1" -2,"Board 2" +2,"Board 2" \ No newline at end of file diff --git a/data/cards.csv b/data/cards.csv deleted file mode 100644 index 02d8e81f..00000000 --- a/data/cards.csv +++ /dev/null @@ -1,13 +0,0 @@ -id,board_id,title,status_id,order -1,1,"new card 1",0,0 -2,1,"new card 2",0,1 -3,1,"in progress card",1,0 -4,1,"planning",2,0 -5,1,"done card 1",3,0 -6,1,"done card 1",3,1 -7,2,"new card 1",0,0 -8,2,"new card 2",0,1 -9,2,"in progress card",1,0 -10,2,"planning",2,0 -11,2,"done card 1",3,0 -12,2,"done card 1",3,1 diff --git a/data/statuses.csv b/data/statuses.csv deleted file mode 100644 index d027decd..00000000 --- a/data/statuses.csv +++ /dev/null @@ -1,5 +0,0 @@ -id,title -0,new -1,"in progress" -2,testing -3,done diff --git a/data_manager/__init__.py b/data_manager/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/data_manager/data_manager_operations.py b/data_manager/data_manager_operations.py new file mode 100644 index 00000000..8986e7ee --- /dev/null +++ b/data_manager/data_manager_operations.py @@ -0,0 +1,58 @@ +import db_connection +import bcrypt + + +def register(data): + credentials = (data["username"], data["email"], hash_password(data['password'])) + sql_query = """INSERT INTO credentials(user_login, user_email, user_password) + VALUES (%s, %s, %s);""" + db_connection.sql_data(sql_query, "write", credentials) + + +def verify_login(data): + sql_query = """SELECT user_login, user_password FROM credentials WHERE user_login = %s""" + login_and_password = db_connection.sql_data(sql_query, "read", (data["username"],)) + if login_and_password: + if verify_password(data["password"], login_and_password[0]["user_password"]): + return 0 + return 1 + return 1 + + + sql_query = """SELECT user_id FROM credentials WHERE user_login = %s""" + user_id = db_connection.sql_data(sql_query, "read", (username, )) + return user_id + + +def hash_password(plain_text_password): + # By using bcrypt, the salt is saved into the hash itself + hashed_bytes = bcrypt.hashpw(plain_text_password.encode('utf-8'), bcrypt.gensalt()) + return hashed_bytes.decode('utf-8') + + +def verify_password(plain_text_password, hashed_password): + hashed_bytes_password = hashed_password.encode('utf-8') + return bcrypt.checkpw(plain_text_password.encode('utf-8'), hashed_bytes_password) + + +def verify_credentials(data): + if verify_username(data["username"]): + return "username taken" + if verify_email(data["email"]): + return "email taken" + + +def verify_username(data): + sql_query = """SELECT user_id FROM credentials WHERE user_login = %s""" + login_check = db_connection.sql_data(sql_query, "read", (data,)) + if login_check: + return "login taken" + return None + + +def verify_email(data): + sql_query = """SELECT user_id FROM credentials WHERE user_email = %s""" + email_check = db_connection.sql_data(sql_query, "read", (data,)) + if email_check: + return "email taken" + return None \ No newline at end of file diff --git a/data_manager/data_manager_user_operations.py b/data_manager/data_manager_user_operations.py new file mode 100644 index 00000000..4caa7135 --- /dev/null +++ b/data_manager/data_manager_user_operations.py @@ -0,0 +1,64 @@ +import db_connection +import bcrypt + + +def register(data): + credentials = (data["username"], data["email"], hash_password(data['password'])) + sql_query = """INSERT INTO credentials(user_login, user_email, user_password) + VALUES (%s, %s, %s);""" + db_connection.sql_data(sql_query, "write", credentials) + + +def verify_login(data): + sql_query = """SELECT user_login, user_password FROM credentials WHERE user_login = %s""" + login_and_password = db_connection.sql_data(sql_query, "read", (data["username"],)) + if login_and_password: + if verify_password(data["password"], login_and_password[0]["user_password"]): + return 0 + return 1 + return 1 + + + sql_query = """SELECT user_id FROM credentials WHERE user_login = %s""" + user_id = db_connection.sql_data(sql_query, "read", (username, )) + return user_id + + +def hash_password(plain_text_password): + # By using bcrypt, the salt is saved into the hash itself + hashed_bytes = bcrypt.hashpw(plain_text_password.encode('utf-8'), bcrypt.gensalt()) + return hashed_bytes.decode('utf-8') + + +def verify_password(plain_text_password, hashed_password): + hashed_bytes_password = hashed_password.encode('utf-8') + return bcrypt.checkpw(plain_text_password.encode('utf-8'), hashed_bytes_password) + + +def verify_credentials(data): + if verify_username(data["username"]): + return "username taken" + if verify_email(data["email"]): + return "email taken" + + +def verify_username(data): + sql_query = """SELECT user_id FROM credentials WHERE user_login = %s""" + login_check = db_connection.sql_data(sql_query, "read", (data,)) + if login_check: + return "login taken" + return None + + +def verify_email(data): + sql_query = """SELECT user_id FROM credentials WHERE user_email = %s""" + email_check = db_connection.sql_data(sql_query, "read", (data,)) + if email_check: + return "email taken" + return None + + +def get_email(username=None): + sql_query = """SELECT user_email FROM credentials WHERE user_login = %s""" + info = db_connection.sql_data(sql_query, "read", (username, )) + return info \ No newline at end of file diff --git a/db_connection.py b/db_connection.py new file mode 100644 index 00000000..7f4a01b1 --- /dev/null +++ b/db_connection.py @@ -0,0 +1,46 @@ +import psycopg2 +import psycopg2.extras + + +def sql_data(sql_query, operation_type, data=None): + try: + user_name = "flupers" + password = "123" + host = "localhost" + database_name = "db_proman" + + connect_str = "postgresql://{user_name}:{password}@{host}/{database_name}".format( + user_name=user_name, + password=password, + host=host, + database_name=database_name + ) + print("Connection string: " + connect_str) + + connection = psycopg2.connect(connect_str) + + connection.autocommit = True + + cursor = connection.cursor() + + if operation_type == "read": + cursor = connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor) + cursor.execute(sql_query, data) + data = cursor.fetchall() + return data + + elif operation_type == "write": + cursor.execute(sql_query, data) + + else: + cursor.execute(sql_query, data) + + cursor.close() + + except psycopg2.DatabaseError as exception: + print(exception) + + finally: + # checks first whether the connection has been created successfully + if 'connection' in locals(): + connection.close() \ No newline at end of file diff --git a/main.py b/main.py index 9e25a1ac..3e03237f 100644 --- a/main.py +++ b/main.py @@ -1,24 +1,44 @@ -from flask import Flask, render_template, url_for +from flask import Flask, render_template, request, redirect, session, escape, url_for +import flask_login +from data_manager import data_manager_user_operations from util import json_response - import data_handler app = Flask(__name__) +app.secret_key = 'xxs' +login_manager = flask_login.LoginManager() +login_manager.init_app(app) @app.route("/") def index(): + login = None + if 'username' in session: + login = session['username'] """ This is a one-pager which shows all the boards and cards """ - return render_template('index.html') + return render_template('index.html', login=login) + + +@app.route("/boards") +def boards(): + login = None + if 'username' in session: + login = session['username'] + else: + return redirect('/login') + """ + This is a one-pager which shows all the boards and cards + """ + return render_template('boards.html', login=login) @app.route("/get-boards") @json_response def get_boards(): """ - All the boards + All the names boards """ return data_handler.get_boards() @@ -33,6 +53,53 @@ def get_cards_for_board(board_id: int): return data_handler.get_cards_for_board(board_id) +@app.route('/login', methods=['POST', 'GET']) +def login(): + if 'username' in session: + return redirect('/my_page') + else: + if request.method == 'POST': + data = request.form + if data_manager_user_operations.verify_login(data): + return render_template("login.html", message="Oops login or password is not correct") + else: + session['username'] = request.form['username'] + return redirect('/my_page') + return render_template("login.html") + +@app.route('/logout') +def logout(): + # remove the username from the session if it is there + session.pop('username', None) + return redirect('/login') + + +@app.route('/register', methods=['POST', 'GET']) +def register(): + if 'username' in session: + return redirect('/my_page') + else: + if request.method == 'POST': + data = request.form + if data_manager_user_operations.verify_credentials(data): + return render_template('register.html', message=data_manager_user_operations.verify_credentials(data)) + else: + data_manager_user_operations.register(data) + session['username'] = request.form['username'] + session['email'] = request.form['email'] + return redirect('/my_page') + return render_template("register.html") + + +@app.route('/my_page') +def my_page(): + if 'username' in session: + info = data_manager_user_operations.get_email(session['username']) + session['email'] = info[0]['user_email'] + return render_template('my_page.html', username=session['username'], email=session['email']) + return redirect("/login") + + def main(): app.run(debug=True) diff --git a/sample_data/askmatepart2-sample-data.sql b/sample_data/askmatepart2-sample-data.sql new file mode 100644 index 00000000..6dd3f9f5 --- /dev/null +++ b/sample_data/askmatepart2-sample-data.sql @@ -0,0 +1,72 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 9.5.6 +-- Dumped by pg_dump version 9.5.6 + +DROP TABLE IF EXISTS public.boards; +DROP SEQUENCE IF EXISTS public.boards_id_seq; +CREATE TABLE boards ( + id serial NOT NULL, + title text +); + +DROP TABLE IF EXISTS public.cards; +DROP SEQUENCE IF EXISTS public.cards_id_seq; +CREATE TABLE cards ( + id serial NOT NULL, + board_id integer, + title text, + status_id integer, + "order" integer +); + +DROP TABLE IF EXISTS public.credentials; +DROP SEQUENCE IF EXISTS public.credentials_id_seq; +CREATE TABLE credentials ( + id serial NOT NULL, + user_id integer, + user_login text, + user_email text, + user_password text +); + +DROP TABLE IF EXISTS public.statuses; +DROP SEQUENCE IF EXISTS public.statuses_id_seq; +CREATE TABLE statuses ( + id serial NOT NULL, + title text +); + +-- ALTER TABLE ONLY answer +-- ADD CONSTRAINT pk_answer_id PRIMARY KEY (id); +-- +-- ALTER TABLE ONLY question_tag +-- ADD CONSTRAINT pk_question_tag_id PRIMARY KEY (question_id, tag_id); +-- +-- ALTER TABLE ONLY comment +-- ADD CONSTRAINT fk_answer_id FOREIGN KEY (answer_id) REFERENCES answer(id); + +INSERT INTO boards VALUES (1, 'Board 1'); +INSERT INTO boards VALUES (2, 'Board 2'); + + +INSERT INTO cards VALUES (1, 1, 'new card 1', 0, 0); +INSERT INTO cards VALUES (2,1,'new card 2',0,1); +INSERT INTO cards VALUES (3,1,'in progress card',1,0); +INSERT INTO cards VALUES (4,1,'planning',2,0); +INSERT INTO cards VALUES (5,1,'done card 1',3,0); +INSERT INTO cards VALUES (6,1,'done card 1',3,1); +INSERT INTO cards VALUES (7,2,'new card 1',0,0); +INSERT INTO cards VALUES (8,2,'new card 2',0,1); +INSERT INTO cards VALUES (9,2,'in progress card',1,0); +INSERT INTO cards VALUES (10,2,'planning',2,0); +INSERT INTO cards VALUES (11,2,'done card 1',3,0); +INSERT INTO cards VALUES (12,2,'done card 1',3,1); + + +INSERT INTO statuses VALUES (0, 'new'); +INSERT INTO statuses VALUES (1, 'in progress'); +INSERT INTO statuses VALUES (2, 'testing'); +INSERT INTO statuses VALUES (3, 'done'); diff --git a/static/css/main.css b/static/css/main.css deleted file mode 100644 index d8f3b84f..00000000 --- a/static/css/main.css +++ /dev/null @@ -1,3 +0,0 @@ -#boards { - background-color: green; -} \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css new file mode 100644 index 00000000..dce65213 --- /dev/null +++ b/static/css/style.css @@ -0,0 +1,103 @@ +body { + margin: 0; +} +.ui-sortable { + min-height: 80px; +} + +/* STYLE TABLICY */ +.kanban-table__title { + margin-left: 8px; +} +.kanban-table__container { + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} +.kanban-table__new-column { + cursor: pointer; + text-decoration: underline; + margin: 5px 8px; + display: block; +} + + +/* STYLE KOLUMNY */ +.kanban-column { + order: 1; + min-width: 300px; + min-height: 50vh; + background-color: lightgrey; + margin-left: 8px; + list-style: none; + position: relative; +} +.kanban-column__title { + color: #ff8d55; + margin: 8px 5px; +} +.kanban-column__delete { + position: absolute; + right: 0; + top: 0; + width: 22px; + height: 22px; + border: 0; + background-color: rgba(0,0,0,0.4); + color: white; +} +.kanban-column__add-card { + cursor: pointer; + text-decoration: underline; + margin: 5px 8px; + display: block; +} +.kanban-column__list { + list-style: none; + margin: 5px; + padding: 0px; +} + +/* STYLE KARTY */ +.kanban-card { + box-sizing: border-box; + display: block; + height: 60px; + margin-bottom: 5px; + padding: 4px; + overflow: hidden; + position: relative; +} +.kanban-card--white { + background-color: #fff; + color: #000; +} +.kanban-card--green { + background-color: #ff213f; + color: #333; +} +.kanban-card--blue { + background-color: #00f; + color: #eee +} +.kanban-card__description { + margin: 2px 0; +} +.kanban-card__delete { + position: absolute; + right: 0; + top: 0; + width: 22px; + height: 22px; + border: 0; + background-color: rgba(0,0,0,0.4); + color: white; +} +.kanban-card__placeholder { + box-sizing: border-box; + display: block; + height: 60px; + margin-bottom: 5px; + border: 2px solid lightgrey; + background-color: lightblue; +} diff --git a/static/js/main.js b/static/js/main.js index f9298ae2..a90eb84f 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,12 +1,126 @@ -import { dom } from "./dom.js"; +$(function(){ -// This function is to initialize the application -function init() { - // init data - dom.init(); - // loads the boards to the screen - dom.loadBoards(); + // FUNKCJE POMOCNICZE + function initSortable() { + $('.kanban-column__list').sortable({ + connectWith: '.kanban-column__list', + placeholder: 'kanban-card__placeholder' + }).disableSelection(); + } -} + function randomString() { + var chars = '0123456789abcdefghiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXTZ'.split + var str = '', i; + for (i = 0; i < 10; i++) { + str += chars[Math.floor(Math.random() * chars.length)]; + } + return str; + } -init(); + // KLASA KANBAN COLUMN + function KanbanColumn(name) { + var self = this; + + this.id = randomString(); + this.name = name || 'Add name'; + this.element = createColumnElement(); + + function createColumnElement() { + // TWORZENIE NOWYCH WĘZŁÓW + var columnElement = $('
'); + var columnTitleElement = $('

' + self.name.toUpperCase() + '

'); + var columnListElement = $(''); + var columnDeleteElement = $(''); + var columnAddCardElement = $('Add card'); + + // PODPINANIE ODPOWIEDNICH ZDARZEŃ POD WĘZŁY + columnDeleteElement.click(function() { + self.removeColumn(); + }); + columnAddCardElement.click(function(event) { + event.preventDefault(); + self.addCard(new KanbanCard('description', 'white')); + }); + + // KONSTRUOWANIE ELEMENTU KOLUMNY + columnElement.append(columnTitleElement) + .append(columnDeleteElement) + .append(columnAddCardElement) + .append(columnListElement); + return columnElement; + } + } + KanbanColumn.prototype = { + addCard: function(card) { + this.element.children('ul').append(card.element); + }, + removeColumn: function() { + this.element.remove(); + } + }; + + // KLASA KANBAN CARD + function KanbanCard(description, color) { + var self = this; + + this.id = randomString(); + this.description = description || 'proman'; + this.color = color || 'white'; + this.element = createCardElement(); + + function createCardElement() { + var kanbanCard = $('
  • '); + var kanbanCardButton = $(''); + var kanbanCardDescription = $('

    '); + kanbanCard.addClass('kanban-card--' + self.color); + kanbanCardButton.click(function(){ + self.removeCard(); + }); + kanbanCard.append(kanbanCardButton); + kanbanCardDescription.text(self.description); + kanbanCard.append(kanbanCardDescription) + return kanbanCard; + } + } + KanbanCard.prototype = { + removeCard: function() { + this.element.remove(); + } + } + + // KANBAN TABLE OBJECT + var kanbanTable = { + name: 'Tablica Kanban', + addColumn: function(column) { + this.element.append(column.element); + initSortable(); + }, + element: $('#kanban-table .kanban-table__container') + }; + $('.kanban-table__new-column') + .click(function(){ + kanbanTable.addColumn(new KanbanColumn()); + }); + + // TWORZENIE NOWYCH EGZEMPLARZY KOLUMN + var coll1 = new KanbanColumn('Add name'); + var coll2 = new KanbanColumn('Add name'); + var coll3 = new KanbanColumn('Add name'); + + // DODAWANIE KOLUMN DO TABLICY + kanbanTable.addColumn(coll1); + kanbanTable.addColumn(coll2); + kanbanTable.addColumn(coll3); + + // TWORZENIE NOWYCH EGZEMPLARZY KART + var card1 = new KanbanCard(); + var card2 = new KanbanCard(); + var card3 = new KanbanCard('!@#@$%^#@ tablice', 'green'); + var card4 = new KanbanCard('wcale nie random data', 'blue'); + + // DODAWANIE KART DO KOLUMN + coll1.addCard(card1); + coll2.addCard(card2); + coll3.addCard(card3); + coll3.addCard(card4); +}) diff --git a/templates/boards.html b/templates/boards.html new file mode 100644 index 00000000..9e758e0e --- /dev/null +++ b/templates/boards.html @@ -0,0 +1,138 @@ + + + + + + + Swarm + + + + + + + + + + + + + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/index.html b/templates/index.html index 6f42f26e..1f5e9cb3 100644 --- a/templates/index.html +++ b/templates/index.html @@ -4,7 +4,7 @@ - ProMan + Swarm @@ -12,10 +12,119 @@ + + + -

    ProMan

    -
    Boards are loading...
    + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/login.html b/templates/login.html new file mode 100644 index 00000000..f296319f --- /dev/null +++ b/templates/login.html @@ -0,0 +1,155 @@ + + + + + + + Swarm + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    +
    + + Dont have an account? + {% if message %} +
    + Error: {{ message }} +
    + {% endif %} +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/templates/my_page.html b/templates/my_page.html new file mode 100644 index 00000000..54514537 --- /dev/null +++ b/templates/my_page.html @@ -0,0 +1,53 @@ + + + + + + + Swarm + + + + + + + + + + + + + + +
    +
    +
    +

    {{ username }}

    +

    {{ email }}

    +

    Log Out

    +
    +
    +
    + + \ No newline at end of file diff --git a/templates/register.html b/templates/register.html new file mode 100644 index 00000000..45970e89 --- /dev/null +++ b/templates/register.html @@ -0,0 +1,163 @@ + + + + + + + Swarm + + + + + + + + + + + + + + + + +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + {% if message %} +
    + Error: {{ message }} +
    + {% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +