import css and js
This commit is contained in:
parent
08bb21b8ff
commit
5e858c3c4b
@ -1,6 +1,6 @@
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from flask import Flask, render_template
|
from flask import Flask, render_template, g, redirect, url_for
|
||||||
|
|
||||||
|
|
||||||
def create_app(test_config=None):
|
def create_app(test_config=None):
|
||||||
@ -21,9 +21,6 @@ def create_app(test_config=None):
|
|||||||
except OSError:
|
except OSError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@app.route('/')
|
|
||||||
def index():
|
|
||||||
return render_template("index.html")
|
|
||||||
from . import db
|
from . import db
|
||||||
db.init_app(app)
|
db.init_app(app)
|
||||||
from . import auth
|
from . import auth
|
||||||
@ -31,4 +28,15 @@ def create_app(test_config=None):
|
|||||||
from . import admin
|
from . import admin
|
||||||
app.register_blueprint(admin.bp)
|
app.register_blueprint(admin.bp)
|
||||||
app.add_url_rule("/admin", endpoint="admin.index")
|
app.add_url_rule("/admin", endpoint="admin.index")
|
||||||
|
from . import user
|
||||||
|
app.register_blueprint(user.bp)
|
||||||
|
|
||||||
|
@app.route('/')
|
||||||
|
def index():
|
||||||
|
if g.user is not None and 'user_id' in g.user.keys():
|
||||||
|
if g.user['user_id'] == 0:
|
||||||
|
return redirect(url_for("admin.index"))
|
||||||
|
else:
|
||||||
|
return redirect(url_for("user.home"))
|
||||||
|
return render_template("index.html")
|
||||||
return app
|
return app
|
||||||
@ -24,7 +24,7 @@ def loginuser():
|
|||||||
if error is None:
|
if error is None:
|
||||||
session.clear()
|
session.clear()
|
||||||
session['user_id'] = user['user_id']
|
session['user_id'] = user['user_id']
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('home'))
|
||||||
|
|
||||||
flash(error)
|
flash(error)
|
||||||
|
|
||||||
@ -79,7 +79,8 @@ def login_required(view):
|
|||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
if g.user is None:
|
if g.user is None:
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
elif g.user['user_id'] == 0:
|
||||||
|
return redirect(url_for('admin.index'))
|
||||||
return view(**kwargs)
|
return view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
@ -88,9 +89,9 @@ def admin_login_required(view):
|
|||||||
@functools.wraps(view)
|
@functools.wraps(view)
|
||||||
def wrapped_view(**kwargs):
|
def wrapped_view(**kwargs):
|
||||||
if g.user is None:
|
if g.user is None:
|
||||||
return redirect(url_for('auth.loginadmin'))
|
return redirect(url_for('auth.login'))
|
||||||
elif g.user['user_id'] != 0:
|
elif g.user['user_id'] != 0:
|
||||||
return redirect(url_for('index'))
|
return redirect(url_for('home'))
|
||||||
return view(**kwargs)
|
return view(**kwargs)
|
||||||
|
|
||||||
return wrapped_view
|
return wrapped_view
|
||||||
@ -40,6 +40,7 @@ book_isbn varchar(100) DEFAULT NULL,
|
|||||||
book_publisher varchar(100) DEFAULT NULL,
|
book_publisher varchar(100) DEFAULT NULL,
|
||||||
book_pubdate datetime DEFAULT NULL,
|
book_pubdate datetime DEFAULT NULL,
|
||||||
book_lang varchar(100) DEFAULT NULL,
|
book_lang varchar(100) DEFAULT NULL,
|
||||||
|
book_author varchar(100) DEFAULT NULL,
|
||||||
user_id int NOT NULL,
|
user_id int NOT NULL,
|
||||||
CONSTRAINT `fk_book_user_id` FOREIGN KEY (`user_id`) REFERENCES user(`user_id`) ON DELETE RESTRICT
|
CONSTRAINT `fk_book_user_id` FOREIGN KEY (`user_id`) REFERENCES user(`user_id`) ON DELETE RESTRICT
|
||||||
);
|
);
|
||||||
|
|||||||
2
src/static/axios.min.js
vendored
Normal file
2
src/static/axios.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/static/spectre-exp.min.css
vendored
Normal file
1
src/static/spectre-exp.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/static/spectre-icons.min.css
vendored
Normal file
1
src/static/spectre-icons.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
1
src/static/spectre.min.css
vendored
Normal file
1
src/static/spectre.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -1,7 +1,11 @@
|
|||||||
<!doctype html>
|
<!doctype html>
|
||||||
<title>{% block title %}{% endblock %} - DBProject</title>
|
<title>{% block title %}{% endblock %} - DBProject</title>
|
||||||
<!-- <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> -->
|
<link rel="stylesheet" href="{{ url_for('static', filename='spectre-exp.min.css') }}">
|
||||||
<nav>
|
<link rel="stylesheet" href="{{ url_for('static', filename='spectre-icons.min.css') }}">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('static', filename='spectre.min.css') }}">
|
||||||
|
<script src="{{ url_for('static', filename='axios.min.js')}}"></script>
|
||||||
|
|
||||||
|
<!-- <nav>
|
||||||
<h1>BookManage</h1>
|
<h1>BookManage</h1>
|
||||||
<ul>
|
<ul>
|
||||||
{% if g.user %}
|
{% if g.user %}
|
||||||
@ -12,7 +16,24 @@
|
|||||||
<li><a href="{{ url_for('auth.loginuser') }}">用户登陆</a>
|
<li><a href="{{ url_for('auth.loginuser') }}">用户登陆</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav> -->
|
||||||
|
<header class="navbar">
|
||||||
|
<section class="navbar-section">
|
||||||
|
<h1>电子书管理系统</h1>
|
||||||
|
</section>
|
||||||
|
<section class="navbar-center">
|
||||||
|
<!-- centered logo or brand -->
|
||||||
|
</section>
|
||||||
|
<section class="navbar-section">
|
||||||
|
{% if g.user %}
|
||||||
|
<span class="chip">{{ g.user['user_name'] }}</span>
|
||||||
|
<a class="btn btn-link" href="{{ url_for('auth.logout') }}">登出</a>
|
||||||
|
{% else %}
|
||||||
|
<a class="btn btn-link" href="{{ url_for('auth.loginadmin') }}">管理员登陆</a>
|
||||||
|
<a class="btn btn-link" href="{{ url_for('auth.loginuser') }}">用户登陆</a>
|
||||||
|
{% endif %}
|
||||||
|
</section>
|
||||||
|
</header>
|
||||||
<section class="content">
|
<section class="content">
|
||||||
<header>
|
<header>
|
||||||
{% block header %}{% endblock %}
|
{% block header %}{% endblock %}
|
||||||
|
|||||||
99
src/templates/user/addbook.html
Normal file
99
src/templates/user/addbook.html
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<style>
|
||||||
|
.maindiv {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 20;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.sidenav {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
z-index: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
var typelist = {};
|
||||||
|
function switch_type(typename) {
|
||||||
|
if (typelist.hasOwnProperty(typename)) {
|
||||||
|
delete typelist[typename];
|
||||||
|
} else {
|
||||||
|
typelist[typename] = 1;
|
||||||
|
}
|
||||||
|
let typeinput = document.getElementById("booktype");
|
||||||
|
let typestr = "";
|
||||||
|
for (let typekey in typelist) {
|
||||||
|
typestr += typekey + ";";
|
||||||
|
}
|
||||||
|
typeinput.value = typestr;
|
||||||
|
}
|
||||||
|
function create_type_button(typename) {
|
||||||
|
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container maindiv">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-3">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/home">主页</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/search">搜索</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/addbook">增加</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/tags">分类</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="column col-9 panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h2>新建图书</h2>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<form method="post" class="form-group">
|
||||||
|
<label class="form-label" for="bookname">书名*</label>
|
||||||
|
<input class="form-input" name="bookname" id="bookname" required>
|
||||||
|
<label class="form-label" for="bookisbn">ISBN</label>
|
||||||
|
<input class="form-input" name="bookisbn" id="bookisbn">
|
||||||
|
<label class="form-label" for="bookpublisher">出版社</label>
|
||||||
|
<input class="form-input" name="bookpublisher" id="bookpublisher">
|
||||||
|
<label class="form-label" for="bookauthor">作者</label>
|
||||||
|
<input class="form-input" name="bookauthor" id="bookauthor">
|
||||||
|
<label class="form-label" for="booklang">语言</label>
|
||||||
|
<select class="form-select" name="booklang" id="booklang">
|
||||||
|
<option selected>CN</option>
|
||||||
|
<option>EN</option>
|
||||||
|
<option>Other</option>
|
||||||
|
</select>
|
||||||
|
<label class="form-label" for="booktype">分类</label>
|
||||||
|
<input class="form-input" name="booktype" id="booktype">
|
||||||
|
<div>
|
||||||
|
{% for atype in typelist%}
|
||||||
|
<span class="chip btn" onclick={{"switch_type(" ~ atype ~ ")"}}>{{atype}}</span>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<input class="btn btn-primary input-group-btn p-centered" type="submit" value="提交">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
Rendered @ {{cur_time}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
64
src/templates/user/addtype.html
Normal file
64
src/templates/user/addtype.html
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<style>
|
||||||
|
.maindiv {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 20;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.sidenav {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
z-index: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container maindiv">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-3">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/home">主页</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/search">搜索</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/addbook">增加</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/tags">分类</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="column col-9 panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<div class="panel-title">图书分类</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<ul>
|
||||||
|
{% for atype in typelist%}
|
||||||
|
<li>
|
||||||
|
TID:{{ atype['type_id'] }}, 类型名: {{ user['user_name'] }}
|
||||||
|
<a href={{"/addtype/removeuser?uid=" ~ user['user_id']}}>删除</a>
|
||||||
|
</li>
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
Rendered @ {{cur_time}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
67
src/templates/user/home.html
Normal file
67
src/templates/user/home.html
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<style>
|
||||||
|
.maindiv {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 20;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.sidenav {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
z-index: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container maindiv">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-3">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/home">主页</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/search">搜索</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/addbook">增加</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/tags">分类</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="column col-9 panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<div class="panel-title">统计信息</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
<dl>
|
||||||
|
<dt> 当前占用的空间 </dt>
|
||||||
|
<dd> {{user_stat['user_usedspace'] / 1024}}MB /
|
||||||
|
{{user_stat['user_limit'] / 1024 / 1024}}GB</dd>
|
||||||
|
<dt> 图书数量 </dt>
|
||||||
|
<dd> {{user_stat['user_bookcount']}}本</dd>
|
||||||
|
<dt> 文件数量 </dt>
|
||||||
|
<dd> {{user_stat['user_doccount']}}个</dd>
|
||||||
|
<dt> 笔记数量 </dt>
|
||||||
|
<dd> {{user_stat['user_notecount']}}篇</dd>
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
Rendered @ {{cur_time}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
57
src/templates/user/search.html
Normal file
57
src/templates/user/search.html
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block header %}
|
||||||
|
<style>
|
||||||
|
.maindiv {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 1;
|
||||||
|
top: 20;
|
||||||
|
left: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
padding-left: 1.2rem;
|
||||||
|
padding-right: 2rem;
|
||||||
|
}
|
||||||
|
.sidenav {
|
||||||
|
height: 100%;
|
||||||
|
max-width: 160px;
|
||||||
|
z-index: 1;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container maindiv">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="column col-3">
|
||||||
|
<ul class="nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/home">主页</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/search">搜索</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/addbook">增加</a>
|
||||||
|
</li>
|
||||||
|
<li class="nav-item">
|
||||||
|
<a href="/tags">分类</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="column col-9 panel">
|
||||||
|
<div class="panel-header">
|
||||||
|
<div class="panel-title">搜索图书</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-body">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="panel-footer">
|
||||||
|
Rendered @ {{cur_time}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
91
src/user.py
91
src/user.py
@ -0,0 +1,91 @@
|
|||||||
|
from flask import (
|
||||||
|
Blueprint, flash, g, redirect, render_template, request, url_for
|
||||||
|
)
|
||||||
|
import re
|
||||||
|
from werkzeug.exceptions import abort
|
||||||
|
from werkzeug.security import generate_password_hash
|
||||||
|
|
||||||
|
from src.auth import login_required
|
||||||
|
from src.db import get_db
|
||||||
|
import sqlite3
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
bp = Blueprint('user', __name__)
|
||||||
|
|
||||||
|
@bp.route("/home")
|
||||||
|
@login_required
|
||||||
|
def home():
|
||||||
|
db = get_db()
|
||||||
|
user_stat = db.execute(
|
||||||
|
"select * from user_stat where user_id = ?", (g.user['user_id'],)
|
||||||
|
).fetchone()
|
||||||
|
return render_template("user/home.html", user_stat=user_stat, cur_time=datetime.datetime.now())
|
||||||
|
|
||||||
|
@bp.route("/addbook", methods=('GET', 'POST'))
|
||||||
|
@login_required
|
||||||
|
def addbook():
|
||||||
|
db = get_db()
|
||||||
|
if request.method == 'GET':
|
||||||
|
typelist = db.execute("select type_name from typetable").fetchall()
|
||||||
|
return render_template("user/addbook.html", typelist=typelist)
|
||||||
|
elif request.method == 'POST':
|
||||||
|
bookname = request.form['bookname']
|
||||||
|
bookisbn = request.form['bookisbn']
|
||||||
|
bookpublisher = request.form['bookpublisher']
|
||||||
|
booklang = request.form['booklang']
|
||||||
|
bookauthor = request.form['bookauthor']
|
||||||
|
booktype = request.form['booktype']
|
||||||
|
error = None
|
||||||
|
if len(bookname) == 0:
|
||||||
|
error = "书名不能为空"
|
||||||
|
if len(bookisbn) == 0:
|
||||||
|
bookisbn = None
|
||||||
|
if len(bookpublisher) == 0:
|
||||||
|
bookpublisher = None
|
||||||
|
if len(booklang) == 0:
|
||||||
|
booklang = None
|
||||||
|
if len(bookauthor) == 0:
|
||||||
|
booklang = None
|
||||||
|
if len(booktype) == 0:
|
||||||
|
booktype = None
|
||||||
|
db.execute(
|
||||||
|
"insert into book (`book_name`, `book_isbn`"
|
||||||
|
", `book_publisher`, `book_lang`, `book_author`, `user_id`) "
|
||||||
|
"values (?,?,?,?,?,?,?)",
|
||||||
|
(bookname, bookisbn, bookpublisher, booklang, bookauthor, g.user['user_id'],))
|
||||||
|
bookid = db.execute("select max(book_id) from book").fetchone()
|
||||||
|
db.commit()
|
||||||
|
error = None
|
||||||
|
if booktype is not None:
|
||||||
|
booktypes = booktype.split(";")
|
||||||
|
booktypes.remove("")
|
||||||
|
print(booktypes)
|
||||||
|
for booktype in booktypes:
|
||||||
|
typeid = db.execute("select type_id from typetable where type_name=?",
|
||||||
|
(booktype,)).fetchone()
|
||||||
|
if typeid is None:
|
||||||
|
db.execute("insert into typetable(`type_name`) values (?)",
|
||||||
|
(booktype,))
|
||||||
|
typeid = db.execute("select type_id from typetable where type_name=?",
|
||||||
|
(booktype,)).fetchone()
|
||||||
|
try:
|
||||||
|
db.execute("insert into book_type values(?,?)",
|
||||||
|
(bookid, typeid))
|
||||||
|
db.commit()
|
||||||
|
except sqlite3.Error as _e:
|
||||||
|
error = "未知错误: %s" % (_e)
|
||||||
|
db.rollback()
|
||||||
|
if error is not None:
|
||||||
|
flash(error)
|
||||||
|
else:
|
||||||
|
return render_template("user/addbook.html", success=True)
|
||||||
|
return render_template("user/addbook.html")
|
||||||
|
|
||||||
|
@bp.route("/addtype", methods=('GET', 'POST', 'PUT'))
|
||||||
|
@login_required
|
||||||
|
def addtype():
|
||||||
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
db = get_db()
|
||||||
|
typelist = db.execute("select * from typetable").fetchall()
|
||||||
|
return render_template("user/addtype.html", typelist=typelist)
|
||||||
Loading…
Reference in New Issue
Block a user