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