moved to single project
This commit is contained in:
51
api/Dockerfile
Normal file
51
api/Dockerfile
Normal file
@ -0,0 +1,51 @@
|
||||
FROM python:latest
|
||||
|
||||
LABEL Maintainer="Wolfgang Hottgenroth wolfgang.hottgenroth@icloud.com"
|
||||
LABEL ImageName="registry.hottis.de/hv2/hv2-api"
|
||||
|
||||
ARG APP_DIR="/opt/app"
|
||||
ARG CONF_DIR="${APP_DIR}/config"
|
||||
|
||||
|
||||
|
||||
RUN \
|
||||
apt update && \
|
||||
apt install -y postgresql-client-common && \
|
||||
pip3 install psycopg2 && \
|
||||
pip3 install dateparser && \
|
||||
pip3 install connexion && \
|
||||
pip3 install connexion[swagger-ui] && \
|
||||
pip3 install uwsgi && \
|
||||
pip3 install flask-cors && \
|
||||
pip3 install python-jose[cryptography] && \
|
||||
pip3 install loguru && \
|
||||
pip3 install Cheetah3
|
||||
|
||||
|
||||
|
||||
RUN \
|
||||
mkdir -p ${APP_DIR} && \
|
||||
mkdir -p ${CONF_DIR} && \
|
||||
useradd -d ${APP_DIR} -u 1000 user
|
||||
|
||||
COPY *.py ${APP_DIR}/
|
||||
COPY openapi.yaml.tmpl ${APP_DIR}/
|
||||
COPY methods.py.tmpl ${APP_DIR}/
|
||||
COPY schema.json ${APP_DIR}/
|
||||
COPY server.ini ${CONF_DIR}/
|
||||
|
||||
WORKDIR ${APP_DIR}
|
||||
VOLUME ${CONF_DIR}
|
||||
|
||||
RUN \
|
||||
python3 generate.py
|
||||
|
||||
USER 1000:1000
|
||||
|
||||
EXPOSE 5000
|
||||
EXPOSE 9191
|
||||
|
||||
CMD [ "uwsgi", "./config/server.ini" ]
|
||||
|
||||
|
||||
|
10
api/ENV.tmp
Normal file
10
api/ENV.tmp
Normal file
@ -0,0 +1,10 @@
|
||||
# copy to ENV and adjust values
|
||||
|
||||
JWT_PUB_KEY="..."
|
||||
|
||||
DB_USER="hv2"
|
||||
DB_PASS="..."
|
||||
DB_HOST="172.16.10.27"
|
||||
DB_NAME="hv2"
|
||||
|
||||
|
27
api/auth.py
Executable file
27
api/auth.py
Executable file
@ -0,0 +1,27 @@
|
||||
from jose import JWTError, jwt
|
||||
import werkzeug
|
||||
import os
|
||||
from loguru import logger
|
||||
import json
|
||||
|
||||
JWT_PUB_KEY = ""
|
||||
try:
|
||||
JWT_PUB_KEY = os.environ["JWT_PUB_KEY"]
|
||||
except KeyError:
|
||||
with open('/opt/app/config/authservice.pub', 'r') as f:
|
||||
JWT_PUB_KEY = f.read()
|
||||
|
||||
|
||||
def decodeToken(token):
|
||||
try:
|
||||
return jwt.decode(token, JWT_PUB_KEY, audience="hv2")
|
||||
except JWTError as e:
|
||||
logger.error("{}".format(e))
|
||||
raise werkzeug.exceptions.Unauthorized()
|
||||
|
||||
|
||||
def testToken(user, token_info):
|
||||
return {
|
||||
"message": f"You are user_id {user} and the provided token has been signed by this issuers. Fine.",
|
||||
"details": json.dumps(token_info)
|
||||
}
|
103
api/db.py
Normal file
103
api/db.py
Normal file
@ -0,0 +1,103 @@
|
||||
import psycopg2
|
||||
import psycopg2.extras
|
||||
from loguru import logger
|
||||
import os
|
||||
import configparser
|
||||
import json
|
||||
import werkzeug
|
||||
|
||||
|
||||
|
||||
DB_USER = ""
|
||||
DB_PASS = ""
|
||||
DB_HOST = ""
|
||||
DB_NAME = ""
|
||||
try:
|
||||
DB_USER = os.environ["DB_USER"]
|
||||
DB_PASS = os.environ["DB_PASS"]
|
||||
DB_HOST = os.environ["DB_HOST"]
|
||||
DB_NAME = os.environ["DB_NAME"]
|
||||
except KeyError:
|
||||
config = configparser.ConfigParser()
|
||||
config.read('/opt/app/config/dbconfig.ini')
|
||||
DB_USER = config["database"]["user"]
|
||||
DB_PASS = config["database"]["pass"]
|
||||
DB_HOST = config["database"]["host"]
|
||||
DB_NAME = config["database"]["name"]
|
||||
|
||||
|
||||
|
||||
class NoDataFoundException(Exception): pass
|
||||
|
||||
class TooMuchDataFoundException(Exception): pass
|
||||
|
||||
|
||||
def databaseOperation(cursor, params):
|
||||
cursor.execute('SELECT key, value FROM claims_for_user_v where "user" = %s and application = %s',
|
||||
params)
|
||||
for claimObj in cursor:
|
||||
logger.debug("add claim {} -> {}".format(claimObj[0], claimObj[1]))
|
||||
return []
|
||||
|
||||
|
||||
def execDatabaseOperation(func, params):
|
||||
conn = None
|
||||
cur = None
|
||||
try:
|
||||
conn = psycopg2.connect(user = DB_USER, password = DB_PASS,
|
||||
host = DB_HOST, database = DB_NAME,
|
||||
sslmode = 'require')
|
||||
conn.autocommit = False
|
||||
|
||||
with conn.cursor(cursor_factory = psycopg2.extras.RealDictCursor) as cur:
|
||||
return func(cur, params)
|
||||
except psycopg2.Error as err:
|
||||
raise Exception("Error when connecting to database: {}".format(err))
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
|
||||
|
||||
|
||||
def _opGetMany(cursor, params):
|
||||
items = []
|
||||
cursor.execute(params["statement"])
|
||||
for itemObj in cursor:
|
||||
logger.debug("item received {}".format(str(itemObj)))
|
||||
items.append(itemObj)
|
||||
return items
|
||||
|
||||
def dbGetMany(user, token_info, params):
|
||||
logger.info("params: {}, token: {}".format(params, json.dumps(token_info)))
|
||||
try:
|
||||
return execDatabaseOperation(_opGetMany, params)
|
||||
except Exception as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
raise werkzeug.exceptions.InternalServerError
|
||||
|
||||
|
||||
|
||||
def _opGetOne(cursor, params):
|
||||
cursor.execute(params["statement"], params["params"])
|
||||
itemObj = cursor.fetchone()
|
||||
logger.debug(f"item received: {itemObj}")
|
||||
if not itemObj:
|
||||
raise NoDataFoundException
|
||||
dummyObj = cursor.fetchone()
|
||||
if dummyObj:
|
||||
raise TooMuchDataFoundException
|
||||
return itemObj
|
||||
|
||||
def dbGetOne(user, token_info, params):
|
||||
logger.info("params: {}, token: {}".format(params, json.dumps(token_info)))
|
||||
try:
|
||||
return execDatabaseOperation(_opGetOne, params)
|
||||
except NoDataFoundException:
|
||||
logger.error("no data found")
|
||||
raise werkzeug.exceptions.NotFound
|
||||
except TooMuchDataFoundException:
|
||||
logger.error("too much data found")
|
||||
raise werkzeug.exceptions.InternalServerError
|
||||
except Exception as e:
|
||||
logger.error(f"Exception: {e}")
|
||||
raise werkzeug.exceptions.InternalServerError
|
32
api/methods.py.tmpl
Normal file
32
api/methods.py.tmpl
Normal file
@ -0,0 +1,32 @@
|
||||
from db import dbGetMany, dbGetOne
|
||||
|
||||
#for $table in $tables
|
||||
def get_${table.name}s(user, token_info):
|
||||
return dbGetMany(user, token_info, {
|
||||
"statement": """
|
||||
SELECT
|
||||
id
|
||||
#for $column in $table.columns
|
||||
,$column.name
|
||||
#end for
|
||||
FROM ${table.name}_t
|
||||
""",
|
||||
"params": ()
|
||||
}
|
||||
)
|
||||
|
||||
def get_${table.name}(user, token_info, ${table.name}Id=None):
|
||||
return dbGetOne(user, token_info, {
|
||||
"statement": """
|
||||
SELECT
|
||||
id
|
||||
#for $column in $table.columns
|
||||
,$column.name
|
||||
#end for
|
||||
FROM ${table.name}_t
|
||||
WHERE id = %s
|
||||
""",
|
||||
"params": (${table.name}Id, )
|
||||
}
|
||||
)
|
||||
#end for
|
75
api/openapi.yaml.tmpl
Normal file
75
api/openapi.yaml.tmpl
Normal file
@ -0,0 +1,75 @@
|
||||
openapi: 3.0.0
|
||||
info:
|
||||
title: hv2-api
|
||||
version: "1"
|
||||
description: "REST-API for the Nober Grundbesitz GbR Hausverwaltungs-Software"
|
||||
termsOfService: "https://home.hottis.de/dokuwiki/doku.php?id=hv2pub:termsofuse"
|
||||
contact:
|
||||
name: "Wolfgang Hottgenroth"
|
||||
email: "wolfgang.hottgenroth@icloud.com"
|
||||
externalDocs:
|
||||
description: "Find more details here"
|
||||
url: "https://home.hottis.de/dokuwiki/doku.php?id=hv2pub:externaldocs"
|
||||
|
||||
paths:
|
||||
#for $table in $tables
|
||||
/v1/${table.name}s:
|
||||
get:
|
||||
tags: [ "$table.name" ]
|
||||
summary: Return all normalized ${table.name}s
|
||||
operationId: methods.get_${table.name}s
|
||||
responses:
|
||||
'200':
|
||||
description: ${table.name}s response
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
\$ref: '#/components/schemas/$table.name'
|
||||
security:
|
||||
- jwt: ['secret']
|
||||
/v1/${table.name}s/{${table.name}Id}:
|
||||
get:
|
||||
tags: [ "$table.name" ]
|
||||
summary: Return the normalized $table.name with given id
|
||||
operationId: methods.get_$table.name
|
||||
parameters:
|
||||
- name: ${table.name}Id
|
||||
in: path
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: $table.name response
|
||||
content:
|
||||
'application/json':
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
\$ref: '#/components/schemas/$table.name'
|
||||
security:
|
||||
- jwt: ['secret']
|
||||
#end for
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
jwt:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
x-bearerInfoFunc: auth.decodeToken
|
||||
schemas:
|
||||
#for $table in $tables
|
||||
$table.name:
|
||||
description: $table.name
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
#for $column in $table.columns
|
||||
$column.name:
|
||||
type: $column.apitype
|
||||
#end for
|
||||
#end for
|
14
api/run.sh
Executable file
14
api/run.sh
Executable file
@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
. ENV
|
||||
|
||||
IMAGE_NAME="registry.hottis.de/hv2/hv2-api"
|
||||
VERSION=0.0.x
|
||||
|
||||
|
||||
docker run \
|
||||
-d \
|
||||
--rm \
|
||||
--name "hv2-api" \
|
||||
-p 5000:5000
|
||||
${IMAGE_NAME}:${VERSION}
|
6
api/server.ini
Normal file
6
api/server.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[uwsgi]
|
||||
http = :5000
|
||||
wsgi-file = server.py
|
||||
processes = 4
|
||||
stats = :9191
|
||||
|
12
api/server.py
Normal file
12
api/server.py
Normal file
@ -0,0 +1,12 @@
|
||||
import connexion
|
||||
from flask_cors import CORS
|
||||
|
||||
# instantiate the webservice
|
||||
app = connexion.App(__name__)
|
||||
app.add_api('openapi.yaml')
|
||||
|
||||
# CORSify it - otherwise Angular won't accept it
|
||||
CORS(app.app)
|
||||
|
||||
# provide the webservice application to uwsgi
|
||||
application = app.app
|
8
api/test.py
Normal file
8
api/test.py
Normal file
@ -0,0 +1,8 @@
|
||||
import connexion
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
app = connexion.App('hv2-api')
|
||||
app.add_api('./openapi.yaml')
|
||||
app.run(port=8080)
|
Reference in New Issue
Block a user