chore: first commit
This commit is contained in:
commit
d908d38e75
15
.env.example
Normal file
15
.env.example
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
OAUTH_CLIENT_ID = ""
|
||||||
|
OAUTH_CLIENT_SECRET = ""
|
||||||
|
OAUTH_REDIRECT_URI = "http://localhost:8080/callback"
|
||||||
|
OAUTH_URL_AUTHORIZE = "https://www.bling.com.br/Api/v3/oauth/authorize"
|
||||||
|
OAUTH_URL_ACCESS_TOKEN = "https://www.bling.com.br/Api/v3/oauth/token"
|
||||||
|
|
||||||
|
# DATABASE
|
||||||
|
DB_NAME = 'ajusta_bling'
|
||||||
|
DB_USER = 'ajusta_bling'
|
||||||
|
DB_PASS = 'ajusta_bling'
|
||||||
|
DB_HOST = 'winhost'
|
||||||
|
DB_PORT = '5432'
|
||||||
|
|
||||||
|
WEB_HOST = "0.0.0.0"
|
||||||
|
WEB_PORT = "8080"
|
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
|
|
||||||
|
# C extensions
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Distribution / packaging
|
||||||
|
.Python
|
||||||
|
build/
|
||||||
|
develop-eggs/
|
||||||
|
dist/
|
||||||
|
downloads/
|
||||||
|
eggs/
|
||||||
|
.eggs/
|
||||||
|
lib/
|
||||||
|
lib64/
|
||||||
|
parts/
|
||||||
|
sdist/
|
||||||
|
var/
|
||||||
|
wheels/
|
||||||
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
|
.installed.cfg
|
||||||
|
*.egg
|
||||||
|
MANIFEST
|
||||||
|
|
||||||
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
|
*.manifest
|
||||||
|
*.spec
|
||||||
|
|
||||||
|
# Installer logs
|
||||||
|
pip-log.txt
|
||||||
|
pip-delete-this-directory.txt
|
||||||
|
|
||||||
|
# Unit test / coverage reports
|
||||||
|
htmlcov/
|
||||||
|
.tox/
|
||||||
|
.nox/
|
||||||
|
.coverage
|
||||||
|
.coverage.*
|
||||||
|
.cache
|
||||||
|
nosetests.xml
|
||||||
|
coverage.xml
|
||||||
|
*.cover
|
||||||
|
*.py,cover
|
||||||
|
.hypothesis/
|
||||||
|
.pytest_cache/
|
||||||
|
cover/
|
||||||
|
|
||||||
|
# Translations
|
||||||
|
*.mo
|
||||||
|
*.pot
|
||||||
|
|
||||||
|
# Django stuff:
|
||||||
|
*.log
|
||||||
|
local_settings.py
|
||||||
|
db.sqlite3
|
||||||
|
db.sqlite3-journal
|
||||||
|
|
||||||
|
# Flask stuff:
|
||||||
|
instance/
|
||||||
|
.webassets-cache
|
||||||
|
|
||||||
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
|
|
||||||
|
# Sphinx documentation
|
||||||
|
docs/_build/
|
||||||
|
|
||||||
|
# PyBuilder
|
||||||
|
.pybuilder/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Jupyter Notebook
|
||||||
|
.ipynb_checkpoints
|
||||||
|
|
||||||
|
# IPython
|
||||||
|
profile_default/
|
||||||
|
ipython_config.py
|
||||||
|
|
||||||
|
# pyenv
|
||||||
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
|
# .python-version
|
||||||
|
|
||||||
|
# pipenv
|
||||||
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
|
# install all needed dependencies.
|
||||||
|
#Pipfile.lock
|
||||||
|
|
||||||
|
# poetry
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
|
# commonly ignored for libraries.
|
||||||
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
|
#poetry.lock
|
||||||
|
|
||||||
|
# pdm
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
|
#pdm.lock
|
||||||
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/#use-with-ide
|
||||||
|
.pdm.toml
|
||||||
|
|
||||||
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
|
__pypackages__/
|
||||||
|
|
||||||
|
# Celery stuff
|
||||||
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
|
|
||||||
|
# SageMath parsed files
|
||||||
|
*.sage.py
|
||||||
|
|
||||||
|
# Environments
|
||||||
|
.env
|
||||||
|
.venv
|
||||||
|
env/
|
||||||
|
venv/
|
||||||
|
ENV/
|
||||||
|
env.bak/
|
||||||
|
venv.bak/
|
||||||
|
|
||||||
|
# Spyder project settings
|
||||||
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
|
|
||||||
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
|
|
||||||
|
# mkdocs documentation
|
||||||
|
/site
|
||||||
|
|
||||||
|
# mypy
|
||||||
|
.mypy_cache/
|
||||||
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
|
|
||||||
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
|
|
||||||
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
|
|
||||||
|
# Cython debug symbols
|
||||||
|
cython_debug/
|
||||||
|
|
||||||
|
# PyCharm
|
||||||
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
|
|
||||||
|
# Vscode
|
||||||
|
.vscode/
|
0
ajusta_bling/__init__.py
Normal file
0
ajusta_bling/__init__.py
Normal file
125
ajusta_bling/__main__.py
Normal file
125
ajusta_bling/__main__.py
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from typing import Sequence
|
||||||
|
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
"""
|
||||||
|
This is always the entry point so you don't technically need to do the
|
||||||
|
__name__ == "__main__" check, but its still common practice to show that
|
||||||
|
this is the entry point.
|
||||||
|
|
||||||
|
You would start your project/app by first installing it.
|
||||||
|
You can install it as an editable package during development as this symlinks it instead.
|
||||||
|
Example (in venv):
|
||||||
|
pip install -e .
|
||||||
|
python -m ajusta_bling args...
|
||||||
|
"""
|
||||||
|
|
||||||
|
class EnvDefault(argparse.Action):
|
||||||
|
def __init__(self, envvar, required=False, default=None, **kwargs):
|
||||||
|
if not default and envvar:
|
||||||
|
if envvar in os.environ:
|
||||||
|
default = os.environ[envvar]
|
||||||
|
|
||||||
|
if required and default:
|
||||||
|
required = False
|
||||||
|
super(EnvDefault, self).__init__(default=default, required=required,
|
||||||
|
**kwargs)
|
||||||
|
|
||||||
|
def __call__(self, parser, namespace, values, option_string=None):
|
||||||
|
setattr(namespace, self.dest, values)
|
||||||
|
|
||||||
|
def main(argv: Sequence[str] | None = None) -> int:
|
||||||
|
"""
|
||||||
|
This is where I would put argparse (if you want to use this as a callable package with args)
|
||||||
|
If not then I use this as setup.
|
||||||
|
"""
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog=__package__ or "ajusta_bling",
|
||||||
|
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--dbuser", "-du",
|
||||||
|
type=str,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="DB_USER",
|
||||||
|
help="PostgreSQL User",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--dbpassword", "-dP",
|
||||||
|
type=str,
|
||||||
|
action=EnvDefault,
|
||||||
|
envvar="DB_PASS",
|
||||||
|
help="PostgreSQL Password",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--dbhost", "-dh",
|
||||||
|
type=str,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="DB_HOST",
|
||||||
|
help="PostgreSQL Host",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--dbport", "-dp",
|
||||||
|
type=int,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="DB_PORT",
|
||||||
|
help="PostgreSQL Port",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--dbname", "-db",
|
||||||
|
type=str,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="DB_NAME",
|
||||||
|
help="PostgreSQL Database",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--webhost", "-wh",
|
||||||
|
type=str,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="WEB_HOST",
|
||||||
|
help="Web server host",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--webport", "-wp",
|
||||||
|
type=int,
|
||||||
|
action=EnvDefault,
|
||||||
|
required=True,
|
||||||
|
envvar="WEB_PORT",
|
||||||
|
help="Web server port",
|
||||||
|
)
|
||||||
|
|
||||||
|
parser.add_argument(
|
||||||
|
"--debug",
|
||||||
|
type=bool,
|
||||||
|
action=EnvDefault,
|
||||||
|
envvar="WEB_DEBUG",
|
||||||
|
default=False,
|
||||||
|
help="Enable web server debug",
|
||||||
|
)
|
||||||
|
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
from . import app
|
||||||
|
from .app import Args
|
||||||
|
|
||||||
|
args = Args(**vars(args))
|
||||||
|
|
||||||
|
app.main(args)
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
load_dotenv(".env")
|
||||||
|
raise SystemExit(main())
|
10
ajusta_bling/app.py
Normal file
10
ajusta_bling/app.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import database
|
||||||
|
import web
|
||||||
|
|
||||||
|
from ajusta_bling.common import Args
|
||||||
|
from ajusta_bling.database import Database
|
||||||
|
|
||||||
|
|
||||||
|
def main(args: Args):
|
||||||
|
db = Database(args)
|
||||||
|
web.run(args, db)
|
15
ajusta_bling/common.py
Normal file
15
ajusta_bling/common.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Args:
|
||||||
|
dbname: str
|
||||||
|
dbuser: str
|
||||||
|
dbpassword: str
|
||||||
|
dbhost: str
|
||||||
|
dbport: int
|
||||||
|
|
||||||
|
webhost: str
|
||||||
|
webport: int
|
||||||
|
|
||||||
|
debug: bool = True
|
43
ajusta_bling/database/__init__.py
Normal file
43
ajusta_bling/database/__init__.py
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from time import sleep
|
||||||
|
from typing import Generator
|
||||||
|
|
||||||
|
import psycopg2
|
||||||
|
import psycopg2.extensions
|
||||||
|
import psycopg2.pool
|
||||||
|
|
||||||
|
from ajusta_bling.common import Args
|
||||||
|
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
pool: psycopg2.pool.SimpleConnectionPool | None = None
|
||||||
|
|
||||||
|
def __init__(self, args: Args):
|
||||||
|
self.user = args.dbuser
|
||||||
|
self.pswd = args.dbpassword
|
||||||
|
self.host = args.dbhost
|
||||||
|
self.port = args.dbport
|
||||||
|
self.db = args.dbname
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
self.pool = psycopg2.pool.SimpleConnectionPool(
|
||||||
|
1, 10,
|
||||||
|
user = self.user,
|
||||||
|
password = self.pswd,
|
||||||
|
host = self.host,
|
||||||
|
port = self.port,
|
||||||
|
database = self.db,
|
||||||
|
)
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def get_cur(self) -> Generator[psycopg2.extensions.cursor]:
|
||||||
|
if self.pool is None:
|
||||||
|
self.connect()
|
||||||
|
conn = self.pool.getconn()
|
||||||
|
try:
|
||||||
|
yield conn.cursor()
|
||||||
|
conn.commit()
|
||||||
|
finally:
|
||||||
|
self.pool.putconn(conn)
|
0
ajusta_bling/py.typed
Normal file
0
ajusta_bling/py.typed
Normal file
91
ajusta_bling/web/__init__.py
Normal file
91
ajusta_bling/web/__init__.py
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import secrets
|
||||||
|
from base64 import b64encode
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
import requests
|
||||||
|
from flask import Flask, redirect, render_template, request, session, url_for
|
||||||
|
|
||||||
|
from ajusta_bling.common import Args
|
||||||
|
from ajusta_bling.database import Database
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
app.secret_key = "#^A549639t5@#&$p"
|
||||||
|
db: Database | None = None
|
||||||
|
|
||||||
|
@app.route('/auth')
|
||||||
|
def auth():
|
||||||
|
session["state"] = secrets.token_urlsafe(16)
|
||||||
|
|
||||||
|
return redirect("%(url)s?client_id=%(client_id)s&redirect_uri=%(redirect)s&response_type=code&state=%(state)s" % {
|
||||||
|
"url": getenv('OAUTH_URL_AUTHORIZE'),
|
||||||
|
"client_id": getenv('OAUTH_CLIENT_ID'),
|
||||||
|
"redirect": url_for('callback', _external = True),
|
||||||
|
"state": session["state"]
|
||||||
|
})
|
||||||
|
|
||||||
|
@app.route('/callback', methods = ["GET"])
|
||||||
|
def callback():
|
||||||
|
if request.method != "GET":
|
||||||
|
return "I curse you!", 403
|
||||||
|
|
||||||
|
if request.args.get("state") != session.pop("state", "fartnugget"):
|
||||||
|
return "I banish thee, to the state of Ohio", 403
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
'grant_type': 'authorization_code',
|
||||||
|
'code': request.args.get('code'),
|
||||||
|
}
|
||||||
|
|
||||||
|
header = "Basic " + b64encode(f"{getenv('OAUTH_CLIENT_ID')}:{getenv('OAUTH_CLIENT_SECRET')}".encode()).decode()
|
||||||
|
|
||||||
|
response = requests.post(getenv('OAUTH_URL_ACCESS_TOKEN'),
|
||||||
|
data = payload,
|
||||||
|
headers= {"Authorization": header})
|
||||||
|
data = response.json()
|
||||||
|
print(response.url)
|
||||||
|
print(data)
|
||||||
|
access_token: str = str(data["access_token"])
|
||||||
|
refresh_token: str = str(data["refresh_token"])
|
||||||
|
expires_in: int = int(data["expires_in"])
|
||||||
|
|
||||||
|
with db.get_cur() as cur: # TODO
|
||||||
|
cur.execute(
|
||||||
|
"""
|
||||||
|
INSERT INTO auth.tokens
|
||||||
|
(
|
||||||
|
ip_address,
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
expires_in
|
||||||
|
) VALUES (
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s,
|
||||||
|
%s
|
||||||
|
)
|
||||||
|
""",
|
||||||
|
(
|
||||||
|
request.remote_addr, # ip
|
||||||
|
access_token,
|
||||||
|
refresh_token,
|
||||||
|
str(expires_in),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
#TODO: Store the session in the database
|
||||||
|
#insert_session(payload['access_token'], payload['refresh_token'], payload['expires_in'])
|
||||||
|
return redirect(url_for('index'))
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def index():
|
||||||
|
return "YIPEE"
|
||||||
|
|
||||||
|
def run(args: Args, _db: Database):
|
||||||
|
global db
|
||||||
|
db = _db
|
||||||
|
|
||||||
|
app.run(host = args.webhost, port = args.webport,
|
||||||
|
threaded = True, use_reloader = True, debug = True)
|
11
ajusta_bling/web/templates/main.html
Normal file
11
ajusta_bling/web/templates/main.html
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>bap</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>bap</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
23
pyproject.toml
Normal file
23
pyproject.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=42.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = "--cov=ajusta_bling"
|
||||||
|
testpaths = [
|
||||||
|
"tests",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
mypy_path = "ajusta_bling"
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_any_generics = true
|
||||||
|
ignore_missing_imports = true
|
||||||
|
no_implicit_optional = true
|
||||||
|
show_error_codes = true
|
||||||
|
strict_equality = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_return_any = true
|
||||||
|
warn_unreachable = true
|
||||||
|
warn_unused_configs = true
|
||||||
|
no_implicit_reexport = true
|
15
requirements.txt
Normal file
15
requirements.txt
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
blinker==1.8.2
|
||||||
|
certifi==2024.6.2
|
||||||
|
charset-normalizer==3.3.2
|
||||||
|
click==8.1.7
|
||||||
|
Flask==3.0.3
|
||||||
|
idna==3.7
|
||||||
|
itsdangerous==2.2.0
|
||||||
|
Jinja2==3.1.4
|
||||||
|
MarkupSafe==2.1.5
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
python-dotenv==1.0.1
|
||||||
|
requests==2.32.3
|
||||||
|
urllib3==2.2.1
|
||||||
|
Werkzeug==3.0.3
|
||||||
|
bcrypt==4.1.3
|
40
setup.cfg
Normal file
40
setup.cfg
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
[metadata]
|
||||||
|
name = ajusta_bling
|
||||||
|
description = Ajust the bling
|
||||||
|
version = 0.0.1
|
||||||
|
author = Djonathan de Souza
|
||||||
|
license = GNU
|
||||||
|
license_files = LICENSE
|
||||||
|
platforms = unix, linux, osx, cygwin, win32
|
||||||
|
classifiers =
|
||||||
|
Programming Language :: Python :: 3
|
||||||
|
Programming Language :: Python :: 3 :: Only
|
||||||
|
Programming Language :: Python :: 3.8
|
||||||
|
Programming Language :: Python :: 3.9
|
||||||
|
Programming Language :: Python :: 3.10
|
||||||
|
Programming Language :: Python :: 3.11
|
||||||
|
Programming Language :: Python :: 3.12
|
||||||
|
|
||||||
|
[options]
|
||||||
|
package_dir =
|
||||||
|
=ajusta_bling
|
||||||
|
packages = find:
|
||||||
|
python_requires = >=3.6
|
||||||
|
include_package_data = true
|
||||||
|
|
||||||
|
[options.packages.find]
|
||||||
|
where = ajusta_bling
|
||||||
|
|
||||||
|
[options.extras_require]
|
||||||
|
testing =
|
||||||
|
pytest>=7.0
|
||||||
|
pytest-cov>=3.0
|
||||||
|
mypy>=0.960
|
||||||
|
flake8>=4.0
|
||||||
|
tox>=3.25
|
||||||
|
|
||||||
|
[options.package_data]
|
||||||
|
ajusta_bling = py.typed
|
||||||
|
|
||||||
|
[flake8]
|
||||||
|
max-line-length = 160
|
Loading…
Reference in New Issue
Block a user