removed v2/v1 division, working on user account system and file uploads
parent
1cb14d82df
commit
11260d9372
|
|
@ -13,3 +13,4 @@ env_win/
|
||||||
velconnect.db
|
velconnect.db
|
||||||
velconnect*.db
|
velconnect*.db
|
||||||
.idea/
|
.idea/
|
||||||
|
velconnect/data/
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,28 @@ CREATE TABLE `UserCount` (
|
||||||
`platform` VARCHAR(64),
|
`platform` VARCHAR(64),
|
||||||
PRIMARY KEY (`timestamp`, `hw_id`)
|
PRIMARY KEY (`timestamp`, `hw_id`)
|
||||||
);
|
);
|
||||||
|
CREATE TABLE `User` (
|
||||||
|
-- TODO user is defined by uuid, to which an email can be added without having to migrate.
|
||||||
|
-- then the data that is coming from a user vs device is constant
|
||||||
|
-- the user's email
|
||||||
|
`email` TEXT NOT NULL PRIMARY KEY,
|
||||||
|
`username` TEXT,
|
||||||
|
-- the first time this device was seen
|
||||||
|
`date_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
-- the last time this device data was modified
|
||||||
|
`last_modified` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
-- JSON containing arbitrary data
|
||||||
|
`data` TEXT
|
||||||
|
);
|
||||||
|
CREATE TABLE `UserDevice` (
|
||||||
|
-- Unique identifier for the device
|
||||||
|
`hw_id` TEXT NOT NULL,
|
||||||
|
-- the user's email
|
||||||
|
`email` TEXT NOT NULL,
|
||||||
|
-- when this connection was created
|
||||||
|
`date_created` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY (`hw_id`, `email`)
|
||||||
|
);
|
||||||
CREATE TABLE `Device` (
|
CREATE TABLE `Device` (
|
||||||
-- Unique identifier for this device
|
-- Unique identifier for this device
|
||||||
`hw_id` TEXT NOT NULL PRIMARY KEY,
|
`hw_id` TEXT NOT NULL PRIMARY KEY,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM python:3.10
|
FROM python:3.10
|
||||||
WORKDIR /usr/src/velconnect
|
WORKDIR /usr/src/velconnect
|
||||||
COPY ./requirements.txt /usr/src/requirements.txt
|
COPY ./requirements.txt /usr/src/requirements.txt
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
||||||
from fastapi.staticfiles import StaticFiles
|
from fastapi.staticfiles import StaticFiles
|
||||||
|
|
||||||
from routes.api import router as api_router
|
from routes.api import router as api_router
|
||||||
from routes.api_v2 import router as api_v2_router
|
|
||||||
from routes.user_count import router as user_count_router
|
from routes.user_count import router as user_count_router
|
||||||
from routes.user_count_v2 import router as user_count_v2_router
|
|
||||||
from routes.oculus_api import router as oculus_api_router
|
from routes.oculus_api import router as oculus_api_router
|
||||||
from routes.website import router as website_router
|
from routes.website import router as website_router
|
||||||
|
|
||||||
|
|
@ -17,6 +15,8 @@ origins = [
|
||||||
"https://velconnect.ugavel.com",
|
"https://velconnect.ugavel.com",
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://localhost:8080",
|
"http://localhost:8080",
|
||||||
|
"http://localhost:8000",
|
||||||
|
"http://localhost:8005",
|
||||||
]
|
]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
|
|
@ -30,9 +30,7 @@ app.add_middleware(
|
||||||
app.mount("/static", StaticFiles(directory="static"), name="static")
|
app.mount("/static", StaticFiles(directory="static"), name="static")
|
||||||
|
|
||||||
app.include_router(api_router)
|
app.include_router(api_router)
|
||||||
app.include_router(api_v2_router)
|
|
||||||
app.include_router(user_count_router)
|
app.include_router(user_count_router)
|
||||||
app.include_router(user_count_v2_router)
|
|
||||||
app.include_router(oculus_api_router)
|
app.include_router(oculus_api_router)
|
||||||
app.include_router(website_router)
|
app.include_router(website_router)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
docker build -t velconnect .
|
docker build --tag velconnect .
|
||||||
docker rm web
|
docker rm web
|
||||||
docker run -p 8081:80 --name web velconnect
|
docker run -p 8081:80 --name web velconnect
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,3 +4,5 @@ uvicorn
|
||||||
pymysql
|
pymysql
|
||||||
pyppeteer
|
pyppeteer
|
||||||
jinja2
|
jinja2
|
||||||
|
python-multipart
|
||||||
|
aiofiles
|
||||||
|
|
@ -1,34 +1,23 @@
|
||||||
from fastapi import APIRouter
|
import secrets
|
||||||
from fastapi import Depends, HTTPException, status
|
import json
|
||||||
|
import string
|
||||||
|
import aiofiles
|
||||||
|
|
||||||
|
import fastapi
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse
|
||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi import FastAPI, File, UploadFile
|
||||||
from pydantic import BaseModel
|
|
||||||
|
|
||||||
import db
|
import db
|
||||||
|
|
||||||
db = db.DB("velconnect.db")
|
db = db.DB("velconnect.db")
|
||||||
|
|
||||||
# APIRouter creates path operations for user module
|
# APIRouter creates path operations for user module
|
||||||
router = APIRouter(
|
router = fastapi.APIRouter(
|
||||||
prefix="/api",
|
prefix="/api",
|
||||||
tags=["API"],
|
tags=["API"],
|
||||||
responses={404: {"description": "Not found"}},
|
responses={404: {"description": "Not found"}},
|
||||||
)
|
)
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(
|
|
||||||
tokenUrl="token") # use token authentication
|
|
||||||
|
|
||||||
|
|
||||||
def api_key_auth(api_key: str = Depends(oauth2_scheme)):
|
|
||||||
return True
|
|
||||||
values = db.query(
|
|
||||||
"SELECT * FROM `APIKey` WHERE `key`=:key;", {'key': api_key})
|
|
||||||
if not (len(values) > 0 and values['auth_level'] < 0):
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
|
||||||
detail="Forbidden"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse, include_in_schema=False)
|
@router.get("/", response_class=HTMLResponse, include_in_schema=False)
|
||||||
async def read_root():
|
async def read_root():
|
||||||
|
|
@ -36,7 +25,7 @@ async def read_root():
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
|
<meta charset="utf-8">
|
||||||
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
||||||
<title>API Reference</title>
|
<title>API Reference</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
@ -58,138 +47,204 @@ async def read_root():
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_all_headsets')
|
def parse_device(device: dict):
|
||||||
def get_all_headsets():
|
if 'data' in device and device['data'] is not None and len(device['data']) > 0:
|
||||||
"""Returns a list of all headsets and details associated with them."""
|
device['data'] = json.loads(device['data'])
|
||||||
values = db.query("SELECT * FROM `Headset`;")
|
|
||||||
|
|
||||||
|
@router.get('/get_all_devices')
|
||||||
|
def get_all_devices():
|
||||||
|
"""Returns a list of all devices and details associated with them."""
|
||||||
|
values = db.query("SELECT * FROM `Device`;")
|
||||||
|
values = [dict(v) for v in values]
|
||||||
|
for device in values:
|
||||||
|
parse_device(device)
|
||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
@router.get('/pair_headset/{pairing_code}')
|
@router.get('/get_device_by_pairing_code/{pairing_code}')
|
||||||
def pair_headset(pairing_code: str):
|
def get_device_by_pairing_code(pairing_code: str):
|
||||||
values = db.query("SELECT * FROM `Headset` WHERE `pairing_code`=:pairing_code;",
|
values = db.query("SELECT * FROM `Device` WHERE `pairing_code`=:pairing_code;",
|
||||||
{'pairing_code': pairing_code})
|
{'pairing_code': pairing_code})
|
||||||
if len(values) == 1:
|
if len(values) == 1:
|
||||||
return values[0]
|
device = dict(values[0])
|
||||||
|
parse_device(device)
|
||||||
|
return device
|
||||||
return {'error': 'Not found'}, 400
|
return {'error': 'Not found'}, 400
|
||||||
|
|
||||||
|
|
||||||
class UpdatePairingCode(BaseModel):
|
def create_device(hw_id: str):
|
||||||
hw_id: str
|
|
||||||
pairing_code: int
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/update_pairing_code')
|
|
||||||
def update_pairing_code(data: UpdatePairingCode):
|
|
||||||
"""This also creates a headset if it doesn't exist"""
|
|
||||||
|
|
||||||
print("Update pairing code")
|
|
||||||
print(data)
|
|
||||||
|
|
||||||
create_headset(data.hw_id)
|
|
||||||
|
|
||||||
db.insert("""
|
db.insert("""
|
||||||
UPDATE `Headset`
|
INSERT OR IGNORE INTO `Device`(hw_id) VALUES (:hw_id);
|
||||||
SET `pairing_code`=:pairing_code, `last_used`=CURRENT_TIMESTAMP
|
|
||||||
WHERE `hw_id`=:hw_id;
|
|
||||||
""", data.dict())
|
|
||||||
|
|
||||||
return {'success': True}
|
|
||||||
|
|
||||||
|
|
||||||
def create_headset(hw_id: str):
|
|
||||||
db.insert("""
|
|
||||||
db.insert IGNORE INTO Headset(hw_id) VALUES (:hw_id);
|
|
||||||
""", {'hw_id': hw_id})
|
""", {'hw_id': hw_id})
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_state/{hw_id}')
|
@router.get('/device/get_data/{hw_id}')
|
||||||
def get_headset_details(hw_id: str):
|
def get_state(request: fastapi.Request, hw_id: str):
|
||||||
data = get_headset_details_db(hw_id)
|
"""Gets the device state"""
|
||||||
if data is None:
|
|
||||||
return {'error': "Can't find headset with that id."}
|
|
||||||
else:
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
devices = db.query("""
|
||||||
def get_headset_details_db(hw_id):
|
SELECT * FROM `Device` WHERE `hw_id`=:hw_id;
|
||||||
headsets = db.query("""
|
|
||||||
SELECT * FROM `Headset` WHERE `hw_id`=:hw_id;
|
|
||||||
""", {'hw_id': hw_id})
|
""", {'hw_id': hw_id})
|
||||||
if len(headsets) == 0:
|
if len(devices) == 0:
|
||||||
return None
|
return {'error': "Can't find device with that id."}
|
||||||
|
block = dict(devices[0])
|
||||||
|
if 'data' in block and block['data'] is not None:
|
||||||
|
block['data'] = json.loads(block['data'])
|
||||||
|
|
||||||
room = get_room_details_db(headsets[0]['current_room'])
|
room_key: str = f"{devices[0]['current_app']}_{devices[0]['current_room']}"
|
||||||
|
room_data = get_data(room_key)
|
||||||
|
|
||||||
return {'user': headsets[0], 'room': room}
|
if "error" in room_data:
|
||||||
|
set_data(request, data={}, key=room_key, modified_by=None, category="room")
|
||||||
|
room_data = get_data(room_key)
|
||||||
|
|
||||||
|
return {'device': block, 'room': room_data}
|
||||||
|
|
||||||
|
|
||||||
@router.post('/set_headset_details/{hw_id}')
|
@router.post('/device/set_data/{hw_id}')
|
||||||
def set_headset_details_generic(hw_id: str, data: dict):
|
def set_state(request: fastapi.Request, hw_id: str, data: dict, modified_by: str = None):
|
||||||
print("Data:")
|
"""Sets the device state"""
|
||||||
print(data)
|
|
||||||
|
|
||||||
# create_headset(hw_id)
|
create_device(hw_id)
|
||||||
|
|
||||||
allowed_keys = [
|
# add the client's IP address if no sender specified
|
||||||
|
if 'modified_by' in data:
|
||||||
|
modified_by = data['modified_by']
|
||||||
|
if modified_by is None:
|
||||||
|
modified_by: str = str(request.client) + "_" + str(request.headers)
|
||||||
|
|
||||||
|
allowed_keys: list[str] = [
|
||||||
|
'os_info',
|
||||||
|
'friendly_name',
|
||||||
|
'current_app',
|
||||||
'current_room',
|
'current_room',
|
||||||
'pairing_code',
|
'pairing_code',
|
||||||
'user_color',
|
|
||||||
'user_name',
|
|
||||||
'avatar_url',
|
|
||||||
'user_details',
|
|
||||||
'streamer_stream_id',
|
|
||||||
'streamer_control_id',
|
|
||||||
]
|
|
||||||
for key in data:
|
|
||||||
if key in allowed_keys:
|
|
||||||
if key == 'current_room':
|
|
||||||
create_room(data['current_room'])
|
|
||||||
db.insert(f"UPDATE `Headset` SET {key}=:value, modified_by=:sender_id WHERE `hw_id`=:hw_id;", {
|
|
||||||
'value': data[key], 'hw_id': hw_id, 'sender_id': data['sender_id']})
|
|
||||||
return {'success': True}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/set_room_details/{room_id}')
|
|
||||||
def set_room_details_generic(room_id: str, data: dict):
|
|
||||||
print(data)
|
|
||||||
allowed_keys = [
|
|
||||||
'modified_by',
|
|
||||||
'whitelist',
|
|
||||||
'tv_url',
|
|
||||||
'carpet_color',
|
|
||||||
'room_details',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
for key in data:
|
for key in data:
|
||||||
if key in allowed_keys:
|
if key in allowed_keys:
|
||||||
db.insert("UPDATE `Room` SET " + key +
|
db.insert(f"""
|
||||||
"=:value, modified_by=:sender_id WHERE `room_id`=:room_id;",
|
UPDATE `Device`
|
||||||
{'value': data[key], 'room_id': room_id, 'sender_id': data['sender_id']})
|
SET {key}=:value,
|
||||||
return {'success': True}
|
last_modified=CURRENT_TIMESTAMP,
|
||||||
|
modified_by=:modified_by
|
||||||
|
WHERE `hw_id`=:hw_id;
|
||||||
|
""",
|
||||||
|
{
|
||||||
|
'value': data[key],
|
||||||
|
'hw_id': hw_id,
|
||||||
|
'modified_by': modified_by
|
||||||
|
})
|
||||||
|
if key == "data":
|
||||||
|
new_data = data['data']
|
||||||
|
# get the old json values and merge the data
|
||||||
|
old_data_query = db.query("""
|
||||||
|
SELECT data
|
||||||
|
FROM `Device`
|
||||||
|
WHERE hw_id=:hw_id
|
||||||
|
""", {"hw_id": hw_id})
|
||||||
|
|
||||||
|
if len(old_data_query) == 1:
|
||||||
|
old_data: dict = {}
|
||||||
|
if old_data_query[0]['data'] is not None:
|
||||||
|
old_data = json.loads(old_data_query[0]["data"])
|
||||||
|
new_data = {**old_data, **new_data}
|
||||||
|
|
||||||
@router.get('/get_room_details/{room_id}')
|
# add the data to the db
|
||||||
def get_room_details(room_id: str):
|
|
||||||
return get_room_details_db(room_id)
|
|
||||||
|
|
||||||
|
|
||||||
def get_room_details_db(room_id):
|
|
||||||
values = db.query("""
|
|
||||||
SELECT * FROM `Room` WHERE room_id=:room_id;
|
|
||||||
""", {'room_id': room_id})
|
|
||||||
if len(values) == 1:
|
|
||||||
return values[0]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def create_room(room_id):
|
|
||||||
db.insert("""
|
db.insert("""
|
||||||
db.insert IGNORE INTO `Room`(room_id)
|
UPDATE `Device`
|
||||||
VALUES(
|
SET data=:data,
|
||||||
:room_id
|
last_modified=CURRENT_TIMESTAMP
|
||||||
);
|
WHERE hw_id=:hw_id;
|
||||||
""", {'room_id': room_id})
|
""", {"hw_id": hw_id, "data": json.dumps(new_data)})
|
||||||
return {'room_id': room_id}
|
return {'success': True}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_id(length: int = 4) -> str:
|
||||||
|
return ''.join(
|
||||||
|
secrets.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for i in range(length))
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/set_data')
|
||||||
|
def set_data_with_random_key(request: fastapi.Request, data: dict, modified_by: str = None,
|
||||||
|
category: str = None) -> dict:
|
||||||
|
"""Creates a little storage bucket for arbitrary data with a random key"""
|
||||||
|
return set_data(request, data, None, modified_by, category)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post('/set_data/{key}')
|
||||||
|
def set_data(request: fastapi.Request, data: dict, key: str = None, modified_by: str = None,
|
||||||
|
category: str = None) -> dict:
|
||||||
|
"""Creates a little storage bucket for arbitrary data"""
|
||||||
|
|
||||||
|
# add the client's IP address if no sender specified
|
||||||
|
if 'modified_by' in data:
|
||||||
|
modified_by = data['modified_by']
|
||||||
|
if modified_by is None:
|
||||||
|
modified_by: str = str(request.client) + "_" + str(request.headers)
|
||||||
|
|
||||||
|
# generates a key if none was supplied
|
||||||
|
if key is None:
|
||||||
|
key = generate_id()
|
||||||
|
|
||||||
|
# regenerate if necessary
|
||||||
|
while len(db.query("SELECT id FROM `DataBlock` WHERE id=:id;", {"id": key})) > 0:
|
||||||
|
key = generate_id()
|
||||||
|
|
||||||
|
# get the old json values and merge the data
|
||||||
|
old_data_query = db.query("""
|
||||||
|
SELECT data
|
||||||
|
FROM `DataBlock`
|
||||||
|
WHERE id=:id
|
||||||
|
""", {"id": key})
|
||||||
|
|
||||||
|
if len(old_data_query) == 1:
|
||||||
|
old_data: dict = json.loads(old_data_query[0]["data"])
|
||||||
|
data = {**old_data, **data}
|
||||||
|
|
||||||
|
# add the data to the db
|
||||||
|
db.insert("""
|
||||||
|
REPLACE INTO `DataBlock` (id, category, modified_by, data, last_modified)
|
||||||
|
VALUES(:id, :category, :modified_by, :data, CURRENT_TIMESTAMP);
|
||||||
|
""", {"id": key, "category": category, "modified_by": modified_by, "data": json.dumps(data)})
|
||||||
|
|
||||||
|
return {'key': key}
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/get_data/{key}')
|
||||||
|
def get_data(key: str) -> dict:
|
||||||
|
"""Gets data from a storage bucket for arbitrary data"""
|
||||||
|
|
||||||
|
data = db.query("""
|
||||||
|
SELECT *
|
||||||
|
FROM `DataBlock`
|
||||||
|
WHERE id=:id
|
||||||
|
""", {"id": key})
|
||||||
|
|
||||||
|
db.insert("""
|
||||||
|
UPDATE `DataBlock`
|
||||||
|
SET last_accessed = CURRENT_TIMESTAMP
|
||||||
|
WHERE id=:id;
|
||||||
|
""", {"id": key})
|
||||||
|
|
||||||
|
try:
|
||||||
|
if len(data) == 1:
|
||||||
|
block = dict(data[0])
|
||||||
|
if 'data' in block and block['data'] is not None:
|
||||||
|
block['data'] = json.loads(block['data'])
|
||||||
|
return block
|
||||||
|
return {'error': 'Not found'}
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return {'error': 'Unknown. Maybe no data at this key.'}
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/upload_file/{key}")
|
||||||
|
async def upload_file(request: fastapi.Request, file: UploadFile, key: str,modified_by: str = None):
|
||||||
|
async with aiofiles.open('data/' + key, 'wb') as out_file:
|
||||||
|
content = await file.read() # async read
|
||||||
|
await out_file.write(content) # async write
|
||||||
|
# add a datablock to link to the file
|
||||||
|
set_data(request, {'filename': file.filename}, key, 'file')
|
||||||
|
return {"filename": file.filename}
|
||||||
|
|
|
||||||
|
|
@ -1,232 +0,0 @@
|
||||||
import secrets
|
|
||||||
import json
|
|
||||||
import string
|
|
||||||
|
|
||||||
import fastapi
|
|
||||||
from fastapi.responses import HTMLResponse
|
|
||||||
|
|
||||||
import db
|
|
||||||
|
|
||||||
db = db.DB("velconnect_v2.db")
|
|
||||||
|
|
||||||
# APIRouter creates path operations for user module
|
|
||||||
router = fastapi.APIRouter(
|
|
||||||
prefix="/api/v2",
|
|
||||||
tags=["API V2"],
|
|
||||||
responses={404: {"description": "Not found"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/", response_class=HTMLResponse, include_in_schema=False)
|
|
||||||
async def read_root():
|
|
||||||
return """
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
|
|
||||||
<title>API Reference</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<rapi-doc
|
|
||||||
render-style = "read"
|
|
||||||
primary-color = "#bc1f2d"
|
|
||||||
show-header = "false"
|
|
||||||
show-info = "true"
|
|
||||||
spec-url = "/openapi.json"
|
|
||||||
default-schema-tab = 'example'
|
|
||||||
>
|
|
||||||
<div slot="nav-logo" style="display: flex; align-items: center; justify-content: center;">
|
|
||||||
<img src = "/static/img/velconnect_logo_1.png" style="width:10em; margin: 2em auto;" />
|
|
||||||
</div>
|
|
||||||
</rapi-doc>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_all_devices')
|
|
||||||
def get_all_devices():
|
|
||||||
"""Returns a list of all devices and details associated with them."""
|
|
||||||
values = db.query("SELECT * FROM `Device`;")
|
|
||||||
values = [dict(v) for v in values]
|
|
||||||
for device in values:
|
|
||||||
if 'data' in device and len(device['data']) > 0:
|
|
||||||
device['data'] = json.loads(device['data'])
|
|
||||||
return values
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_device_by_pairing_code/{pairing_code}')
|
|
||||||
def get_device_by_pairing_code(pairing_code: str):
|
|
||||||
values = db.query("SELECT * FROM `Device` WHERE `pairing_code`=:pairing_code;",
|
|
||||||
{'pairing_code': pairing_code})
|
|
||||||
if len(values) == 1:
|
|
||||||
return values[0]
|
|
||||||
return {'error': 'Not found'}, 400
|
|
||||||
|
|
||||||
|
|
||||||
def create_device(hw_id: str):
|
|
||||||
db.insert("""
|
|
||||||
INSERT OR IGNORE INTO `Device`(hw_id) VALUES (:hw_id);
|
|
||||||
""", {'hw_id': hw_id})
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/device/get_data/{hw_id}')
|
|
||||||
def get_state(request: fastapi.Request, hw_id: str):
|
|
||||||
"""Gets the device state"""
|
|
||||||
|
|
||||||
devices = db.query("""
|
|
||||||
SELECT * FROM `Device` WHERE `hw_id`=:hw_id;
|
|
||||||
""", {'hw_id': hw_id})
|
|
||||||
if len(devices) == 0:
|
|
||||||
return {'error': "Can't find device with that id."}
|
|
||||||
block = dict(devices[0])
|
|
||||||
if 'data' in block and block['data'] is not None:
|
|
||||||
block['data'] = json.loads(block['data'])
|
|
||||||
|
|
||||||
room_key: str = f"{devices[0]['current_app']}_{devices[0]['current_room']}"
|
|
||||||
room_data = get_data(room_key)
|
|
||||||
|
|
||||||
if "error" in room_data:
|
|
||||||
set_data(request, data={}, key=room_key, modified_by=None, category="room")
|
|
||||||
room_data = get_data(room_key)
|
|
||||||
|
|
||||||
return {'device': block, 'room': room_data}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/device/set_data/{hw_id}')
|
|
||||||
def set_state(request: fastapi.Request, hw_id: str, data: dict, modified_by: str = None):
|
|
||||||
"""Sets the device state"""
|
|
||||||
|
|
||||||
create_device(hw_id)
|
|
||||||
|
|
||||||
# add the client's IP address if no sender specified
|
|
||||||
if 'modified_by' in data:
|
|
||||||
modified_by = data['modified_by']
|
|
||||||
if modified_by is None:
|
|
||||||
modified_by: str = str(request.client) + "_" + str(request.headers)
|
|
||||||
|
|
||||||
allowed_keys: list[str] = [
|
|
||||||
'os_info',
|
|
||||||
'friendly_name',
|
|
||||||
'current_app',
|
|
||||||
'current_room',
|
|
||||||
'pairing_code',
|
|
||||||
]
|
|
||||||
|
|
||||||
for key in data:
|
|
||||||
if key in allowed_keys:
|
|
||||||
db.insert(f"""
|
|
||||||
UPDATE `Device`
|
|
||||||
SET {key}=:value,
|
|
||||||
last_modified=CURRENT_TIMESTAMP,
|
|
||||||
modified_by=:modified_by
|
|
||||||
WHERE `hw_id`=:hw_id;
|
|
||||||
""",
|
|
||||||
{
|
|
||||||
'value': data[key],
|
|
||||||
'hw_id': hw_id,
|
|
||||||
'modified_by': modified_by
|
|
||||||
})
|
|
||||||
if key == "data":
|
|
||||||
new_data = data['data']
|
|
||||||
# get the old json values and merge the data
|
|
||||||
old_data_query = db.query("""
|
|
||||||
SELECT data
|
|
||||||
FROM `Device`
|
|
||||||
WHERE hw_id=:hw_id
|
|
||||||
""", {"hw_id": hw_id})
|
|
||||||
|
|
||||||
if len(old_data_query) == 1:
|
|
||||||
old_data: dict = {}
|
|
||||||
if old_data_query[0]['data'] is not None:
|
|
||||||
old_data = json.loads(old_data_query[0]["data"])
|
|
||||||
new_data = {**old_data, **new_data}
|
|
||||||
|
|
||||||
# add the data to the db
|
|
||||||
db.insert("""
|
|
||||||
UPDATE `Device`
|
|
||||||
SET data=:data,
|
|
||||||
last_modified=CURRENT_TIMESTAMP
|
|
||||||
WHERE hw_id=:hw_id;
|
|
||||||
""", {"hw_id": hw_id, "data": json.dumps(new_data)})
|
|
||||||
return {'success': True}
|
|
||||||
|
|
||||||
|
|
||||||
def generate_id(length: int = 4) -> str:
|
|
||||||
return ''.join(
|
|
||||||
secrets.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for i in range(length))
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/set_data')
|
|
||||||
def set_data_with_random_key(request: fastapi.Request, data: dict, modified_by: str = None,
|
|
||||||
category: str = None) -> dict:
|
|
||||||
"""Creates a little storage bucket for arbitrary data with a random key"""
|
|
||||||
return set_data(request, data, None, modified_by, category)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/set_data/{key}')
|
|
||||||
def set_data(request: fastapi.Request, data: dict, key: str = None, modified_by: str = None,
|
|
||||||
category: str = None) -> dict:
|
|
||||||
"""Creates a little storage bucket for arbitrary data"""
|
|
||||||
|
|
||||||
# add the client's IP address if no sender specified
|
|
||||||
if 'modified_by' in data:
|
|
||||||
modified_by = data['modified_by']
|
|
||||||
if modified_by is None:
|
|
||||||
modified_by: str = str(request.client) + "_" + str(request.headers)
|
|
||||||
|
|
||||||
# generates a key if none was supplied
|
|
||||||
if key is None:
|
|
||||||
key = generate_id()
|
|
||||||
|
|
||||||
# regenerate if necessary
|
|
||||||
while len(db.query("SELECT id FROM `DataBlock` WHERE id=:id;", {"id": key})) > 0:
|
|
||||||
key = generate_id()
|
|
||||||
|
|
||||||
# get the old json values and merge the data
|
|
||||||
old_data_query = db.query("""
|
|
||||||
SELECT data
|
|
||||||
FROM `DataBlock`
|
|
||||||
WHERE id=:id
|
|
||||||
""", {"id": key})
|
|
||||||
|
|
||||||
if len(old_data_query) == 1:
|
|
||||||
old_data: dict = json.loads(old_data_query[0]["data"])
|
|
||||||
data = {**old_data, **data}
|
|
||||||
|
|
||||||
# add the data to the db
|
|
||||||
db.insert("""
|
|
||||||
REPLACE INTO `DataBlock` (id, category, modified_by, data, last_modified)
|
|
||||||
VALUES(:id, :category, :modified_by, :data, CURRENT_TIMESTAMP);
|
|
||||||
""", {"id": key, "category": category, "modified_by": modified_by, "data": json.dumps(data)})
|
|
||||||
|
|
||||||
return {'key': key}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_data/{key}')
|
|
||||||
def get_data(key: str) -> dict:
|
|
||||||
"""Gets data from a storage bucket for arbitrary data"""
|
|
||||||
|
|
||||||
data = db.query("""
|
|
||||||
SELECT *
|
|
||||||
FROM `DataBlock`
|
|
||||||
WHERE id=:id
|
|
||||||
""", {"id": key})
|
|
||||||
|
|
||||||
db.insert("""
|
|
||||||
UPDATE `DataBlock`
|
|
||||||
SET last_accessed = CURRENT_TIMESTAMP
|
|
||||||
WHERE id=:id;
|
|
||||||
""", {"id": key})
|
|
||||||
|
|
||||||
try:
|
|
||||||
if len(data) == 1:
|
|
||||||
block = dict(data[0])
|
|
||||||
if 'data' in block and block['data'] is not None:
|
|
||||||
block['data'] = json.loads(block['data'])
|
|
||||||
return block
|
|
||||||
return {'error': 'Not found'}
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return {'error': 'Unknown. Maybe no data at this key.'}
|
|
||||||
|
|
@ -28,27 +28,41 @@ post_user_count_example = {
|
||||||
|
|
||||||
|
|
||||||
@router.post('/update_user_count')
|
@router.post('/update_user_count')
|
||||||
def update_user_count(data: dict):
|
def update_user_count(data: dict = fastapi.Body(..., examples=post_user_count_example)) -> dict:
|
||||||
|
if 'app_id' not in data:
|
||||||
|
data['app_id'] = ""
|
||||||
|
|
||||||
db.insert("""
|
db.insert("""
|
||||||
REPLACE INTO `UserCount`
|
REPLACE INTO `UserCount` (
|
||||||
|
timestamp,
|
||||||
|
hw_id,
|
||||||
|
app_id,
|
||||||
|
room_id,
|
||||||
|
total_users,
|
||||||
|
room_users,
|
||||||
|
version,
|
||||||
|
platform
|
||||||
|
)
|
||||||
VALUES(
|
VALUES(
|
||||||
CURRENT_TIMESTAMP,
|
CURRENT_TIMESTAMP,
|
||||||
%(hw_id)s,
|
:hw_id,
|
||||||
%(room_id)s,
|
:app_id,
|
||||||
%(total_users)s,
|
:room_id,
|
||||||
%(room_users)s,
|
:total_users,
|
||||||
%(version)s,
|
:room_users,
|
||||||
%(platform)s
|
:version,
|
||||||
|
:platform
|
||||||
);
|
);
|
||||||
""", data)
|
""", data)
|
||||||
return {'success': True}
|
return {'success': True}
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_user_count')
|
@router.get('/get_user_count')
|
||||||
def get_user_count(hours: float = 24):
|
def get_user_count(app_id: str = None, hours: float = 24) -> list:
|
||||||
values = db.query("""
|
values = db.query("""
|
||||||
SELECT timestamp, total_users
|
SELECT timestamp, total_users
|
||||||
FROM `UserCount`
|
FROM `UserCount`
|
||||||
WHERE TIMESTAMP > DATE_SUB(NOW(), INTERVAL """ + str(hours) + """ HOUR);
|
WHERE app_id = :app_id AND
|
||||||
""")
|
timestamp > datetime('now', '-""" + str(hours) + """ Hour');
|
||||||
|
""", {"app_id": app_id})
|
||||||
return values
|
return values
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
import fastapi
|
|
||||||
|
|
||||||
import db
|
|
||||||
|
|
||||||
db = db.DB("velconnect_v2.db")
|
|
||||||
|
|
||||||
# APIRouter creates path operations for user module
|
|
||||||
router = fastapi.APIRouter(
|
|
||||||
prefix="/api/v2",
|
|
||||||
tags=["User Count V2"],
|
|
||||||
responses={404: {"description": "Not found"}},
|
|
||||||
)
|
|
||||||
|
|
||||||
post_user_count_example = {
|
|
||||||
"default": {
|
|
||||||
"summary": "Example insert for user count",
|
|
||||||
"value": {
|
|
||||||
"hw_id": "1234",
|
|
||||||
"app_id": "example",
|
|
||||||
"room_id": "0",
|
|
||||||
"total_users": 1,
|
|
||||||
"room_users": 1,
|
|
||||||
"version": "0.1",
|
|
||||||
"platform": "Windows"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@router.post('/update_user_count')
|
|
||||||
def update_user_count(data: dict = fastapi.Body(..., examples=post_user_count_example)) -> dict:
|
|
||||||
if 'app_id' not in data:
|
|
||||||
data['app_id'] = ""
|
|
||||||
|
|
||||||
db.insert("""
|
|
||||||
REPLACE INTO `UserCount` (
|
|
||||||
timestamp,
|
|
||||||
hw_id,
|
|
||||||
app_id,
|
|
||||||
room_id,
|
|
||||||
total_users,
|
|
||||||
room_users,
|
|
||||||
version,
|
|
||||||
platform
|
|
||||||
)
|
|
||||||
VALUES(
|
|
||||||
CURRENT_TIMESTAMP,
|
|
||||||
:hw_id,
|
|
||||||
:app_id,
|
|
||||||
:room_id,
|
|
||||||
:total_users,
|
|
||||||
:room_users,
|
|
||||||
:version,
|
|
||||||
:platform
|
|
||||||
);
|
|
||||||
""", data)
|
|
||||||
return {'success': True}
|
|
||||||
|
|
||||||
|
|
||||||
@router.get('/get_user_count')
|
|
||||||
def get_user_count(app_id: str = None, hours: float = 24) -> list:
|
|
||||||
values = db.query("""
|
|
||||||
SELECT timestamp, total_users
|
|
||||||
FROM `UserCount`
|
|
||||||
WHERE app_id = :app_id AND
|
|
||||||
timestamp > datetime('now', '-""" + str(hours) + """ Hour');
|
|
||||||
""", {"app_id": app_id})
|
|
||||||
return values
|
|
||||||
|
|
@ -29,8 +29,8 @@ def success():
|
||||||
|
|
||||||
|
|
||||||
@router.get('/failure')
|
@router.get('/failure')
|
||||||
def failure():
|
def failure(request: fastapi.Request, code: int = 0):
|
||||||
return FileResponse("templates/failure.html")
|
return templates.TemplateResponse("failure.html", {"request": request, "code": code})
|
||||||
|
|
||||||
|
|
||||||
@router.get('/join/{app_id}/{link}')
|
@router.get('/join/{app_id}/{link}')
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
if (hw_id !== "" && hw_id !== undefined && hw_id !== "undefined") {
|
if (hw_id !== "" && hw_id !== undefined && hw_id !== "undefined") {
|
||||||
|
|
||||||
httpGetAsync('/api/v2/device/get_data/' + hw_id, (resp) => {
|
httpGetAsync('/api/device/get_data/' + hw_id, (resp) => {
|
||||||
console.log(resp);
|
console.log(resp);
|
||||||
let respData = JSON.parse(resp);
|
let respData = JSON.parse(resp);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
function setDeviceField(device) {
|
function setDeviceField(device) {
|
||||||
let hw_id = getCookie('hw_id');
|
let hw_id = getCookie('hw_id');
|
||||||
fetch('/api/v2/device/set_data/' + hw_id, {
|
fetch('/api/device/set_data/' + hw_id, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
@ -15,7 +15,7 @@ function setDeviceField(device) {
|
||||||
|
|
||||||
function setDeviceData(data, successCallback, failureCallback) {
|
function setDeviceData(data, successCallback, failureCallback) {
|
||||||
let hw_id = getCookie('hw_id');
|
let hw_id = getCookie('hw_id');
|
||||||
fetch('/api/v2/device/set_data/' + hw_id, {
|
fetch('/api/device/set_data/' + hw_id, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
@ -35,7 +35,7 @@ function setDeviceData(data, successCallback, failureCallback) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoomData(data) {
|
function setRoomData(data) {
|
||||||
fetch('/api/v2/set_data/' + current_app.value + "_" + current_room.value, {
|
fetch('/api/set_data/' + current_app.value + "_" + current_room.value, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
<html>
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|
||||||
|
<title>Failure</title>
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/static/favicons/apple-touch-icon.png">
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
|
<link rel="icon" type="image/png" sizes="32x32" href="/static/favicons/favicon-32x32.png">
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicons/favicon-16x16.png">
|
||||||
|
|
@ -30,6 +32,14 @@
|
||||||
<div class="centered">
|
<div class="centered">
|
||||||
🤮 FAIL 🤡
|
🤮 FAIL 🤡
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="centered">
|
||||||
|
{% if code==1 %}
|
||||||
|
<p>Pairing code not recognized. Go back and try again.</p>
|
||||||
|
{% else %}
|
||||||
|
<p>Unknown error</p>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
@ -259,7 +259,7 @@
|
||||||
|
|
||||||
if (hw_id !== "" && hw_id !== undefined && hw_id !== "undefined") {
|
if (hw_id !== "" && hw_id !== undefined && hw_id !== "undefined") {
|
||||||
|
|
||||||
httpGetAsync('/api/v2/device/get_data/' + hw_id, (resp) => {
|
httpGetAsync('/api/device/get_data/' + hw_id, (resp) => {
|
||||||
console.log(resp);
|
console.log(resp);
|
||||||
let respData = JSON.parse(resp);
|
let respData = JSON.parse(resp);
|
||||||
|
|
||||||
|
|
@ -309,7 +309,7 @@
|
||||||
|
|
||||||
|
|
||||||
function setDeviceField(data) {
|
function setDeviceField(data) {
|
||||||
fetch('/api/v2/device/set_data/' + hw_id, {
|
fetch('/api/device/set_data/' + hw_id, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
@ -323,7 +323,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setDeviceData(data) {
|
function setDeviceData(data) {
|
||||||
fetch('/api/v2/device/set_data/' + hw_id, {
|
fetch('/api/device/set_data/' + hw_id, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
@ -337,7 +337,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function setRoomData(data) {
|
function setRoomData(data) {
|
||||||
fetch('/api/v2/set_data/' + current_app.value + "_" + current_room.value, {
|
fetch('/api/set_data/' + current_app.value + "_" + current_room.value, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'application/json',
|
'Accept': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -69,7 +70,7 @@
|
||||||
You can find the code in the bottom left of your menu tablet in conVRged.
|
You can find the code in the bottom left of your menu tablet in conVRged.
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer centered">
|
<div class="card-footer centered">
|
||||||
<form onsubmit="submitCode()">
|
<form id="pair_form">
|
||||||
<label for="pair_code"></label><input class="btn" type="text" id="pair_code" placeholder="0000">
|
<label for="pair_code"></label><input class="btn" type="text" id="pair_code" placeholder="0000">
|
||||||
<input class="btn btn-primary" id="submit_pairing_code" type="submit" value="Submit"/>
|
<input class="btn btn-primary" id="submit_pairing_code" type="submit" value="Submit"/>
|
||||||
</form>
|
</form>
|
||||||
|
|
@ -81,18 +82,28 @@
|
||||||
|
|
||||||
const pair_code_input = document.getElementById('pair_code');
|
const pair_code_input = document.getElementById('pair_code');
|
||||||
|
|
||||||
function submitCode() {
|
document.getElementById('pair_form').addEventListener('submit', submitCode, true);
|
||||||
fetch('/api/v2/get_device_by_pairing_code/' + pair_code_input.value).then(resp => resp.json()).then(resp => {
|
|
||||||
console.log(resp);
|
function submitCode(event) {
|
||||||
|
fetch('/api/get_device_by_pairing_code/' + pair_code_input.value)
|
||||||
|
.then(resp => resp.json())
|
||||||
|
.then(resp => {
|
||||||
|
if (resp.length === 2 && resp[1] === 400) {
|
||||||
|
window.location.href = "/failure?code=1";
|
||||||
|
console.error(resp);
|
||||||
|
} else {
|
||||||
let respData = JSON.parse(resp);
|
let respData = JSON.parse(resp);
|
||||||
if (respData['hw_id'] !== '') {
|
if (respData['hw_id'] !== '') {
|
||||||
setCookie('hw_id', respData['hw_id'], 60);
|
setCookie('hw_id', respData['hw_id'], 60);
|
||||||
window.location.href = "/";
|
window.location.href = "/";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
window.location.href = "/failure";
|
window.location.href = "/failure";
|
||||||
console.error(e);
|
console.error(e);
|
||||||
});
|
});
|
||||||
|
event.preventDefault();
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue