add api spec, add fetching of headset details, improve website flow

dev
Anton Franzluebbers 2021-10-15 23:14:35 -04:00
parent bd363e5481
commit 79ff48915d
18 changed files with 377 additions and 30 deletions

View File

@ -1,7 +1,8 @@
from flask.helpers import send_from_directory
from velconnect.auth import require_api_key
from velconnect.db import connectToDB
from velconnect.logger import logger
from flask import Blueprint, request, jsonify
from flask import Blueprint, request, jsonify, render_template, url_for
import time
import simplejson as json
from random import random
@ -9,6 +10,20 @@ from random import random
bp = Blueprint('api', __name__)
@bp.route('/', methods=['GET'])
@require_api_key(0)
def api_home():
return render_template('api_spec.html')
@bp.route('/api_spec.json', methods=['GET'])
@require_api_key(0)
def api_spec():
return send_from_directory('static', 'api_spec.json')
@bp.route('/get_all_headsets', methods=['GET'])
@require_api_key(0)
def get_all_headsets():
@ -48,8 +63,6 @@ def update_paring_code():
return 'Must supply hw_id', 400
if 'pairing_code' not in data:
return 'Must supply pairing_code', 400
if 'pairing_code' not in data:
return 'Must supply pairing_code', 400
conn, curr = connectToDB()
@ -74,6 +87,24 @@ def update_paring_code():
return 'Success'
@bp.route('/get_headset_details/<hw_id>', methods=['GET'])
@require_api_key(10)
def get_headset_details(hw_id):
return jsonify(get_headset_details_db(hw_id))
def get_headset_details_db(hw_id):
conn, curr = connectToDB()
query = """
SELECT * FROM `Headset` WHERE hw_id=%(hw_id)s;
"""
curr.execute(query, {'hw_id': hw_id})
values = [dict(row) for row in curr.fetchall()]
curr.close()
return jsonify(values)
@bp.route('/get_room_details/<room_id>', methods=['GET'])
@require_api_key(10)
def get_room_details(room_id):
@ -91,13 +122,13 @@ def get_room_details_db(room_id):
return jsonify(values)
@bp.route('/create_room', methods=['GET'])
@bp.route('/update_room/<room_id>', methods=['POST'])
@require_api_key(10)
def create_room():
return jsonify(create_room_db())
def update_room(room_id):
return jsonify(update_room_db(room_id, request.json))
def create_room_db():
def update_room_db(room_id, data):
room_id = random.randint(0, 9999)
conn, curr = connectToDB()
query = """

View File

@ -0,0 +1,134 @@
{
"openapi": "3.0.0",
"info": {
"version": "1.0",
"title": "VEL Connect API",
"description": "This API provides an interface with the database for pairing and controlling headsets while within a VEL app."
},
"servers": [
{
"url": "http://3.23.79.32/api/"
}
],
"tags": [
{
"name": "Read"
},
{
"name": "Write"
}
],
"paths": {
"/get_all_headsets": {
"get": {
"summary": "Get all headset data",
"description" : "Gets a list of all headset data that has been stored",
"tags": ["Read"],
"parameters": [
]
}
},
"/pair_headset/{pairing_code}": {
"get": {
"summary": "Pair a headset by code",
"description" : "Tries to pair to a headset with a supplied pairing code. Returns the hw_id if successfully paired. Used by the website for pairing.",
"tags": ["Write"],
"parameters": [
{
"name": "pairing_code",
"example": "1234",
"in": "path",
"description": "Pairing Code",
"required": true,
"schema": {
"type": "integer"
}
}
]
}
},
"/update_pairing_code": {
"post": {
"summary": "Update Pairing Code",
"description": "This is called by the VR application on login or when the pairing code is reset. If this is the first time this headset is seen, it also creates this headset in the database. A JSON body must be submitted with hw_id and pairing_code defined.",
"tags": ["Write"],
"parameters": [
]
}
},
"/get_headset_details/{hw_id}": {
"get": {
"summary": "Get Headset Details",
"description" : "Gets the info associated with a specific hardware id.",
"tags": ["Read"],
"parameters": [
{
"name": "hw_id",
"example": "123456789",
"in": "path",
"description": "Hardware id of the device",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/get_room_details/{room_id}": {
"get": {
"summary": "Get Room Details",
"description" : "Gets the info associated with a specific room id. Used by the website to show initial data, and polled often by the headset.",
"tags": ["Read"],
"parameters": [
{
"name": "room_id",
"example": "1234",
"in": "path",
"description": "Room id",
"required": true,
"schema": {
"type": "string"
}
}
]
}
},
"/update_room/{room_id}": {
"post": {
"summary": "Update Room Details",
"description" : "Sets the info associated with a specific room id. The room id is specified in the url and the data is specified in the JSON body. Used by both the website and headset.",
"tags": ["Write"],
"parameters": [
{
"name": "room_id",
"example": "1234",
"in": "path",
"description": "Room id",
"required": true,
"schema": {
"type": "string"
}
}
]
}
}
},
"components": {
"schemas": {
},
"securitySchemes": {
"api_key": {
"type": "apiKey",
"name": "x-api-key",
"in": "query"
}
}
},
"security": [
{
"BasicAuth": []
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig>
<msapplication>
<tile>
<square150x150logo src="/mstile-150x150.png"/>
<TileColor>#b91d47</TileColor>
</tile>
</msapplication>
</browserconfig>

Binary file not shown.

After

Width:  |  Height:  |  Size: 815 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View File

@ -0,0 +1,20 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="300.000000pt" height="300.000000pt" viewBox="0 0 300.000000 300.000000"
preserveAspectRatio="xMidYMid meet">
<metadata>
Created by potrace 1.14, written by Peter Selinger 2001-2017
</metadata>
<g transform="translate(0.000000,300.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M605 1500 l600 -600 97 0 98 0 0 600 0 600 -100 0 -100 0 0 -452 0
-453 -453 453 -452 452 -145 0 -145 0 600 -600z"/>
<path d="M1500 2000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M2200 1500 l0 -600 398 2 397 3 3 98 3 97 -301 0 -300 0 -2 498 -3
497 -97 3 -98 3 0 -601z"/>
<path d="M1500 1500 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
<path d="M1500 1000 l0 -100 300 0 300 0 0 100 0 100 -300 0 -300 0 0 -100z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 977 B

View File

@ -0,0 +1,19 @@
{
"name": "",
"short_name": "",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-256x256.png",
"sizes": "256x256",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#ffffff",
"display": "standalone"
}

View File

@ -0,0 +1,31 @@
function httpGetAsync(theUrl, callback, failCallback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4) {
if (xmlHttp.status == 200) {
callback(xmlHttp.responseText);
} else {
failCallback(xmlHttp.status);
}
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
function getCookie(cname) {
let name = cname + "=";
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
}
if (c.indexOf(name) == 0) {
return c.substring(name.length, c.length);
}
}
return "";
}

View File

@ -0,0 +1,24 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8"> <!-- Important: rapi-doc uses utf8 charecters -->
<script type="module" src="https://unpkg.com/rapidoc/dist/rapidoc-min.js"></script>
</head>
<body>
<rapi-doc
render-style = "read"
primary-color = "#bc1f2d"
show-header = "false"
show-info = "true"
spec-url = "/api/api_spec.json"
default-schema-tab = 'example'
>
<div slot="nav-logo" style="display: flex; align-items: center; justify-content: center;">
<img src = "/static/favicons/android-chrome-256x256.png" style="width:10em; margin: auto;" />
</div>
</rapi-doc>
</body>
</html>

View File

@ -1,6 +1,16 @@
<html>
<head>
<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="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<style>
body {
background-color: #333;

View File

@ -1,25 +1,85 @@
<html>
<head>
<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="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="/static/css/spectre.min.css">
<script src="/static/js/util.js"></script>
<style>
body {
background-color: #333;
font-size: 1.2em;
.container {
max-width: 30em;
}
.card {
margin: 1em;
box-shadow: 0 0 2em #0003;
}
.centered {
margin: 8em auto;
width: max-content;
font-family: arial, sans-serif;
color: #ddd;
margin: auto;
}
</style>
</head>
<body>
<div class="centered">
Headset pairing. Wow so cool! 😋😎
<div class="container">
<div id="loading" class="loading loading-lg"></div>
<div id="failure">☹️</div>
<div id="headset_details" style="display: none;">
<h2>HW ID</h2>
<p id="hw_id"></p>
</div>
</div>
<script>
let submit_button = document.getElementById('submit_pairing_code');
let pair_code_input = document.getElementById('pair_code');
let loading = document.getElementById('loading');
let enter_pairing_id = document.getElementById('enter_pairing_id');
let headset_details = document.getElementById('headset_details');
let hw_id_field = document.getElementById('hw_id');
let failure = document.getElementById('failure');
// check cookie
let hw_id = getCookie('hw_id');
loading.style.display = "none";
if (hw_id != "") {
httpGetAsync('/api/get_headset_details/' + hw_id, (resp) => {
console.log(resp);
let respData = JSON.parse(resp);
if (respData['hw_id'] != '') {
hw_id_field.innerText = respData['hw_id'];
}
headset_details.style.display = "block";
}, (status) => {
failure.style.display = "block";
});
} else {
window.location.href = "/pair";
}
</script>
</body>
</html>

View File

@ -1,7 +1,19 @@
<html>
<head>
<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="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="/static/css/spectre.min.css">
<script src="/static/js/util.js"></script>
<style>
#pair_code {
max-width: 4em;
@ -49,19 +61,6 @@
</div>
<script>
function httpGetAsync(theUrl, callback, failCallback) {
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function () {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
callback(xmlHttp.responseText);
}
else {
failCallback(xmlHttp.status);
}
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}
let submit_button = document.getElementById('submit_pairing_code');
let pair_code_input = document.getElementById('pair_code');
@ -70,7 +69,7 @@
console.log(resp);
let respData = JSON.parse(resp);
if (respData['hw_id'] != '') {
document.cookie = "hw_id="+respData['hw_id'];
document.cookie = "hw_id=" + respData['hw_id'] + "; SameSite=None; Secure";
window.location.href = "/success";
}
}, (status) => {

View File

@ -1,6 +1,16 @@
<html>
<head>
<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="16x16" href="/static/favicons/favicon-16x16.png">
<link rel="manifest" href="/static/favicons/site.webmanifest">
<link rel="mask-icon" href="/static/favicons/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#b91d47">
<meta name="theme-color" content="#ffffff">
<style>
body {
background-color: #333;