Initial FastApi, Peewee setup
This commit is contained in:
parent
ce6fec6823
commit
5875a5ebd5
17 changed files with 81 additions and 397 deletions
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
|
@ -1,58 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
class Activity
|
|
||||||
{
|
|
||||||
public int $startTime; // Unix timestamp in seconds
|
|
||||||
public int $expectedDuration; // Expected duration in seconds, 0 if open-ended
|
|
||||||
|
|
||||||
public string $title;
|
|
||||||
public string $description; // Additional information, but might be kept empty
|
|
||||||
|
|
||||||
public bool $available; // Am I available for requests and to talk?
|
|
||||||
public bool $working; // Am I working on something?
|
|
||||||
|
|
||||||
public string $template; // ID of the template that is supposed to be applied to this activity
|
|
||||||
|
|
||||||
public array $streak; // Last success dates of the associated streak
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->startTime = time();
|
|
||||||
$this->expectedDuration = 0;
|
|
||||||
$this->title = "";
|
|
||||||
$this->description = "";
|
|
||||||
$this->available = false;
|
|
||||||
$this->working = false;
|
|
||||||
$this->template = "";
|
|
||||||
$this->streak = array();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function initialLoad(): void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getCurrentDuration(): int
|
|
||||||
{
|
|
||||||
return time() - $this->startTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getRemainingDuration(): int
|
|
||||||
{
|
|
||||||
return $this->expectedDuration - $this->getCurrentDuration();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getPublicData(): array
|
|
||||||
{
|
|
||||||
return array(
|
|
||||||
"startTime" => $this->startTime,
|
|
||||||
"expectedDuration" => $this->expectedDuration,
|
|
||||||
"title" => $this->title,
|
|
||||||
"description" => $this->description,
|
|
||||||
"available" => $this->available,
|
|
||||||
"working" => $this->working,
|
|
||||||
"currentDuration" => $this->getCurrentDuration(),
|
|
||||||
"remainingDuration" => $this->getRemainingDuration(),
|
|
||||||
"streak" => $this->streak,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
$SECRET_KEY = "ENTER_SECRET_HERE";
|
|
||||||
$ACTIVITY_FILE = "last_activity.json";
|
|
||||||
$TEMPLATES_FILE = "templates.json";
|
|
||||||
$STREAKS_FILE_SUFFIX = "_streak.csv";
|
|
1
src/config.py
Normal file
1
src/config.py
Normal file
|
@ -0,0 +1 @@
|
||||||
|
DATABASE = "./db.sqlite3"
|
0
src/endpoints/__init__.py
Normal file
0
src/endpoints/__init__.py
Normal file
0
src/endpoints/activity_api.py
Normal file
0
src/endpoints/activity_api.py
Normal file
12
src/endpoints/streak_api.py
Normal file
12
src/endpoints/streak_api.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from ..storage import Streak
|
||||||
|
|
||||||
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/list", tags=["streak"])
|
||||||
|
def get_streaks() -> str:
|
||||||
|
return "Hellow"
|
14
src/http.php
14
src/http.php
|
@ -1,14 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
function respondWithJson($data): void
|
|
||||||
{
|
|
||||||
header('Content-Type: application/json');
|
|
||||||
echo json_encode($data);
|
|
||||||
die();
|
|
||||||
}
|
|
||||||
|
|
||||||
function respondAndDie(string $message): void
|
|
||||||
{
|
|
||||||
echo $message;
|
|
||||||
die();
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
<?php
|
|
||||||
// Temporary error reporting
|
|
||||||
error_reporting(E_ALL);
|
|
||||||
ini_set('display_errors', 1);
|
|
||||||
|
|
||||||
require_once("config.php");
|
|
||||||
require_once("storage.php");
|
|
||||||
require_once("activity.php");
|
|
||||||
require_once("http.php");
|
|
||||||
require_once("streaks.php");
|
|
||||||
|
|
||||||
function loadGivenParameters(Activity $activity): void
|
|
||||||
{
|
|
||||||
if (isset($_GET["title"])) {
|
|
||||||
$activity->title = $_GET["title"];
|
|
||||||
}
|
|
||||||
if (isset($_GET["description"])) {
|
|
||||||
$activity->description = $_GET["description"];
|
|
||||||
}
|
|
||||||
if (isset($_GET["duration"])) {
|
|
||||||
$activity->expectedDuration = (int)$_GET["duration"];
|
|
||||||
}
|
|
||||||
if (isset($_GET["available"])) {
|
|
||||||
$activity->available = filter_var($_GET["available"], FILTER_VALIDATE_BOOLEAN);
|
|
||||||
}
|
|
||||||
if (isset($_GET["working"])) {
|
|
||||||
$activity->working = filter_var($_GET["working"], FILTER_VALIDATE_BOOLEAN);
|
|
||||||
}
|
|
||||||
if (isset($_GET["template"])) {
|
|
||||||
$activity->template = $_GET["template"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTemplate(Activity $activity)
|
|
||||||
{
|
|
||||||
// Load and find template
|
|
||||||
try {
|
|
||||||
$templates = loadTemplates();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
respondAndDie($e->getMessage());
|
|
||||||
}
|
|
||||||
|
|
||||||
$template = $templates->findValidTemplate($activity);
|
|
||||||
if ($template == null) {
|
|
||||||
// No applicable template found
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply template
|
|
||||||
$template->applyTo($activity);
|
|
||||||
|
|
||||||
// Overwrite parameters if template was specified on activity
|
|
||||||
if ($activity->template != "") {
|
|
||||||
loadGivenParameters($activity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no secret key is provided, respond with the current activity
|
|
||||||
global $SECRET_KEY;
|
|
||||||
if (!isset($_GET["secret"]) || $_GET["secret"] != $SECRET_KEY) {
|
|
||||||
// Respond with the current activity
|
|
||||||
try {
|
|
||||||
$activity = loadActivity();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
respondAndDie($e->getMessage());
|
|
||||||
}
|
|
||||||
handleTemplate($activity);
|
|
||||||
$activity->streak = getStreakEntries("sleep");
|
|
||||||
respondWithJson($activity->getPublicData());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update activity
|
|
||||||
$activity = new Activity();
|
|
||||||
loadGivenParameters($activity);
|
|
||||||
storeActivity($activity);
|
|
||||||
|
|
||||||
// Handle sleep streak
|
|
||||||
$tz = 'Europe/Berlin';
|
|
||||||
$timestamp = $activity->startTime;
|
|
||||||
$dt = new DateTime("now", new DateTimeZone($tz)); //first argument "must" be a string
|
|
||||||
$dt->setTimestamp($timestamp); //adjust the object to correct timestamp
|
|
||||||
|
|
||||||
$startingHour = (int) $dt->format('H');
|
|
||||||
if ($activity->template == "sleeping" && $startingHour > 18) {
|
|
||||||
markTodayAsSuccess("sleep");
|
|
||||||
}
|
|
||||||
|
|
||||||
respondAndDie("Activity updated");
|
|
21
src/main.py
Normal file
21
src/main.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from .config import DATABASE
|
||||||
|
from .storage import *
|
||||||
|
from .endpoints.streak_api import router as streak_router
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
# Setup database
|
||||||
|
def create_database():
|
||||||
|
with database:
|
||||||
|
database.create_tables([Activity, ActivityRecord, Streak, StreakRecord])
|
||||||
|
|
||||||
|
|
||||||
|
if not os.path.exists(DATABASE):
|
||||||
|
create_database()
|
||||||
|
|
||||||
|
app = FastAPI()
|
||||||
|
|
||||||
|
app.include_router(streak_router, prefix="/streak", tags=["streak"])
|
|
@ -1,76 +0,0 @@
|
||||||
<?php
|
|
||||||
require_once("config.php");
|
|
||||||
require_once("templates.php");
|
|
||||||
require_once("activity.php");
|
|
||||||
|
|
||||||
function storeActivity(Activity $activity): void
|
|
||||||
{
|
|
||||||
global $ACTIVITY_FILE;
|
|
||||||
storeObject($activity, $ACTIVITY_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadActivity(): Activity
|
|
||||||
{
|
|
||||||
global $ACTIVITY_FILE;
|
|
||||||
return loadObject($ACTIVITY_FILE, Activity::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeTemplates(Templates $templates): void
|
|
||||||
{
|
|
||||||
global $TEMPLATES_FILE;
|
|
||||||
storeObject($templates, $TEMPLATES_FILE);
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadTemplates(): Templates
|
|
||||||
{
|
|
||||||
global $TEMPLATES_FILE;
|
|
||||||
if (!file_exists($TEMPLATES_FILE)) {
|
|
||||||
// Create initial template file
|
|
||||||
$templates = new Templates();
|
|
||||||
$templates->loadDefaultTemplates();
|
|
||||||
storeTemplates($templates);
|
|
||||||
return $templates;
|
|
||||||
}
|
|
||||||
|
|
||||||
return loadObject($TEMPLATES_FILE, Templates::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
function storeObject(object $object, string $file): void
|
|
||||||
{
|
|
||||||
$file = fopen($file, "w");
|
|
||||||
fwrite($file, json_encode($object, JSON_PRETTY_PRINT));
|
|
||||||
fclose($file);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws ReflectionException
|
|
||||||
* @throws JsonException
|
|
||||||
*/
|
|
||||||
function loadObject(string $file, string $class): object
|
|
||||||
{
|
|
||||||
$json = file_get_contents($file);
|
|
||||||
$data = json_decode($json, true, 512, JSON_THROW_ON_ERROR);
|
|
||||||
return parseObject($data, $class);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws ReflectionException
|
|
||||||
*/
|
|
||||||
function parseObject(array $data, string $class): object
|
|
||||||
{
|
|
||||||
$reflection = new ReflectionClass($class);
|
|
||||||
$instance = $reflection->newInstanceWithoutConstructor();
|
|
||||||
foreach ($data as $property => $value) {
|
|
||||||
$propertyReflection = $reflection->getProperty($property);
|
|
||||||
$propertyReflection->setAccessible(true);
|
|
||||||
$propertyReflection->setValue($instance, $value);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
$instance->initialLoad();
|
|
||||||
} catch (Exception $e) {
|
|
||||||
// Ignore
|
|
||||||
}
|
|
||||||
|
|
||||||
return $instance;
|
|
||||||
}
|
|
3
src/storage/__init__.py
Normal file
3
src/storage/__init__.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
from .activity import Activity, ActivityRecord
|
||||||
|
from .streak import Streak, StreakRecord
|
||||||
|
from .database import database
|
15
src/storage/activity.py
Normal file
15
src/storage/activity.py
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
from .database import BaseModel
|
||||||
|
from peewee import BooleanField, TextField, DateTimeField, ForeignKeyField
|
||||||
|
|
||||||
|
|
||||||
|
class Activity(BaseModel):
|
||||||
|
name = TextField()
|
||||||
|
description = TextField()
|
||||||
|
is_available = BooleanField()
|
||||||
|
is_working = BooleanField()
|
||||||
|
|
||||||
|
|
||||||
|
class ActivityRecord(BaseModel):
|
||||||
|
started_at = DateTimeField()
|
||||||
|
|
||||||
|
activity = ForeignKeyField(Activity, backref='records')
|
12
src/storage/database.py
Normal file
12
src/storage/database.py
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
from peewee import Model, SqliteDatabase, UUIDField
|
||||||
|
|
||||||
|
from ..config import DATABASE
|
||||||
|
|
||||||
|
database = SqliteDatabase(DATABASE)
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(Model):
|
||||||
|
id = UUIDField(primary_key=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
database = database
|
17
src/storage/streak.py
Normal file
17
src/storage/streak.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from .database import BaseModel
|
||||||
|
from peewee import TextField, ForeignKeyField, FloatField, BooleanField, DateField
|
||||||
|
|
||||||
|
|
||||||
|
class Streak(BaseModel):
|
||||||
|
name = TextField()
|
||||||
|
description = TextField
|
||||||
|
|
||||||
|
|
||||||
|
class StreakRecord(BaseModel):
|
||||||
|
reference_date = DateField(default=date.today)
|
||||||
|
is_achieved = BooleanField()
|
||||||
|
value = FloatField()
|
||||||
|
|
||||||
|
streak = ForeignKeyField(Streak, backref='records')
|
|
@ -1,22 +0,0 @@
|
||||||
<?php
|
|
||||||
|
|
||||||
require_once("config.php");
|
|
||||||
|
|
||||||
function markTodayAsSuccess(string $streakId)
|
|
||||||
{
|
|
||||||
global $STREAKS_FILE_SUFFIX;
|
|
||||||
$date = date("Y-m-d");
|
|
||||||
|
|
||||||
$entries = getStreakEntries($streakId);
|
|
||||||
if (in_array($date, $entries)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
file_put_contents($streakId . $STREAKS_FILE_SUFFIX, "$date\n", FILE_APPEND);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStreakEntries(string $streakId)
|
|
||||||
{
|
|
||||||
global $STREAKS_FILE_SUFFIX;
|
|
||||||
return explode("\n", trim(file_get_contents($streakId . $STREAKS_FILE_SUFFIX)));
|
|
||||||
}
|
|
|
@ -1,133 +0,0 @@
|
||||||
<?php
|
|
||||||
require_once("storage.php");
|
|
||||||
|
|
||||||
class Templates
|
|
||||||
{
|
|
||||||
public array $templates;
|
|
||||||
|
|
||||||
public function initialLoad()
|
|
||||||
{
|
|
||||||
foreach ($this->templates as $templateId => $template) {
|
|
||||||
$this->templates[$templateId] = parseObject($template, Template::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public function existsTemplate(string $templateId): bool
|
|
||||||
{
|
|
||||||
// Does key in array exist?
|
|
||||||
if (array_key_exists($templateId, $this->templates)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function findValidTemplate(Activity $activity): ?Template
|
|
||||||
{
|
|
||||||
if ($activity->template != "") {
|
|
||||||
if ($this->existsTemplate($activity->template)) {
|
|
||||||
return $this->templates[$activity->template];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for other validity conditions
|
|
||||||
$validTemplate = null;
|
|
||||||
foreach ($this->templates as $templateId => $template) {
|
|
||||||
if ($template->isValidFor($activity) && ($validTemplate == null || $template->priority > $validTemplate->priority)) {
|
|
||||||
$validTemplate = $template;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $validTemplate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function loadDefaultTemplates(): void
|
|
||||||
{
|
|
||||||
$awakeTemplate = new Template();
|
|
||||||
$awakeTemplate->title = "Awake";
|
|
||||||
$awakeTemplate->description = "";
|
|
||||||
$awakeTemplate->expectedDuration = 0;
|
|
||||||
$awakeTemplate->available = false;
|
|
||||||
$awakeTemplate->working = false;
|
|
||||||
$awakeTemplate->priority = 0;
|
|
||||||
$awakeTemplate->triggerOnlyOnEmptyTitle = true;
|
|
||||||
|
|
||||||
$this->templates["awake"] = $awakeTemplate;
|
|
||||||
|
|
||||||
$sleepingTemplate = new Template();
|
|
||||||
$sleepingTemplate->title = "Sleeping";
|
|
||||||
$sleepingTemplate->description = "";
|
|
||||||
$sleepingTemplate->expectedDuration = 60 * 60 * 8; // 8 hours
|
|
||||||
$sleepingTemplate->available = false;
|
|
||||||
$sleepingTemplate->working = false;
|
|
||||||
$sleepingTemplate->priority = 10;
|
|
||||||
$sleepingTemplate->triggerOnlyOnEmptyTitle = true;
|
|
||||||
$sleepingTemplate->triggerAfterTimeout = true;
|
|
||||||
$sleepingTemplate->timeout = 60 * 60 * 4; // 4 hours
|
|
||||||
|
|
||||||
$this->templates["sleeping"] = $sleepingTemplate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Template
|
|
||||||
{
|
|
||||||
public string $title;
|
|
||||||
public string $description;
|
|
||||||
public int $expectedDuration;
|
|
||||||
public bool $available;
|
|
||||||
public bool $working;
|
|
||||||
|
|
||||||
// Meta template information
|
|
||||||
public int $priority; // Higher priority templates are applied first
|
|
||||||
|
|
||||||
public bool $triggerOnlyOnEmptyTitle; // If true, the template will be only applied if the activity title is empty
|
|
||||||
|
|
||||||
public bool $triggerAfterTimeout; // If true, the template will be applied after the timeout
|
|
||||||
public int $timeout; // Timeout for trigger in seconds
|
|
||||||
|
|
||||||
public function __construct()
|
|
||||||
{
|
|
||||||
$this->title = "";
|
|
||||||
$this->description = "";
|
|
||||||
$this->expectedDuration = 0;
|
|
||||||
$this->available = false;
|
|
||||||
$this->working = false;
|
|
||||||
$this->priority = 0;
|
|
||||||
$this->triggerOnlyOnEmptyTitle = false;
|
|
||||||
$this->triggerAfterTimeout = false;
|
|
||||||
$this->timeout = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function applyTo(Activity $activity): void
|
|
||||||
{
|
|
||||||
$activity->title = $this->title;
|
|
||||||
$activity->description = $this->description;
|
|
||||||
$activity->expectedDuration = $this->expectedDuration;
|
|
||||||
$activity->available = $this->available;
|
|
||||||
$activity->working = $this->working;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isValidFor(Activity $activity): bool
|
|
||||||
{
|
|
||||||
// Is any trigger active?
|
|
||||||
if (!$this->triggerOnlyOnEmptyTitle && !$this->triggerAfterTimeout) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->triggerOnlyOnEmptyTitle && $activity->title != "") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($this->triggerAfterTimeout && $activity->getCurrentDuration() < $this->timeout) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function initialLoad(): void
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in a new issue