Compare commits
No commits in common. 'master' and 'master' have entirely different histories.
@ -1,52 +0,0 @@
|
|||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
env/
|
|
||||||
bin/
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
.idea
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.coverage
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
# Rope
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
|||||||
## YellWOWPages
|
|
||||||
|
|
||||||
Wownero yellow pages web-application - lookup the WOW address of users.
|
|
||||||
|
|
||||||
### Installation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone gitea@git.wownero.com:wownero/YellWOWPages.git
|
|
||||||
cd YellWOWPages
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
cp settings.py_example settings.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Change `settings.py` to your liking. Then run the application:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python3 run.py
|
|
||||||
```
|
|
@ -0,0 +1,5 @@
|
|||||||
|
remove Constraints.client_secret before publishing and make a backup of it!!
|
||||||
|
|
||||||
|
Register new application new login.wownero.com
|
||||||
|
client_id = yellwowpages
|
||||||
|
client url = <domain>/authenticate
|
@ -0,0 +1,8 @@
|
|||||||
|
from starlette.templating import Jinja2Templates
|
||||||
|
|
||||||
|
|
||||||
|
class Constraints:
|
||||||
|
templates = Jinja2Templates(directory='frontend/templates')
|
||||||
|
client_id = 'yellwowpagestesting11'
|
||||||
|
client_secret = '8284d868-f5a1-48f4-a287-8bdb18a7d3a1'
|
||||||
|
uri = 'sqlite:///users.db'
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,108 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>YellWOWPages - Sex and Drugs in the metaverse</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="../../static/colors.css">
|
||||||
|
<link rel="stylesheet" href="../../static/icon.css">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
border-color: var(--yellow);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
#dropdown{
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#dropdowncontent{
|
||||||
|
display: none;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--table-border-color);
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
padding: 30px 50px;
|
||||||
|
color: var(--yellow);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dropdown:hover #dropdowncontent {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#main{
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form{
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
#footer{
|
||||||
|
height: 12vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
kbd{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div id="dropdown">
|
||||||
|
<i class="icon icon-menu"></i>
|
||||||
|
<div id="dropdowncontent">
|
||||||
|
<p>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
<a href="/yellwowpage">Yell<span>WOW</span>Page</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/about/api">Api</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">Yell<span>WOW</span>Pages</a></strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.wownero.com/muchwowmining/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="main">
|
||||||
|
<h1>About - Api</h1>
|
||||||
|
<p>
|
||||||
|
Search user: <code><a href="/api/user/{username}" data-tooltip="no partial search yet">/api/user/{username}</a></code>
|
||||||
|
<br><br>
|
||||||
|
Get all users: <code><a href="/api/all">/api/all</a></code>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
2022 - ... [the future is w0w]
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,125 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>YellWOWPages - Sex and Drugs in the metaverse</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="../../static/colors.css">
|
||||||
|
<link rel="stylesheet" href="../../static/icon.css">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
border-color: var(--yellow);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
#dropdown{
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#dropdowncontent{
|
||||||
|
display: none;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--table-border-color);
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
padding: 30px 50px;
|
||||||
|
color: var(--yellow);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dropdown:hover #dropdowncontent {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#main{
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
form{
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
#footer{
|
||||||
|
height: 12vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
kbd{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div id="dropdown">
|
||||||
|
<i class="icon icon-menu"></i>
|
||||||
|
<div id="dropdowncontent">
|
||||||
|
<p>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
<a href="/yellwowpage">Yell<span>WOW</span>Page</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/about/api">Api</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">Yell<span>WOW</span>Pages</a></strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.wownero.com/muchwowmining/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="main">
|
||||||
|
<h1>About</h1>
|
||||||
|
<p>
|
||||||
|
Search for any Wownero <em>sub-address</em> you want by username and pay
|
||||||
|
the world!
|
||||||
|
<br>
|
||||||
|
This application uses <u>Wownero's Centralized Authentication Service.</u>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Other Wownero related stuff:
|
||||||
|
<br>
|
||||||
|
<a href="https://wownero.org/">WebSite</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://suchwow.xyz">SuchWow</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://git.wownero.com">Official Git</a>
|
||||||
|
<br>
|
||||||
|
<a href="https://discord.com/invite/ykZyAzJhDK">Discord server</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Idea of: dsc_
|
||||||
|
<br>
|
||||||
|
Made by <a href="https://notmtth.xyz">NotMtth</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
2022 - ... [the future is w0w]
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,100 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Such dashboard</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="../../static/colors.css">
|
||||||
|
<link rel="stylesheet" href="../../static/icon.css">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
border-color: var(--yellow);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
#dropdown{
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#dropdowncontent{
|
||||||
|
display: none;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--table-border-color);
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
padding: 30px 50px;
|
||||||
|
color: var(--yellow);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dropdown:hover #dropdowncontent {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#main{
|
||||||
|
width: 100%;
|
||||||
|
height: 85vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div id="dropdown">
|
||||||
|
<i class="icon icon-menu"></i>
|
||||||
|
<div id="dropdowncontent">
|
||||||
|
<p>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
<a href="/yellwowpage">Yell<span>WOW</span>Page</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/about/api">Api</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">Yell<span>WOW</span>Pages</a></strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.wownero.com/muchwowmining/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="main">
|
||||||
|
<article>
|
||||||
|
{% for username, address in user_data.items() %}
|
||||||
|
<Header>Welcome back <em>{{username}}</em>!</Header>
|
||||||
|
Current <u>sub-address</u>: <label><mark>{{address}}</mark></label>
|
||||||
|
<footer>
|
||||||
|
Change <u>sub-address</u>:
|
||||||
|
<form action="/submit_address" method="POST">
|
||||||
|
<input type="text" name="address">
|
||||||
|
<button data-tooltip="Be sure it's correct">Submit</button>
|
||||||
|
</form>
|
||||||
|
</footer>
|
||||||
|
{% endfor %}
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,126 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>YellWOWPages - Sex and Drugs in the metaverse</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="../../static/colors.css">
|
||||||
|
<link rel="stylesheet" href="../../static/icon.css">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
border-color: var(--yellow);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
#dropdown{
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#dropdowncontent{
|
||||||
|
display: none;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--table-border-color);
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
padding: 30px 50px;
|
||||||
|
color: var(--yellow);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dropdown:hover #dropdowncontent {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#main{
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
form{
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
#addresses{
|
||||||
|
width: 100%;
|
||||||
|
height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#addresses::-webkit-scrollbar{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#footer{
|
||||||
|
height: 12vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
kbd{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div id="dropdown">
|
||||||
|
<i class="icon icon-menu"></i>
|
||||||
|
<div id="dropdowncontent">
|
||||||
|
<p>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
<a href="/yellwowpage">Yell<span>WOW</span>Page</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/about/api">Api</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">Yell<span>WOW</span>Pages</a></strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.wownero.com/muchwowmining/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="main">
|
||||||
|
<form action="/search" method="GET">
|
||||||
|
<input type="text" name="username" placeholder="Username to search">
|
||||||
|
<label for="switch">
|
||||||
|
<input type="checkbox" name="switch" role="switch">
|
||||||
|
<em data-tooltip="Search address with a part of username; get 3 results">Partial</em>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<div id="addresses">
|
||||||
|
{% for username, address in user_data.items() %}
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<em>{{username}}</em>
|
||||||
|
</header>
|
||||||
|
<kbd>{{address}}</kbd>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
2022 - ... [the future is w0w]
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,132 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en" data-theme="dark">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>YellWOWPages - Sex and Drugs in the metaverse</title>
|
||||||
|
<link rel="stylesheet" href="https://unpkg.com/@picocss/pico@latest/css/pico.min.css">
|
||||||
|
<link rel="stylesheet" href="../../static/colors.css">
|
||||||
|
<link rel="stylesheet" href="../../static/icon.css">
|
||||||
|
</head>
|
||||||
|
<style>
|
||||||
|
html, body{
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
a{
|
||||||
|
color: var(--yellow);
|
||||||
|
}
|
||||||
|
span{
|
||||||
|
color: var(--purple);
|
||||||
|
}
|
||||||
|
.nav{
|
||||||
|
border-color: var(--yellow);
|
||||||
|
border: 2px;
|
||||||
|
}
|
||||||
|
#dropdown{
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
#dropdowncontent{
|
||||||
|
display: none;
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
background-color: var(--table-border-color);
|
||||||
|
min-width: 160px;
|
||||||
|
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||||
|
padding: 30px 50px;
|
||||||
|
color: var(--yellow);
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
#dropdown:hover #dropdowncontent {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#main{
|
||||||
|
width: 100%;
|
||||||
|
height: 80vh;
|
||||||
|
display: grid;
|
||||||
|
place-content: center;
|
||||||
|
}
|
||||||
|
form{
|
||||||
|
height: 80px;
|
||||||
|
}
|
||||||
|
#addresses{
|
||||||
|
width: 100%;
|
||||||
|
height: 50vh;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
#addresses::-webkit-scrollbar{
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#footer{
|
||||||
|
height: 12vh;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
@media (max-width: 800px) {
|
||||||
|
kbd{
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<body>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<nav>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<div id="dropdown">
|
||||||
|
<i class="icon icon-menu"></i>
|
||||||
|
<div id="dropdowncontent">
|
||||||
|
<p>
|
||||||
|
<a href="/login">Login</a>
|
||||||
|
<a href="/dashboard">Dashboard</a>
|
||||||
|
<a href="/yellwowpage">Yell<span>WOW</span>Page</a>
|
||||||
|
<a href="/about">About</a>
|
||||||
|
<a href="/about/api">Api</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><strong><a href="/">Yell<span>WOW</span>Pages</a></strong></li>
|
||||||
|
</ul>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://git.wownero.com/muchwowmining/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div id="main">
|
||||||
|
<form action="/search" method="GET">
|
||||||
|
<input type="text" name="username" placeholder="Username to search">
|
||||||
|
<label for="switch">
|
||||||
|
<input type="checkbox" name="switch" role="switch">
|
||||||
|
<em data-tooltip="Search address with a part of username; get 3 results">Partial</em>
|
||||||
|
</label>
|
||||||
|
</form>
|
||||||
|
<br>
|
||||||
|
Result: {{user_data|length}}
|
||||||
|
{% if not user_data|length %}
|
||||||
|
Nothing found...
|
||||||
|
{% else %}
|
||||||
|
<div id="addresses">
|
||||||
|
{% for username, address in user_data.items() %}
|
||||||
|
<article>
|
||||||
|
<header>
|
||||||
|
<em>{{username}}</em>
|
||||||
|
</header>
|
||||||
|
<kbd>{{address}}</kbd>
|
||||||
|
</article>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="footer">
|
||||||
|
2022 - ... [the future is w0w]
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,45 @@
|
|||||||
|
from fastapi import FastAPI, Request
|
||||||
|
from fastapi.responses import HTMLResponse
|
||||||
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
|
||||||
|
from starlette.exceptions import HTTPException as StarletteHTTPException
|
||||||
|
|
||||||
|
app = FastAPI(docs_url=None, redoc_url=None)
|
||||||
|
|
||||||
|
app.mount('/static', StaticFiles(directory='frontend/static'), name='static')
|
||||||
|
|
||||||
|
from routers import auth, static, dashboard, db, api
|
||||||
|
|
||||||
|
app.include_router(auth.router)
|
||||||
|
app.include_router(dashboard.router)
|
||||||
|
app.include_router(static.router)
|
||||||
|
app.include_router(api.router)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/")
|
||||||
|
@app.get('/root', response_class=HTMLResponse)
|
||||||
|
async def root(request: Request):
|
||||||
|
return Constraints.templates.TemplateResponse('/root/index.html', {'request': request})
|
||||||
|
|
||||||
|
|
||||||
|
# shitty error handling
|
||||||
|
@app.exception_handler(StarletteHTTPException)
|
||||||
|
async def http_exception_handler(request, exc):
|
||||||
|
if exc.status_code == 404:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'not found...',
|
||||||
|
'url': '/root'})
|
||||||
|
elif exc.status_code == 500:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'internal server error',
|
||||||
|
'url': '/root'})
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': exc.detail,
|
||||||
|
'url': '/root'})
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
uvicorn.run(app, host='127.0.0.1', port=8080)
|
@ -1,5 +1,30 @@
|
|||||||
peewee
|
anyio==3.5.0
|
||||||
quart
|
asgiref==3.5.0
|
||||||
Quart-Keycloak
|
asttokens==2.0.5
|
||||||
uvicorn
|
certifi==2021.10.8
|
||||||
redis
|
charset-normalizer==2.0.12
|
||||||
|
click==8.0.4
|
||||||
|
colorama==0.4.4
|
||||||
|
executing==0.8.3
|
||||||
|
fastapi==0.75.0
|
||||||
|
Flask==2.0.3
|
||||||
|
Flask-SQLAlchemy==2.5.1
|
||||||
|
greenlet==1.1.2
|
||||||
|
h11==0.13.0
|
||||||
|
icecream==2.1.2
|
||||||
|
idna==3.3
|
||||||
|
itsdangerous==2.1.1
|
||||||
|
Jinja2==3.0.3
|
||||||
|
MarkupSafe==2.1.0
|
||||||
|
pydantic==1.9.0
|
||||||
|
Pygments==2.11.2
|
||||||
|
python-multipart==0.0.5
|
||||||
|
requests==2.27.1
|
||||||
|
six==1.16.0
|
||||||
|
sniffio==1.2.0
|
||||||
|
SQLAlchemy==1.4.32
|
||||||
|
starlette==0.17.1
|
||||||
|
typing-extensions==4.1.1
|
||||||
|
urllib3==1.26.8
|
||||||
|
uvicorn==0.17.5
|
||||||
|
Werkzeug==2.0.3
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
from fastapi import APIRouter, Request, Form
|
||||||
|
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
|
from icecream import ic
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
from .db import Database
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
# external api
|
||||||
|
@router.get('/api/user/{username}')
|
||||||
|
async def get_api(request: Request, username: str):
|
||||||
|
if not Database.Users.get_address(username):
|
||||||
|
return {'error': 'invalid user'}
|
||||||
|
return Database.Users.get_address(username)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/api/all')
|
||||||
|
async def get_api_all(request: Request):
|
||||||
|
return Database.Users.get_all()
|
||||||
|
|
||||||
|
|
||||||
|
# site search redirect
|
||||||
|
@router.get('/search')
|
||||||
|
async def search_api(request: Request):
|
||||||
|
username = request.query_params['username']
|
||||||
|
ic(request.query_params.get('switch', None) == 'on')
|
||||||
|
if request.query_params.get('switch', None) == 'on':
|
||||||
|
return RedirectResponse(f'/yellwowpage/matches/{username}')
|
||||||
|
else:
|
||||||
|
return RedirectResponse(f'/yellwowpage/user/{username}')
|
@ -0,0 +1,68 @@
|
|||||||
|
from fastapi import APIRouter, Request, Cookie
|
||||||
|
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
|
import requests
|
||||||
|
from icecream import ic
|
||||||
|
import secrets
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/login')
|
||||||
|
async def get_login(request: Request):
|
||||||
|
state = secrets.token_hex(10)
|
||||||
|
response = RedirectResponse(
|
||||||
|
'https://login.wownero.com/auth/realms/master/protocol/openid-connect/auth?'f'client_id='
|
||||||
|
f'{Constraints.client_id}&redirect_uri=http://127.0.0.1:8080/authenticate&'
|
||||||
|
f'response_type=code&state={state}')
|
||||||
|
response.set_cookie(key='state', value=state)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/authenticate')
|
||||||
|
async def get_auth(request: Request, state: str = Cookie(None)):
|
||||||
|
params = request.query_params
|
||||||
|
if state is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html',
|
||||||
|
{'request': request,
|
||||||
|
'error': '`state` security code not found...',
|
||||||
|
'url': '/login'})
|
||||||
|
if params['state'] != state:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html',
|
||||||
|
{'request': request,
|
||||||
|
'error': '`state` security code is wrong',
|
||||||
|
'url': '/login'})
|
||||||
|
url = "https://login.wownero.com/auth/realms/master/protocol/openid-connect/token"
|
||||||
|
data = {
|
||||||
|
"grant_type": "authorization_code",
|
||||||
|
"code": params["code"],
|
||||||
|
"redirect_uri": "http://127.0.0.1:8080/authenticate",
|
||||||
|
"client_id": f'{Constraints.client_id}',
|
||||||
|
"client_secret": f'{Constraints.client_secret}',
|
||||||
|
"state": params['state']
|
||||||
|
}
|
||||||
|
r = requests.post(url=url, data=data)
|
||||||
|
response = r.json()
|
||||||
|
|
||||||
|
if response.get('error', None) is not None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html',
|
||||||
|
{'request': request, 'error': r.json()['error_description'],
|
||||||
|
'url': '/login'})
|
||||||
|
auth_code = response.get('access_token', None)
|
||||||
|
|
||||||
|
if auth_code is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html',
|
||||||
|
{'request': request, 'error': 'invalid auth code',
|
||||||
|
'url': '/login'})
|
||||||
|
response = RedirectResponse('/dashboard')
|
||||||
|
response.set_cookie(key='auth_code', value=auth_code)
|
||||||
|
response.delete_cookie(key='state')
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/logout')
|
||||||
|
async def get_logout():
|
||||||
|
response = RedirectResponse('/root')
|
||||||
|
response.delete_cookie('auth_code')
|
||||||
|
return response
|
@ -0,0 +1,63 @@
|
|||||||
|
from fastapi import APIRouter, Request, Cookie, Form
|
||||||
|
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
|
import requests
|
||||||
|
from icecream import ic
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
from .db import Database
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/dashboard', response_class=HTMLResponse)
|
||||||
|
async def get_dashboard(request: Request, auth_code: str = Cookie(None)):
|
||||||
|
if auth_code is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'not logged in',
|
||||||
|
'url': '/login'})
|
||||||
|
|
||||||
|
url = "https://login.wownero.com/auth/realms/master/protocol/openid-connect/userinfo"
|
||||||
|
response = requests.post(url, headers={"Authorization": f"Bearer {auth_code}"})
|
||||||
|
user_profile = response.json()
|
||||||
|
|
||||||
|
if user_profile.get('preferred_username', None) is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'account not found...',
|
||||||
|
'url': '/login'})
|
||||||
|
user_name = user_profile.get('preferred_username', None)
|
||||||
|
return Constraints.templates.TemplateResponse('/dashboard/index.html', {'request': request,
|
||||||
|
'user_data': Database.Users.get_address(user_name)})
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/submit_address')
|
||||||
|
async def post_submit_address(request: Request, auth_code: str = Cookie(None), address: str = Form(None)):
|
||||||
|
if auth_code is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'not logged in',
|
||||||
|
'url': '/login'})
|
||||||
|
if address is None:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'invalid address',
|
||||||
|
'url': '/dashboard'})
|
||||||
|
|
||||||
|
if len(address) != 97:
|
||||||
|
return Constraints.templates.TemplateResponse('/errors/index.html', {'request': request,
|
||||||
|
'error': 'invalid address length',
|
||||||
|
'url': '/dashboard'})
|
||||||
|
|
||||||
|
url = "https://login.wownero.com/auth/realms/master/protocol/openid-connect/userinfo"
|
||||||
|
response = requests.post(url, headers={"Authorization": f"Bearer {auth_code}"})
|
||||||
|
user_name = response.json().get('preferred_username', None)
|
||||||
|
|
||||||
|
if not Database.Users.get_address(user_name):
|
||||||
|
new_user = Database.Users(username=user_name, address=address)
|
||||||
|
Database.sqla.session.add(new_user)
|
||||||
|
Database.sqla.session.commit()
|
||||||
|
return RedirectResponse('/dashboard', status_code=303)
|
||||||
|
|
||||||
|
update_address = Database.Users.query.filter_by(username=user_name).first()
|
||||||
|
update_address.address = address
|
||||||
|
Database.sqla.session.commit()
|
||||||
|
return RedirectResponse('/dashboard', status_code=303)
|
||||||
|
|
||||||
|
|
@ -0,0 +1,54 @@
|
|||||||
|
from flask import Flask
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
from icecream import install, ic
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||||
|
app.config['SQLALCHEMY_DATABASE_URI'] = Constraints.uri
|
||||||
|
db = SQLAlchemy(app, session_options={'autocommit': False})
|
||||||
|
|
||||||
|
|
||||||
|
class Users(db.Model):
|
||||||
|
__tablename__ = 'wowusers'
|
||||||
|
username = db.Column(db.VARCHAR(32), primary_key=True)
|
||||||
|
address = db.Column(db.CHAR(97), nullable=False)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_address(username):
|
||||||
|
user_data = {}
|
||||||
|
try:
|
||||||
|
user_data.update({Users.query.filter_by(username=username).first().username:
|
||||||
|
Users.query.filter_by(username=username).first().address})
|
||||||
|
except AttributeError:
|
||||||
|
return user_data
|
||||||
|
return user_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all():
|
||||||
|
users_data = {}
|
||||||
|
for user in Users.query.all():
|
||||||
|
users_data.update({user.username: user.address})
|
||||||
|
return users_data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_matches(username):
|
||||||
|
user_data = {}
|
||||||
|
try:
|
||||||
|
for i in range(3):
|
||||||
|
user = Users.query.filter(Database.Users.username.like(f'%{username}%'))[i]
|
||||||
|
user_data.update({user.username: user.address})
|
||||||
|
except IndexError:
|
||||||
|
return user_data
|
||||||
|
return user_data
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
Users = Users
|
||||||
|
sqla = db
|
||||||
|
|
||||||
|
|
||||||
|
db.create_all()
|
||||||
|
ic('db done')
|
@ -0,0 +1,2 @@
|
|||||||
|
# stupid fastapi error handling drove me crazy for some hours, updating this asap
|
||||||
|
# the handler is in the main file
|
@ -0,0 +1,39 @@
|
|||||||
|
from fastapi import APIRouter, Request
|
||||||
|
from fastapi.responses import RedirectResponse, HTMLResponse
|
||||||
|
from icecream import ic
|
||||||
|
|
||||||
|
from classes.constraints import Constraints
|
||||||
|
from .db import Database
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/yellwowpage')
|
||||||
|
async def get_yellwowpage(request: Request):
|
||||||
|
return Constraints.templates.TemplateResponse('/yellwow/index.html',
|
||||||
|
{'request': request, 'user_data': Database.Users.get_all()})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/yellwowpage/user/{username}')
|
||||||
|
async def get_yellwowpage(request: Request, username: str):
|
||||||
|
return Constraints.templates.TemplateResponse('/yellwow/single_user/index.html',
|
||||||
|
{'request': request,
|
||||||
|
'user_data': Database.Users.get_address(username)})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/yellwowpage/matches/{username}')
|
||||||
|
async def get_yellwowpage_matches(request: Request, username: str):
|
||||||
|
ic(Database.Users.get_matches(username))
|
||||||
|
return Constraints.templates.TemplateResponse('/yellwow/single_user/index.html',
|
||||||
|
{'request': request,
|
||||||
|
'user_data': Database.Users.get_matches(username)})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/about')
|
||||||
|
async def get_about(request: Request):
|
||||||
|
return Constraints.templates.TemplateResponse('/about/index.html', {'request': request})
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/about/api')
|
||||||
|
async def get_api_about(request: Request):
|
||||||
|
return Constraints.templates.TemplateResponse('/about/api/index.html', {'request': request})
|
@ -1,5 +0,0 @@
|
|||||||
from yellow.factory import create_app
|
|
||||||
import settings
|
|
||||||
|
|
||||||
app = create_app()
|
|
||||||
app.run(settings.HOST, port=settings.PORT, debug=settings.DEBUG, use_reloader=False)
|
|
@ -1,23 +0,0 @@
|
|||||||
import os
|
|
||||||
cwd = os.path.dirname(os.path.realpath(__file__))
|
|
||||||
|
|
||||||
|
|
||||||
def bool_env(val):
|
|
||||||
return val is True or (isinstance(val, str) and (val.lower() == 'true' or val == '1'))
|
|
||||||
|
|
||||||
|
|
||||||
DEBUG = bool_env(os.environ.get("DEBUG", False))
|
|
||||||
HOST = os.environ.get("HOST", "127.0.0.1")
|
|
||||||
PORT = int(os.environ.get("PORT", 8080))
|
|
||||||
APP_SECRET = os.environ.get("APP_SECRET")
|
|
||||||
|
|
||||||
REDIS_URI = os.environ.get("REDIS_URI", "redis://localhost:6379")
|
|
||||||
|
|
||||||
DB_PATH = os.path.join(cwd, "data", "db.sqlite3")
|
|
||||||
|
|
||||||
OPENID_CFG = {
|
|
||||||
"client_id": "",
|
|
||||||
"client_secret": "",
|
|
||||||
"configuration": "https://login.wownero.com/auth/realms/master/.well-known/openid-configuration"
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
|||||||
from quart import session, abort
|
|
||||||
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
|
|
||||||
def login_required(func):
|
|
||||||
@wraps(func)
|
|
||||||
async def wrapper(*args, **kwargs):
|
|
||||||
user = session.get('user')
|
|
||||||
if not isinstance(user, dict):
|
|
||||||
abort(403)
|
|
||||||
return await func(*args, **kwargs)
|
|
||||||
return wrapper
|
|
@ -1,27 +0,0 @@
|
|||||||
from quart import render_template, request, redirect, url_for, jsonify, Blueprint, abort, flash, send_from_directory, current_app
|
|
||||||
|
|
||||||
import settings
|
|
||||||
from yellow.models import User
|
|
||||||
|
|
||||||
bp_api = Blueprint('bp_api', __name__, url_prefix='/api')
|
|
||||||
|
|
||||||
|
|
||||||
@bp_api.get("/")
|
|
||||||
async def api_root():
|
|
||||||
return await render_template('api.html')
|
|
||||||
|
|
||||||
|
|
||||||
@bp_api.get('/user/')
|
|
||||||
async def api_all():
|
|
||||||
q = User.select()
|
|
||||||
q = q.where(User.address.is_null(False))
|
|
||||||
return jsonify([u.to_json(ignore_key='id') for u in q])
|
|
||||||
|
|
||||||
|
|
||||||
@bp_api.get('/user/<path:needle>')
|
|
||||||
async def api_search(needle: str):
|
|
||||||
try:
|
|
||||||
return jsonify([u.to_json(ignore_key='id') for u in await User.search(needle)])
|
|
||||||
except Exception as ex:
|
|
||||||
current_app.logger.error(ex)
|
|
||||||
return jsonify([])
|
|
@ -1,34 +0,0 @@
|
|||||||
import re
|
|
||||||
|
|
||||||
import peewee
|
|
||||||
from quart import session, redirect, url_for, current_app
|
|
||||||
from quart_keycloak import Keycloak, KeycloakAuthToken, KeycloakLogoutRequest
|
|
||||||
from yellow.factory import keycloak
|
|
||||||
from yellow.models import User
|
|
||||||
|
|
||||||
|
|
||||||
@keycloak.after_login()
|
|
||||||
async def handle_user_login(auth_token: KeycloakAuthToken):
|
|
||||||
username = auth_token.username
|
|
||||||
uid = auth_token.sub
|
|
||||||
|
|
||||||
if not re.match(r"^[a-zA-Z0-9_\.-]+$", username):
|
|
||||||
raise Exception("bad username")
|
|
||||||
|
|
||||||
try:
|
|
||||||
user = User.select().where(User.username == username).get()
|
|
||||||
except Exception as ex:
|
|
||||||
user = None
|
|
||||||
|
|
||||||
if not user:
|
|
||||||
# create new user if it does not exist yet
|
|
||||||
current_app.logger.info(f'User {username} not found, creating')
|
|
||||||
try:
|
|
||||||
user = User.create(id=uid, username=username)
|
|
||||||
except Exception as ex:
|
|
||||||
current_app.logger.debug(f'User {username}, creation error')
|
|
||||||
raise
|
|
||||||
|
|
||||||
# user is now logged in
|
|
||||||
session['user'] = user.to_json()
|
|
||||||
return redirect(url_for('bp_routes.dashboard'))
|
|
@ -1,84 +0,0 @@
|
|||||||
import os
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
from quart import Quart, url_for, jsonify, render_template, session
|
|
||||||
from quart_session import Session
|
|
||||||
from quart_keycloak import Keycloak, KeycloakAuthToken, KeycloakLogoutRequest
|
|
||||||
from uvicorn.middleware.proxy_headers import ProxyHeadersMiddleware
|
|
||||||
import settings
|
|
||||||
|
|
||||||
|
|
||||||
app: Quart = None
|
|
||||||
peewee = None
|
|
||||||
cache = None
|
|
||||||
keycloak = None
|
|
||||||
|
|
||||||
|
|
||||||
async def _setup_database(app: Quart):
|
|
||||||
import peewee
|
|
||||||
import yellow.models
|
|
||||||
models = peewee.Model.__subclasses__()
|
|
||||||
for m in models:
|
|
||||||
m.create_table()
|
|
||||||
|
|
||||||
|
|
||||||
async def _setup_openid(app: Quart):
|
|
||||||
global keycloak
|
|
||||||
keycloak = Keycloak(app, **settings.OPENID_CFG)
|
|
||||||
from yellow.auth import handle_user_login
|
|
||||||
|
|
||||||
|
|
||||||
async def _setup_cache(app: Quart):
|
|
||||||
global cache
|
|
||||||
app.config['SESSION_TYPE'] = 'redis'
|
|
||||||
app.config['SESSION_URI'] = settings.REDIS_URI
|
|
||||||
Session(app)
|
|
||||||
|
|
||||||
|
|
||||||
async def _setup_error_handlers(app: Quart):
|
|
||||||
@app.errorhandler(500)
|
|
||||||
async def page_error(e):
|
|
||||||
return await render_template('error.html', code=500, msg="Error occurred"), 500
|
|
||||||
|
|
||||||
@app.errorhandler(403)
|
|
||||||
async def page_forbidden(e):
|
|
||||||
return await render_template('error.html', code=403, msg="Forbidden"), 403
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
async def page_not_found(e):
|
|
||||||
return await render_template('error.html', code=404, msg="Page not found"), 404
|
|
||||||
|
|
||||||
|
|
||||||
def create_app():
|
|
||||||
global app
|
|
||||||
app = Quart(__name__)
|
|
||||||
if settings.X_FORWARDED:
|
|
||||||
app.asgi_app = ProxyHeadersMiddleware(app.asgi_app, trusted_hosts=["127.0.0.1", "10.1.0.1"])
|
|
||||||
|
|
||||||
app.logger.setLevel(logging.INFO)
|
|
||||||
app.secret_key = settings.APP_SECRET
|
|
||||||
|
|
||||||
@app.context_processor
|
|
||||||
def template_variables():
|
|
||||||
from yellow.models import User
|
|
||||||
current_user = session.get('user')
|
|
||||||
if current_user:
|
|
||||||
current_user = User(**current_user)
|
|
||||||
now = datetime.now()
|
|
||||||
return dict(user=current_user, url_login=keycloak.endpoint_name_login, year=now.year)
|
|
||||||
|
|
||||||
@app.before_serving
|
|
||||||
async def startup():
|
|
||||||
await _setup_cache(app)
|
|
||||||
await _setup_openid(app)
|
|
||||||
await _setup_database(app)
|
|
||||||
await _setup_error_handlers(app)
|
|
||||||
|
|
||||||
from yellow.routes import bp_routes
|
|
||||||
from yellow.api import bp_api
|
|
||||||
app.register_blueprint(bp_routes)
|
|
||||||
app.register_blueprint(bp_api)
|
|
||||||
|
|
||||||
return app
|
|
@ -1,46 +0,0 @@
|
|||||||
import os, re, random
|
|
||||||
from typing import Optional, List
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from peewee import SqliteDatabase, SQL, ForeignKeyField
|
|
||||||
import peewee as pw
|
|
||||||
|
|
||||||
import settings
|
|
||||||
|
|
||||||
db = SqliteDatabase(settings.DB_PATH)
|
|
||||||
|
|
||||||
|
|
||||||
class User(pw.Model):
|
|
||||||
id = pw.UUIDField(primary_key=True)
|
|
||||||
created: datetime = pw.DateTimeField(default=datetime.now)
|
|
||||||
username = pw.CharField(unique=True, null=False)
|
|
||||||
address = pw.CharField(null=True)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def created_dt(self):
|
|
||||||
return self.created.strftime('%Y-%m-%d')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
async def search(needle) -> List['User']:
|
|
||||||
if not needle:
|
|
||||||
raise Exception("need search term")
|
|
||||||
needle = needle.replace("*", "")
|
|
||||||
needle = needle.lower()
|
|
||||||
|
|
||||||
return User.select().where(
|
|
||||||
User.address.is_null(False),
|
|
||||||
User.username % f"*{needle}*"
|
|
||||||
)
|
|
||||||
|
|
||||||
def to_json(self, ignore_key=None):
|
|
||||||
data = {
|
|
||||||
"id": self.id,
|
|
||||||
"username": self.username,
|
|
||||||
"address": self.address
|
|
||||||
}
|
|
||||||
if isinstance(ignore_key, str):
|
|
||||||
data.pop(ignore_key)
|
|
||||||
return data
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
database = db
|
|
@ -1,100 +0,0 @@
|
|||||||
from quart import render_template, request, redirect, url_for, jsonify, Blueprint, abort, flash, send_from_directory, session
|
|
||||||
import re
|
|
||||||
|
|
||||||
from yellow import login_required
|
|
||||||
from yellow.models import User
|
|
||||||
|
|
||||||
bp_routes = Blueprint('bp_routes', __name__)
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.get("/")
|
|
||||||
async def root():
|
|
||||||
return await render_template('index.html')
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/login")
|
|
||||||
async def login():
|
|
||||||
from yellow.factory import keycloak
|
|
||||||
return redirect(url_for(keycloak.endpoint_name_login))
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/logout")
|
|
||||||
@login_required
|
|
||||||
async def logout():
|
|
||||||
session['user'] = None
|
|
||||||
return redirect(url_for('bp_routes.root'))
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/dashboard")
|
|
||||||
@login_required
|
|
||||||
async def dashboard():
|
|
||||||
return await render_template('dashboard.html')
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.post("/dashboard/address")
|
|
||||||
@login_required
|
|
||||||
async def dashboard_address_post():
|
|
||||||
form = await request.form
|
|
||||||
address = form.get('address')
|
|
||||||
if len(address) != 97:
|
|
||||||
raise Exception("Please submit a WOW address")
|
|
||||||
|
|
||||||
# update user
|
|
||||||
from yellow.models import User
|
|
||||||
user = User.select().filter(User.id == session['user']['id']).get()
|
|
||||||
user.address = address
|
|
||||||
user.save()
|
|
||||||
session['user'] = user.to_json()
|
|
||||||
|
|
||||||
return await render_template('dashboard.html')
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.post("/dashboard/address/delete")
|
|
||||||
@login_required
|
|
||||||
async def dashboard_address_delete():
|
|
||||||
from yellow.models import User
|
|
||||||
user = User.select().filter(User.id == session['user']['id']).get()
|
|
||||||
user.address = None
|
|
||||||
user.save()
|
|
||||||
session['user'] = user.to_json()
|
|
||||||
return redirect(url_for("bp_routes.dashboard"))
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/search")
|
|
||||||
async def search():
|
|
||||||
needle = request.args.get('username')
|
|
||||||
if needle:
|
|
||||||
users = [u for u in await User.search(needle)]
|
|
||||||
if users:
|
|
||||||
return await render_template('search_results.html', users=users)
|
|
||||||
else:
|
|
||||||
return await render_template('search_results.html')
|
|
||||||
|
|
||||||
q = User.select()
|
|
||||||
q = q.where(User.address.is_null(False))
|
|
||||||
q = q.limit(100)
|
|
||||||
|
|
||||||
users = [u for u in q]
|
|
||||||
return await render_template('search.html', users=users)
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/user/<path:name>")
|
|
||||||
async def user_page(name: str):
|
|
||||||
if not name:
|
|
||||||
raise Exception("invalid name")
|
|
||||||
name = name.lower()
|
|
||||||
|
|
||||||
try:
|
|
||||||
_user = User.select().where(
|
|
||||||
User.username == name,
|
|
||||||
User.address.is_null(False)
|
|
||||||
).get()
|
|
||||||
except:
|
|
||||||
return abort(404)
|
|
||||||
|
|
||||||
return await render_template('user.html', users=[_user])
|
|
||||||
|
|
||||||
|
|
||||||
@bp_routes.route("/about")
|
|
||||||
async def about():
|
|
||||||
return await render_template('about.html')
|
|
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.1 KiB |
@ -1,49 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - About{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
<h1>About</h1>
|
|
||||||
<p>
|
|
||||||
Search for any Wownero <em>address</em> you want by username and pay
|
|
||||||
the world!
|
|
||||||
<br>
|
|
||||||
This application uses <u>Wownero's Centralized Authentication Service.</u>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Other Wownero related stuff:
|
|
||||||
<br>
|
|
||||||
<a href="https://wownero.org/">WebSite</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://suchwow.xyz">SuchWow</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://git.wownero.com">Official Git</a>
|
|
||||||
<br>
|
|
||||||
<a href="https://discord.com/invite/ykZyAzJhDK">Discord server</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Made by <a href="https://notmtth.xyz">NotMtth</a> and <code>dsc</code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#main{
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
form{
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
kbd{
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,36 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - API{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
<h1>API</h1>
|
|
||||||
<p>
|
|
||||||
Search user: <code><a href="/api/user/dsc" data-tooltip="partial search supported">/api/user/{username}</a></code>
|
|
||||||
<br><br>
|
|
||||||
Get all users: <code><a href="/api/user/">/api/user/</a></code>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#main {
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
height: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
kbd {
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,43 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - Dashboard{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
<article style="min-width: 620px">
|
|
||||||
<Header>Welcome back <em>{{user.username}}</em>!</Header>
|
|
||||||
Current <u>WOW address</u>: <label>
|
|
||||||
{% if user.address %}
|
|
||||||
<mark>{{user.address}}</mark>
|
|
||||||
{% else %}
|
|
||||||
<mark>empty</mark>
|
|
||||||
{% endif %}
|
|
||||||
</label>
|
|
||||||
<footer>
|
|
||||||
Change <u>WOW address</u>:
|
|
||||||
<form action="{{ url_for('bp_routes.dashboard_address_post') }}" method="POST">
|
|
||||||
<input type="text" name="address">
|
|
||||||
<button data-tooltip="Make sure it is correct!">Submit</button>
|
|
||||||
</form>
|
|
||||||
<form action="{{ url_for('bp_routes.dashboard_address_delete') }}" method="POST">
|
|
||||||
<button class="secondary" data-tooltip="Remove address from YelloWOWPages">Delete</button>
|
|
||||||
</form>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</article>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
|
|
||||||
#main {
|
|
||||||
width: 100%;
|
|
||||||
height: 85vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,27 +0,0 @@
|
|||||||
<nav>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<div id="dropdown">
|
|
||||||
<i class="icon icon-menu"></i>
|
|
||||||
|
|
||||||
<div id="dropdowncontent">
|
|
||||||
<a href="{{ url_for('bp_routes.root') }}">Home</a>
|
|
||||||
{% if not user %}
|
|
||||||
<a href="{{ url_for('bp_routes.login') }}">Login</a>
|
|
||||||
{% else %}
|
|
||||||
<a href="{{ url_for('bp_routes.dashboard') }}">My Profile</a>
|
|
||||||
{% endif %}
|
|
||||||
<a href="{{ url_for('bp_routes.search') }}">Search</a>
|
|
||||||
<a href="{{ url_for('bp_routes.about') }}">About</a>
|
|
||||||
<a href="{{ url_for('bp_api.api_root') }}">Api</a>
|
|
||||||
{% if user %}
|
|
||||||
<a href="{{ url_for('bp_routes.logout') }}">Logout</a>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://git.wownero.com/wownero/YellWOWPages"><i class="icon icon-edit"></i></a></li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
@ -1,3 +0,0 @@
|
|||||||
<form action="{{ url_for('bp_routes.search') }}" method="GET">
|
|
||||||
<input type="text" name="username" placeholder="Search for an username...">
|
|
||||||
</form>
|
|
@ -1,11 +0,0 @@
|
|||||||
<div id="addresses">
|
|
||||||
{% for user in users %}
|
|
||||||
<article>
|
|
||||||
<header>
|
|
||||||
<em><a href="{{ url_for('bp_routes.user_page', name=user.username) }}">{{user.username}}</a></em>
|
|
||||||
<small style="float: right">Added: {{ user.created_dt }}</small>
|
|
||||||
</header>
|
|
||||||
<kbd>{{user.address}}</kbd>
|
|
||||||
</article>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
@ -1,24 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - Sex and Drugs in the metaverse{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
<div id="root">
|
|
||||||
<img src="{{ url_for('static', filename='1-ico.svg') }}" alt="">
|
|
||||||
<div style="flex-direction: column;">
|
|
||||||
<div>
|
|
||||||
The first <img src="{{ url_for('static', filename='wownero.png') }}" alt="" id="wow"> addresses library <br>
|
|
||||||
from the community, for the community.
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<a style="text-decoration: none;" href="{{ url_for('bp_routes.search') }}">
|
|
||||||
<button style="max-width:320px;">start searching</button>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,42 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - Yellwow{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
{% include 'includes/search.html' %}
|
|
||||||
|
|
||||||
{% include 'includes/user_results.html' %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#main {
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
form {
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addresses {
|
|
||||||
width: 100%;
|
|
||||||
height: 54vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#addresses::-webkit-scrollbar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
kbd {
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,45 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - User{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
{% include 'includes/search.html' %}
|
|
||||||
|
|
||||||
<br>
|
|
||||||
Result(s): {{users|length}}
|
|
||||||
|
|
||||||
{% if not users %}
|
|
||||||
<br>Nothing found...
|
|
||||||
{% else %}
|
|
||||||
{% include 'includes/user_results.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#main{
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
form{
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
#addresses{
|
|
||||||
width: 100%;
|
|
||||||
height: 50vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
#addresses::-webkit-scrollbar{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
kbd{
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
@ -1,42 +0,0 @@
|
|||||||
{% extends "base.html" %}
|
|
||||||
{% block content %}
|
|
||||||
<div style="display:none">
|
|
||||||
{% block title %}YellWOWPages - User{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="main">
|
|
||||||
{% include 'includes/search.html' %}
|
|
||||||
|
|
||||||
{% if not users %}
|
|
||||||
<br>Nothing found...
|
|
||||||
{% else %}
|
|
||||||
{% include 'includes/user_results.html' %}
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
#main{
|
|
||||||
width: 100%;
|
|
||||||
height: 80vh;
|
|
||||||
display: grid;
|
|
||||||
place-content: center;
|
|
||||||
}
|
|
||||||
form{
|
|
||||||
height: 80px;
|
|
||||||
}
|
|
||||||
#addresses{
|
|
||||||
width: 100%;
|
|
||||||
height: 50vh;
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
#addresses::-webkit-scrollbar{
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
kbd{
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
Loading…
Reference in new issue