✨ Improved ClassQuizController
This commit is contained in:
@@ -320,7 +320,7 @@ class GameResults(ormar.Model):
|
||||
database = database
|
||||
|
||||
|
||||
class Controllers(ormar.Model):
|
||||
class Controller(ormar.Model):
|
||||
id: uuid.UUID = ormar.UUID(primary_key=True)
|
||||
user: uuid.UUID | User = ormar.ForeignKey(User)
|
||||
secret_key: str = ormar.String(nullable=False, max_length=24, min_length=24)
|
||||
@@ -329,8 +329,9 @@ class Controllers(ormar.Model):
|
||||
first_seen: datetime | None = ormar.DateTime(nullable=True)
|
||||
name: str = ormar.Text(nullable=False)
|
||||
os_version: str | None = ormar.Text(nullable=True)
|
||||
wanted_os_version: str = ormar.Text(nullable=True, default=None)
|
||||
|
||||
class Meta:
|
||||
tablename = "controllers"
|
||||
tablename = "controller"
|
||||
metadata = metadata
|
||||
database = database
|
||||
|
||||
@@ -10,9 +10,10 @@ import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from fastapi import APIRouter, HTTPException
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from pydantic import BaseModel
|
||||
from classquiz.config import redis
|
||||
from classquiz.db.models import PlayGame, Controllers
|
||||
from classquiz.db.models import PlayGame, Controller
|
||||
from classquiz.routers.box_controller.embedded.socket import router as socket_router
|
||||
|
||||
router = APIRouter()
|
||||
@@ -31,7 +32,7 @@ class JoinGameResponse(BaseModel):
|
||||
|
||||
@router.post("/join")
|
||||
async def join_game(data: JoinGameInput) -> JoinGameResponse:
|
||||
controller = await Controllers.objects.get_or_none(id=data.id, secret_key=data.secret_key)
|
||||
controller = await Controller.objects.get_or_none(id=data.id, secret_key=data.secret_key)
|
||||
game_pin = await redis.get(f"game:cqc:code:{data.code}")
|
||||
if game_pin is None:
|
||||
raise HTTPException(status_code=404, detail="Game not found")
|
||||
@@ -65,7 +66,7 @@ async def register_with_code(data: RegisterWithCodeInput) -> RegisterWithCodeRes
|
||||
raise HTTPException(status_code=404, detail="Code not found")
|
||||
await redis.delete(f"controller_setup:{data.code}")
|
||||
c_id = uuid.UUID(c_id)
|
||||
controller = await Controllers.objects.get(id=c_id)
|
||||
controller = await Controller.objects.get(id=c_id)
|
||||
controller.first_seen = datetime.now()
|
||||
controller.last_seen = datetime.now()
|
||||
await controller.update()
|
||||
@@ -74,9 +75,19 @@ async def register_with_code(data: RegisterWithCodeInput) -> RegisterWithCodeRes
|
||||
|
||||
@router.get("/ping")
|
||||
async def ping_server(id: uuid.UUID, secret_key: str, version: str):
|
||||
controller = await Controllers.objects.get_or_none(id=id, secret_key=secret_key)
|
||||
controller = await Controller.objects.get_or_none(id=id, secret_key=secret_key)
|
||||
if controller is None:
|
||||
raise HTTPException(status_code=404, detail="Key and/or id invalid")
|
||||
controller.last_seen = datetime.now()
|
||||
controller.os_version = version
|
||||
await controller.update()
|
||||
|
||||
|
||||
@router.get("/update")
|
||||
async def get_firmware_version(id: uuid.UUID, secret_key: str) -> PlainTextResponse:
|
||||
controller = await Controller.objects.get_or_none(id=id, secret_key=secret_key)
|
||||
if controller is None:
|
||||
return PlainTextResponse(status_code=404, content="Key and/or id invalid")
|
||||
if controller.wanted_os_version is None:
|
||||
return PlainTextResponse(status_code=400, content="No update needed")
|
||||
return PlainTextResponse(status_code=200, content=controller.wanted_os_version)
|
||||
|
||||
@@ -62,7 +62,7 @@ async def submit_answer_fn(data_answer: int, game_pin: str, player_id: str, now:
|
||||
await sio.emit("everyone_answered", {})
|
||||
|
||||
|
||||
button_to_index_map = {"b": 0, "g": 2, "y": 1, "r": 3}
|
||||
button_to_index_map = {"y": 0, "r": 2, "g": 1, "b": 3}
|
||||
|
||||
|
||||
class WebSocketTypes(enum.Enum):
|
||||
@@ -85,7 +85,6 @@ async def websocket_endpoint(ws: WebSocket, game_id: str):
|
||||
await ws.close(code=status.WS_1001_GOING_AWAY)
|
||||
print("Client {} already exists.".format(game_id))
|
||||
return
|
||||
print("hI!")
|
||||
await ws.accept()
|
||||
wss_clients[game_id] = ws
|
||||
|
||||
@@ -119,9 +118,7 @@ async def websocket_endpoint(ws: WebSocket, game_id: str):
|
||||
except (KeyError, AttributeError):
|
||||
await ws.send_text(WebSocketRequest(type=WebSocketTypes.Error, data="InvalidButton").json())
|
||||
continue
|
||||
print(data)
|
||||
await submit_answer_fn(answer_index, game_pin, player_id, now)
|
||||
print("Data from client {}: {}".format(game_id, data))
|
||||
|
||||
except WebSocketDisconnect as ex:
|
||||
print("Client {} is disconnected: {}".format(game_id, ex))
|
||||
|
||||
@@ -8,7 +8,7 @@ from fastapi import APIRouter, Depends, HTTPException
|
||||
from pydantic import BaseModel
|
||||
|
||||
from classquiz.auth import get_current_user
|
||||
from classquiz.db.models import User, Controllers
|
||||
from classquiz.db.models import User, Controller
|
||||
from classquiz.config import redis
|
||||
from classquiz.helpers.box_controller import generate_code
|
||||
|
||||
@@ -32,7 +32,7 @@ async def set_controller_up(
|
||||
code = generate_code(10)
|
||||
if input_data.player_name is None:
|
||||
input_data.player_name = user.username
|
||||
data = Controllers(
|
||||
data = Controller(
|
||||
id=uuid.uuid4(),
|
||||
user=user,
|
||||
secret_key=os.urandom(12).hex(),
|
||||
@@ -47,12 +47,12 @@ async def set_controller_up(
|
||||
return SetControllerUpResponse(code=code, id=data.id)
|
||||
|
||||
|
||||
GetControllerResponse = Controllers.get_pydantic(exclude={"secret_key", "user"})
|
||||
GetControllerResponse = Controller.get_pydantic(exclude={"secret_key", "user"})
|
||||
|
||||
|
||||
@router.get("/controller")
|
||||
async def get_controller(id: uuid.UUID, user: User = Depends(get_current_user)) -> GetControllerResponse:
|
||||
controller = await Controllers.objects.get_or_none(id=id, user=user.id)
|
||||
controller = await Controller.objects.get_or_none(id=id, user=user.id)
|
||||
if controller is None:
|
||||
raise HTTPException(status_code=404, detail="Controller not found")
|
||||
return GetControllerResponse(**controller.dict())
|
||||
@@ -68,7 +68,7 @@ class ModifyControllerInput(BaseModel):
|
||||
async def modify_controller(
|
||||
data: ModifyControllerInput, user: User = Depends(get_current_user)
|
||||
) -> GetControllerResponse:
|
||||
controller = await Controllers.objects.get_or_none(id=data.id, user=user.id)
|
||||
controller = await Controller.objects.get_or_none(id=data.id, user=user.id)
|
||||
if controller is None:
|
||||
raise HTTPException(status_code=404, detail="Controller not found")
|
||||
controller.player_name = data.player_name
|
||||
@@ -79,10 +79,24 @@ async def modify_controller(
|
||||
|
||||
@router.get("/list")
|
||||
async def get_all_controllers(user: User = Depends(get_current_user)) -> list[GetControllerResponse]:
|
||||
controllers = await Controllers.objects.all(user=user.id)
|
||||
controllers = await Controller.objects.all(user=user.id)
|
||||
if len(controllers) == 0:
|
||||
return []
|
||||
return_list = []
|
||||
for controller in controllers:
|
||||
return_list.append(GetControllerResponse(**controller.dict()))
|
||||
return return_list
|
||||
|
||||
|
||||
class SetVersionToBeFlashedInput(BaseModel):
|
||||
id: uuid.UUID
|
||||
version: str
|
||||
|
||||
|
||||
@router.post("/set_update")
|
||||
async def set_version_to_be_flashed(data: SetVersionToBeFlashedInput, user: User = Depends(get_current_user)):
|
||||
controller = await Controller.objects.get_or_none(id=data.id, user=user.id)
|
||||
if controller is None:
|
||||
raise HTTPException(status_code=404, detail="Controller not found")
|
||||
controller.wanted_os_version = data.version
|
||||
await controller.update()
|
||||
|
||||
@@ -161,7 +161,12 @@
|
||||
"results": "Results",
|
||||
"note": "Note",
|
||||
"player_plural": "Players",
|
||||
"score": "Score"
|
||||
"score": "Score",
|
||||
"version": "Version",
|
||||
"never": "Never",
|
||||
"unknown": "Unknown",
|
||||
"name": "Name",
|
||||
"update": "Update"
|
||||
},
|
||||
"editor": {
|
||||
"time_in_seconds": "Time in seconds",
|
||||
@@ -272,5 +277,16 @@
|
||||
"correct_answer": "{{count}} correct answer",
|
||||
"correct_answer_plural": "{{count}} correct answers",
|
||||
"time_taken": "Time taken"
|
||||
},
|
||||
"controllers": {
|
||||
"add_new_controller": "Add new controller",
|
||||
"player_name": "Player name",
|
||||
"first_seen": "First seen",
|
||||
"last_seen": "Last seen",
|
||||
"controller_name": "Controller Name",
|
||||
"version_overview": "The newest version available is {{newest_version}} and version {{current_version}} is installed.",
|
||||
"already_latest_version": "You're already on the latest version",
|
||||
"cancel_update": "Cancel Update!",
|
||||
"update_from_to": "Update from {{current_version}} to {{newest_version}}"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
const { t } = getLocalization();
|
||||
export let data: PageData;
|
||||
|
||||
console.log(data.controllers);
|
||||
@@ -12,24 +18,35 @@
|
||||
{#if controllers.length === 0}
|
||||
<div class="w-full h-full flex">
|
||||
<div class="m-auto">
|
||||
<BrownButton>Add new controller</BrownButton>
|
||||
<BrownButton href="/account/controllers/add"
|
||||
>{$t('controllers.add_new_controller')}</BrownButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="p-2">
|
||||
<div class="flex">
|
||||
<div class="mx-auto">
|
||||
<BrownButton href="/account/controllers/add"
|
||||
>{$t('controllers.add_new_controller')}</BrownButton
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<table class="w-full">
|
||||
<tr class="border-b-2 dark:border-gray-500 text-left border-gray-300">
|
||||
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300">Name</th>
|
||||
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
|
||||
>Player name</th
|
||||
>{$t('words.name')}</th
|
||||
>
|
||||
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
|
||||
>First seen</th
|
||||
>
|
||||
>{$t('controllers.player_name')}
|
||||
</th>
|
||||
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
|
||||
>Last seen</th
|
||||
>
|
||||
<th class="mx-auto p-1">Version</th>
|
||||
>{$t('controllers.first_seen')}
|
||||
</th>
|
||||
<th class="border-r dark:border-gray-500 p-1 mx-auto border-gray-300"
|
||||
>{$t('controllers.last_seen')}
|
||||
</th>
|
||||
<th class="mx-auto p-1">{$t('words.version')}</th>
|
||||
</tr>
|
||||
{#each data.controllers as controller}
|
||||
<tr class="text-left">
|
||||
@@ -45,14 +62,14 @@
|
||||
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
|
||||
>{controller.first_seen
|
||||
? new Date(controller.first_seen).toLocaleString()
|
||||
: 'Never'}</td
|
||||
: $t('words.never')}</td
|
||||
>
|
||||
<td class="border-r dark:border-gray-500 p-1 border-gray-300"
|
||||
>{controller.last_seen
|
||||
? new Date(controller.last_seen).toLocaleString()
|
||||
: 'Never'}</td
|
||||
: $t('words.never')}</td
|
||||
>
|
||||
<td class="mx-auto p-1">{controller.os_version ?? 'Unknown'}</td>
|
||||
<td class="mx-auto p-1">{controller.os_version ?? $t('words.unknown')}</td>
|
||||
</tr>
|
||||
{/each}
|
||||
</table>
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { SaveStatus } from './commons';
|
||||
import SaveIndicator from './SaveIndicator.svelte';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import Cookies from 'js-cookie';
|
||||
import { browser } from '$app/environment';
|
||||
import BrownButton from '$lib/components/buttons/brown.svelte';
|
||||
import { getLocalization } from '$lib/i18n';
|
||||
|
||||
const { t } = getLocalization();
|
||||
export let data: PageData;
|
||||
const controller = data.controller;
|
||||
|
||||
let newest_version: string | undefined = undefined;
|
||||
|
||||
let saved_status = SaveStatus.Unchanged;
|
||||
|
||||
let save_player_timer;
|
||||
const save_player_name_debounce = () => {
|
||||
clearTimeout(save_player_timer);
|
||||
save_player_timer = setTimeout(save_names, 750);
|
||||
};
|
||||
|
||||
const save_names = async () => {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
saved_status = SaveStatus.Saving;
|
||||
const res = await fetch(`/api/v1/box-controller/web/modify`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
id: controller.id,
|
||||
player_name: controller.player_name,
|
||||
name: controller.name
|
||||
})
|
||||
});
|
||||
if (res.ok) {
|
||||
saved_status = SaveStatus.Saved;
|
||||
} else {
|
||||
saved_status = SaveStatus.Error;
|
||||
}
|
||||
};
|
||||
const get_latest_version = async () => {
|
||||
if (!browser) {
|
||||
return;
|
||||
}
|
||||
const cookie_data = Cookies.get('latest_cqc_version');
|
||||
if (cookie_data) {
|
||||
newest_version = cookie_data;
|
||||
return;
|
||||
}
|
||||
const res = await fetch(
|
||||
'https://api.github.com/repos/mawoka-myblock/ClassQuizController/releases'
|
||||
);
|
||||
const json = await res.json();
|
||||
newest_version = json[0].tag_name.replace('v', '');
|
||||
Cookies.set('latest_cqc_version', newest_version, { path: '', expires: 3600 });
|
||||
};
|
||||
|
||||
const allow_update_to_version = async () => {
|
||||
if (newest_version === controller.os_version) {
|
||||
return;
|
||||
}
|
||||
let wanted_version;
|
||||
if (newest_version === controller.wanted_os_version) {
|
||||
wanted_version = controller.os_version;
|
||||
} else {
|
||||
wanted_version = newest_version;
|
||||
}
|
||||
await fetch('/api/v1/box-controller/web/set_update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ id: controller.id, version: wanted_version })
|
||||
});
|
||||
window.location.reload();
|
||||
};
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<div class="w-full flex flex-col">
|
||||
<h2 class="text-2xl mx-auto">{$t('controllers.controller_name')}</h2>
|
||||
<div class="mx-auto flex">
|
||||
<input
|
||||
class="rounded p-1 transition-all outline-none text-center dark:bg-gray-700"
|
||||
bind:value={controller.name}
|
||||
on:keyup={save_player_name_debounce}
|
||||
/>
|
||||
<SaveIndicator status={saved_status} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h2 class="mx-auto text-2xl">{$t('controllers.player_name')}</h2>
|
||||
<div class="mx-auto flex">
|
||||
<input
|
||||
class="rounded p-1 transition-all outline-none text-center dark:bg-gray-700"
|
||||
bind:value={controller.player_name}
|
||||
on:keyup={save_player_name_debounce}
|
||||
/>
|
||||
<SaveIndicator status={saved_status} />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<h2 class="mx-auto text-2xl">{$t('words.update')}</h2>
|
||||
{#await get_latest_version()}
|
||||
<Spinner my_20={false} />
|
||||
{:then _}
|
||||
<p class="mx-auto">
|
||||
{$t('controllers.version_overview', {
|
||||
newest_version,
|
||||
current_version: controller.os_version
|
||||
})}
|
||||
</p>
|
||||
{/await}
|
||||
{#if newest_version && controller.os_version}
|
||||
<div class="mx-auto">
|
||||
<BrownButton
|
||||
disabled={newest_version === controller.os_version}
|
||||
on:click={allow_update_to_version}
|
||||
>
|
||||
{#if newest_version === controller.os_version}
|
||||
{$t('controllers.already_latest_version')}
|
||||
{:else if newest_version === controller.wanted_os_version}
|
||||
{$t('controllers.cancel_update')}
|
||||
{:else}
|
||||
{$t('controllers.update_from_to', {
|
||||
newest_version,
|
||||
current_version: controller.os_version
|
||||
})}
|
||||
{/if}
|
||||
</BrownButton>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
import type { PageLoad } from './$types';
|
||||
import { error } from '@sveltejs/kit';
|
||||
|
||||
export const load = (async ({ fetch, params }) => {
|
||||
const res = await fetch(`/api/v1/box-controller/web/controller?id=${params.controller_id}`);
|
||||
if (res.status !== 200) {
|
||||
throw error(res.status, await res.text());
|
||||
}
|
||||
const json: {
|
||||
id: string;
|
||||
player_name: string;
|
||||
last_seen?: string;
|
||||
first_seen?: string;
|
||||
name: string;
|
||||
os_version?: string;
|
||||
wanted_os_version?: string;
|
||||
} = await res.json();
|
||||
return {
|
||||
controller: json
|
||||
};
|
||||
}) satisfies PageLoad;
|
||||
@@ -0,0 +1,37 @@
|
||||
<!--
|
||||
- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
-->
|
||||
<script lang="ts">
|
||||
import { SaveStatus } from './commons';
|
||||
|
||||
export let status: SaveStatus;
|
||||
</script>
|
||||
|
||||
<div class="h-6 w-6 grow-0 my-auto">
|
||||
{#if status === SaveStatus.Saved}
|
||||
<svg
|
||||
class="h-full w-full"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d="M5 13l4 4L19 7" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
{:else if status === SaveStatus.Saving}
|
||||
<svg class="max-h-full max-w-full h-full animate-spin w-full" viewBox="3 3 18 18">
|
||||
<path
|
||||
class="fill-black"
|
||||
d="M12 5C8.13401 5 5 8.13401 5 12C5 15.866 8.13401 19 12 19C15.866 19 19 15.866 19 12C19 8.13401 15.866 5 12 5ZM3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12Z"
|
||||
/>
|
||||
<path
|
||||
class="fill-blue-100"
|
||||
d="M16.9497 7.05015C14.2161 4.31648 9.78392 4.31648 7.05025 7.05015C6.65973 7.44067 6.02656 7.44067 5.63604 7.05015C5.24551 6.65962 5.24551 6.02646 5.63604 5.63593C9.15076 2.12121 14.8492 2.12121 18.364 5.63593C18.7545 6.02646 18.7545 6.65962 18.364 7.05015C17.9734 7.44067 17.3403 7.44067 16.9497 7.05015Z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
export enum SaveStatus {
|
||||
Unchanged,
|
||||
Saved,
|
||||
Saving,
|
||||
Error
|
||||
}
|
||||
|
||||
/* eslint-enable */
|
||||
@@ -3,7 +3,6 @@
|
||||
import CodeDisplay from '$lib/components/controller/code.svelte';
|
||||
import Spinner from '$lib/Spinner.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export let data: PageData;
|
||||
let controller_seen = false;
|
||||
|
||||
+8
-7
@@ -1,8 +1,8 @@
|
||||
"""Added controllers table
|
||||
"""Added Controllers Table
|
||||
|
||||
Revision ID: b9ca06dfa179
|
||||
Revision ID: 4916d7b204cb
|
||||
Revises: 7afe98d04169
|
||||
Create Date: 2023-04-03 13:16:49.585163
|
||||
Create Date: 2023-06-29 19:01:18.331367
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
@@ -11,7 +11,7 @@ import ormar
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "b9ca06dfa179"
|
||||
revision = "4916d7b204cb"
|
||||
down_revision = "7afe98d04169"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
@@ -20,7 +20,7 @@ depends_on = None
|
||||
def upgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.create_table(
|
||||
"controllers",
|
||||
"controller",
|
||||
sa.Column("id", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=False),
|
||||
sa.Column("user", ormar.fields.sqlalchemy_uuid.CHAR(32), nullable=True),
|
||||
sa.Column("secret_key", sa.String(length=24), nullable=False),
|
||||
@@ -29,7 +29,8 @@ def upgrade() -> None:
|
||||
sa.Column("first_seen", sa.DateTime(), nullable=True),
|
||||
sa.Column("name", sa.Text(), nullable=False),
|
||||
sa.Column("os_version", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user"], ["users.id"], name="fk_controllers_users_id_user"),
|
||||
sa.Column("wanted_os_version", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(["user"], ["users.id"], name="fk_controller_users_id_user"),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
# ### end Alembic commands ###
|
||||
@@ -37,5 +38,5 @@ def upgrade() -> None:
|
||||
|
||||
def downgrade() -> None:
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_table("controllers")
|
||||
op.drop_table("controller")
|
||||
# ### end Alembic commands ###
|
||||
Reference in New Issue
Block a user