summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/__pycache__/main.cpython-314.pycbin4703 -> 34213 bytes
-rw-r--r--backend/__pycache__/notif.cpython-314.pycbin0 -> 3576 bytes
-rw-r--r--backend/db.dbbin8192 -> 0 bytes
-rw-r--r--backend/db.db-journalbin4616 -> 8720 bytes
-rw-r--r--backend/main.py559
-rw-r--r--backend/notif.py76
-rw-r--r--backend/notif_test.py14
-rw-r--r--backend/serviceAccountKey.json13
8 files changed, 625 insertions, 37 deletions
diff --git a/backend/__pycache__/main.cpython-314.pyc b/backend/__pycache__/main.cpython-314.pyc
index 35af45c..f65235c 100644
--- a/backend/__pycache__/main.cpython-314.pyc
+++ b/backend/__pycache__/main.cpython-314.pyc
Binary files differ
diff --git a/backend/__pycache__/notif.cpython-314.pyc b/backend/__pycache__/notif.cpython-314.pyc
new file mode 100644
index 0000000..e05be83
--- /dev/null
+++ b/backend/__pycache__/notif.cpython-314.pyc
Binary files differ
diff --git a/backend/db.db b/backend/db.db
deleted file mode 100644
index 20ded47..0000000
--- a/backend/db.db
+++ /dev/null
Binary files differ
diff --git a/backend/db.db-journal b/backend/db.db-journal
index b0fb71e..e58d84d 100644
--- a/backend/db.db-journal
+++ b/backend/db.db-journal
Binary files differ
diff --git a/backend/main.py b/backend/main.py
index 8b8a3d1..e242389 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -1,13 +1,27 @@
import fastapi
from fastapi.middleware.cors import CORSMiddleware
+from fastapi.responses import PlainTextResponse
+from apscheduler.schedulers.background import BackgroundScheduler
+import onesignal
+from onesignal.model.rate_limit_error import RateLimitError
+from onesignal.model.generic_error import GenericError
+from onesignal.model.notification import Notification
+from onesignal.model.create_notification_success_response import CreateNotificationSuccessResponse
+from onesignal.api import default_api
import sqlite3
from typing import Annotated
import datetime
+from zoneinfo import ZoneInfo
from pydantic import BaseModel
-from apscheduler.schedulers.background import BackgroundScheduler
-import firebase_admin
-from firebase_admin import credentials
+import secrets
+from enum import Enum, IntEnum
+from dotenv import load_dotenv
+from os import getenv
+import yaml
+import notif # import notif.py
+import bcrypt
+# ## API, db, and scheduler initialisation
app = fastapi.FastAPI(title="Victoria Hall LaundryWeb", description="LaundryWeb Backend API", version="0.1")
conn = sqlite3.connect("db.db", check_same_thread=False)
cursor = conn.cursor()
@@ -15,15 +29,16 @@ cursor = conn.cursor()
scheduler = BackgroundScheduler()
scheduler.start()
-firebase_app = firebase_admin.initialize_app(credentials.Certificate("serviceAccountKey.json"))
-
origins = [
"http://localhost",
+ "http://localhost:8081",
"http://localhost:998",
"http://localhost:5173",
"http://127.0.0.1",
"http://127.0.0.1:998",
"http://127.0.0.1:5173",
+ "https://laundryweb.altafcreator.com",
+ "https://backend.laundryweb.altafcreator.com"
]
app.add_middleware(
@@ -37,49 +52,545 @@ app.add_middleware(
cursor.execute("""
CREATE TABLE IF NOT EXISTS timers (
timer_id INTEGER PRIMARY KEY,
- user_id VARCHAR(16) NOT NULL,
+ user_id VARCHAR(64) NOT NULL,
start_time TEXT NOT NULL,
- duration INT NOT NULL,
+ end_time TEXT NOT NULL,
block INT NOT NULL,
machine INT NOT NULL,
- status TEXT NOT NULL CHECK(status IN ('Running', 'Finished'))
+ status TEXT NOT NULL CHECK(status IN ('RUNNING', 'FINISHED')),
+ subscription_id TEXT NOT NULL
+);""") # block is either 1 or 2, machine (1-4), odd is dryer, even is machine.
+
+cursor.execute("""
+CREATE TABLE IF NOT EXISTS admin_cookies (
+ cookie VARCHAR(64) PRIMARY KEY
);
- """) # block is either 1 or 2, machine (1-4), odd is dryer, even is machine.
+""")
+
+cursor.execute("DELETE FROM admin_cookies;")
+
+class RowIndices(IntEnum):
+ TIMER_ID = 0,
+ USER_ID = 1,
+ START_TIME = 2,
+ END_TIME = 3,
+ BLOCK = 4,
+ MACHINE = 5,
+ STATUS = 6,
+ SUBSCRIPTION_ID = 7
+
+
+# ## yaml configuration initialisation
+
+qr_uri = {}
+
+stream = open("config.yaml", 'r')
+yaml_dict = yaml.load(stream, yaml.Loader)
+
+# inverting the key-value pair for the dict to act as a map
+# easy matching for the users' scanned obscure uri and the supposed
+for k, v in yaml_dict["qr_uri"]["h1"].items():
+ qr_uri[v] = f"h1-{k}"
+for k, v in yaml_dict["qr_uri"]["h2"].items():
+ qr_uri[v] = f"h2-{k}"
+
+print("config.yaml loaded, qr_uri:")
+for k, v in qr_uri.items():
+ print(k, v)
+
+
+# ## onesignal initialisation
+load_dotenv()
+
+onesignal_configuration = onesignal.Configuration(
+ rest_api_key=getenv("REST_API_KEY"),
+ organization_api_key=getenv("ORGANIZATION_API_KEY"),
+)
+
+api_client = onesignal.ApiClient(onesignal_configuration)
+api_instance = default_api.DefaultApi(api_client)
+ONESIGNAL_APP_ID = "83901cc7-d964-475a-90ec-9f854df4ba52"
+
+
+# ## class / data struct definitions
class RequestData(BaseModel):
duration: int
+ machine_id: str
+
+
+class InformationRequestData(BaseModel):
+ machine_id: str
+
+
+class BlockRequestData(BaseModel):
+ block: int
+
+
+class FinishRequestData(BaseModel):
+ id: int
+
+
+class OverrideMachineData(BaseModel):
block: int
- machine: int
+ machine_id: int
+ disabled: bool
+
+
+class Status(Enum):
+ EMPTY = 0,
+ FINISHED = 1,
+ RUNNING = 2,
+ OUTOFSERVICE = 3,
+
+
+class PlaintextPasswordData(BaseModel):
+ password: str
+
+
+URI_TO_MACHINES = {
+ "h1-status": [1, None],
+ "h1-dryer1": [1, 1],
+ "h1-washer1": [1, 2],
+ "h1-dryer2": [1, 3],
+ "h1-washer2": [1, 4],
+ "h2-status": [2, None],
+ "h2-dryer1": [2, 1],
+ "h2-washer1": [2, 2],
+ "h2-dryer2": [2, 3],
+ "h2-washer2": [2, 4],
+}
+
+TZ = "Asia/Jakarta"
+
+# ## global vars for user-end
+
+machine_status = [[Status.EMPTY.name, Status.EMPTY.name, Status.EMPTY.name, Status.EMPTY.name],
+ [Status.EMPTY.name, Status.EMPTY.name, Status.EMPTY.name, Status.EMPTY.name]]
+machine_times = [[None, None, None, None],
+ [None, None, None, None]]
+machine_endings = [[None, None, None, None],
+ [None, None, None, None]]
+
+
+# ## some non-api endpoint method definitions
+
+# this method checks for any entry, and starts the previously-terminated schedules
+# useful if you're restarting the server
+def restart_terminated_schedules():
+ cursor.execute("SELECT * FROM timers;")
+ out = cursor.fetchall()
+ print("unfinished timers: " + str(len(out)))
+
+ for row in out:
+ print(row)
+ end_date = datetime.datetime.fromisoformat(row[RowIndices.END_TIME])
+ now = datetime.datetime.now(ZoneInfo(TZ))
+ timer_id = row[RowIndices.TIMER_ID]
+
+ if now > end_date:
+ print("unfinished timer was long gone", timer_id)
+ scheduler.add_job(final_timer_finished, 'date', run_date=(now + datetime.timedelta(seconds=1)), id=str(timer_id), args=[timer_id])
+ elif now + datetime.timedelta(minutes=5) > end_date:
+ print("unfinished timer ends in less than five mins", timer_id)
+ scheduler.add_job(final_timer_finished, 'date', run_date=end_date, id=str(timer_id), args=[timer_id])
+ else:
+ print("unfinished timer scheduler started", timer_id)
+ scheduler.add_job(reminder_timer_finished, 'date', run_date=end_date - datetime.timedelta(minutes=5), id=str(timer_id), args=[timer_id])
+
+ print("setting internal array information")
+ machine_status[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = Status.RUNNING.name
+ machine_times[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = row[2]
+ machine_endings[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = row[3]
+
+ print(machine_status, machine_times, machine_endings)
def reminder_timer_finished(timer_id):
+ print("timer almost finished", timer_id)
+ cursor.execute("SELECT * FROM timers WHERE timer_id = ?", (timer_id,))
+ out = cursor.fetchall()
+ print(out)
+
+ scheduler.add_job(final_timer_finished, 'date', run_date=out[0][RowIndices.END_TIME], id=str(timer_id), args=[timer_id])
+
+ notif.send_notification(
+ out[0][RowIndices.SUBSCRIPTION_ID],
+ {
+ "title": "Laundry Reminder",
+ "body": "Your laundry is almost finished.",
+ }
+ )
+
+def final_timer_finished(timer_id):
print("timer finished!1", timer_id)
- cursor.execute(f"DELETE FROM timers WHERE timer_id = {timer_id}")
+ cursor.execute("SELECT * FROM timers WHERE timer_id = ?", (timer_id,))
+ out = cursor.fetchall()
-def final_timer_finished():
- pass
+ for row in out:
+ machine_status[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = Status.FINISHED.name
+ notif.send_notification(
+ out[0][RowIndices.SUBSCRIPTION_ID],
+ {
+ "title": "Laundry Finished",
+ "body": "Your laundry is finished! Please collect your laundry.",
+ "requireInteraction": True,
+ "timerId": timer_id,
+ }
+ )
-@app.post("/start")
-def start_new_timer(data: RequestData, session_key: Annotated[str | None, fastapi.Cookie()] = None):
- if session_key:
+# sec min hrs days
+COOKIE_MAX_AGE = 60 * 60 * 24 * 30 # 30 days
+
+
+def create_session(response: fastapi.Response):
+ cookie = secrets.token_hex(32)
+ response.set_cookie(key="session_key", value=cookie, secure=True, max_age=COOKIE_MAX_AGE)
+ return cookie
+
+
+def authenticate_block(response: fastapi.Response, machine_id: str = None, block: int = None):
+ if machine_id:
+ blk = URI_TO_MACHINES[qr_uri[machine_id]][0]
+ response.set_cookie(key="auth_block", value=blk, secure=True, max_age=COOKIE_MAX_AGE)
+ return blk
+ elif block:
+ blk = block
+ response.set_cookie(key="auth_block", value=blk, secure=True, max_age=COOKIE_MAX_AGE)
+ return block
+ else:
+ return "FAIL"
+
+
+# ## beginning
+
+print("Hello, world!")
+restart_terminated_schedules()
+
+
+# ## api endpoints
+
+# --- starting new timer
+# eugh. too complex. TODO: refactor perhaps
+# it's so complex even the linter is complaining
+@app.post("/start", response_class=PlainTextResponse)
+def start_new_timer(data: RequestData, response: fastapi.Response, session_key: Annotated[str | None, fastapi.Cookie()] = None, auth_block: Annotated[str | None, fastapi.Cookie()] = None, subscription_endpoint: Annotated[str | None, fastapi.Cookie()] = None):
+ now = datetime.datetime.now(ZoneInfo(TZ))
+ try:
+ if not session_key:
+ print("no session key, creating.")
+ session_key = create_session(response)
+ except Exception as exception:
+ print("err @ key creation //", exception)
+ response.status_code = fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
+ return "something went wrong during session key creation"
+
+ try:
+ block = URI_TO_MACHINES[qr_uri[data.machine_id]][0]
+ machine = URI_TO_MACHINES[qr_uri[data.machine_id]][1]
+ except KeyError:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "invalid uri; you are unauthorised"
+
+ if auth_block:
+ if str(auth_block) != str(block):
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return "mismatch in block authentication cookie and requested block machine. forbidden."
+ else:
+ authenticate_block(response, machine_id=data.machine_id)
+
+ if not subscription_endpoint:
+ print("user is not subscribed to notif")
+ response.status_code = fastapi.status = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "a notification subscription is required to start a timer"
+
+ try:
print("session key valid", session_key)
- cursor.execute(f"""
- INSERT INTO timers (user_id, start_time, duration, block, machine, status)
- VALUES ('{session_key}', '{datetime.datetime.now().isoformat()}', {data.block}, {data.machine}, {data.duration}, 'Running')
- """)
+ end_date = now + datetime.timedelta(minutes=(data.duration * 30))
+ cursor.execute("""
+ INSERT INTO timers (user_id, start_time, end_time, block, machine, status, subscription_id)
+ VALUES (?, ?, ?, ?, ?, ?, ?)""", (session_key, now.isoformat(), end_date.isoformat(), block, machine, 'RUNNING', subscription_endpoint,))
conn.commit()
- cursor.execute(f"SELECT * FROM timers WHERE user_id = {session_key}")
+ cursor.execute("SELECT * FROM timers WHERE end_time = ?;", (end_date.isoformat(),))
out = cursor.fetchall()
+
for row in out:
print(row)
- end_date = datetime.datetime.now() + datetime.timedelta(seconds=data.duration)
timer_id = str(out[len(out) - 1][0])
print("timer id", timer_id)
+ except Exception as exception:
+ print("err @ sql stuff //", exception)
+ response.status_code = fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
+ return "something went wrong during sql stuff"
+
+ try:
+ scheduler.add_job(reminder_timer_finished, 'date', run_date=end_date - datetime.timedelta(minutes=5), id=timer_id, args=[timer_id])
+ except Exception as exception:
+ print("err @ scheduler //", exception)
+ response.status_code = fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
+ return "something went wrong during scheduler stuff"
+
+ try:
+ machine_status[block - 1][machine - 1] = Status.RUNNING.name
+ machine_times[block - 1][machine - 1] = now.isoformat()
+ machine_endings[block - 1][machine - 1] = end_date
+ except Exception as exception:
+ print("err @ machine_status //", exception)
+ response.status_code = fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
+ return "something went wrong during machine_status setting somehow"
+
+ # HTTP 200
+ return "all good bro timer started"
+
+
+# --- check whether user has laundry or not
+@app.post("/check", response_class=PlainTextResponse)
+def check_status(response: fastapi.Response, session_key: Annotated[str | None, fastapi.Cookie()] = None):
+ if not session_key:
+ print("no session key, creating.")
+ session_key = create_session(response)
+
+ cursor.execute("SELECT * FROM timers WHERE user_id = ?", (session_key,))
+ out = cursor.fetchall()
+
+ for row in out:
+ print(row)
+
+ if len(out) > 0:
+ return "you got laundry"
+ else:
+ return "no got laundry"
+
+
+# --- fetch machine status for block
+@app.post("/status")
+def get_machine_status(response: fastapi.Response, auth_block: Annotated[str | None, fastapi.Cookie()] = None):
+ if auth_block:
+ block = int(auth_block) - 1
+ return [machine_status[block], machine_times[block], machine_endings[block]]
+ else:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "block cookie needed. unauthorised"
+
+
+# --- get laundr(y/ies) information of user
+@app.post("/laundry")
+def get_laundry_info(response: fastapi.Response, session_key: Annotated[str | None, fastapi.Cookie()] = None, auth_block: Annotated[str | None, fastapi.Cookie()] = None):
+ if session_key:
+ result = []
+
+ cursor.execute("SELECT * FROM timers WHERE user_id = ?", (session_key,))
+ out = cursor.fetchall()
+
+ for row in out:
+ curr_timer = {
+ "end_time": 0,
+ "start_time": 0,
+ "machine": 0,
+ "status": "RUNNING",
+ "id": -1,
+ }
+ curr_timer["end_time"] = row[RowIndices.END_TIME]
+ curr_timer["start_time"] = row[RowIndices.START_TIME]
+ curr_timer["machine"] = row[RowIndices.MACHINE]
+ curr_timer["status"] = row[RowIndices.STATUS]
+ curr_timer["id"] = row[RowIndices.TIMER_ID]
+
+ result.append(curr_timer)
+
+ return result
+ else:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "you got no session key cookie how am i supposed to identify you"
+
+
+# --- finish one's laundry
+@app.post("/finish", response_class=PlainTextResponse)
+def finish_laundry(data: FinishRequestData, response: fastapi.Response, session_key: Annotated[str | None, fastapi.Cookie()] = None):
+ if session_key:
+ cursor.execute("SELECT * FROM timers WHERE timer_id = ?", (data.id,))
+ row = cursor.fetchall()[0]
+
+ if datetime.datetime.now(ZoneInfo(TZ)) < datetime.datetime.fromisoformat(row[RowIndices.END_TIME]):
+ response.status_code = fastapi.status.HTTP_400_BAD_REQUEST
+ return "timer has not finished yet"
+
+ machine_status[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = Status.EMPTY.name
+ machine_times[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = None
+ machine_endings[row[RowIndices.BLOCK] - 1][row[RowIndices.MACHINE] - 1] = None
+
+ cursor.execute("DELETE FROM timers WHERE timer_id = ?", (row[0],))
+ conn.commit()
+
+ print(f"timer of id {data.id} has been finished by {session_key}")
+
+ return "laundry finished"
+
+ if session_key != row[1]:
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return "session key mismatch with timer id, dubious!"
+ else:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "you got no session key, cannot"
+
+
+# --- get information from uri search query
+@app.post("/info")
+def uri_to_information(data: InformationRequestData, response: fastapi.Response, auth_block: Annotated[str | None, fastapi.Cookie()] = None):
+ info = None
+ try:
+ if len(data.machine_id) > 0:
+ info = URI_TO_MACHINES[qr_uri[data.machine_id]]
+ except KeyError:
+ response.status_code = fastapi.status.HTTP_404_NOT_FOUND
+ return "INVALID machine ID/URI"
+
+ print(auth_block)
+ if auth_block:
+ if info:
+ if str(auth_block) != str(info[0]):
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return "UNAUTHORISED to view information of block " + str(info[0])
+ else:
+ info = [auth_block, None]
+ else:
+ if info:
+ authenticate_block(response, block=info[0])
+ else:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "NO INFORMATION PROVIDED. NO AUTH COOKIE."
+
+ return {"block": info[0], "machine": info[1]}
+
+
+# #### NOTIFICATION API END POINTS ####
+
+
+# --- subscribe
+@app.post("/notifsubscribe", response_class=PlainTextResponse)
+def notif_subscribe(data: notif.PushSubscriptionData, response: fastapi.Response):
+ try:
+ endpoint = notif.subscribe(data)
+ response.set_cookie(key="subscription_endpoint", value=endpoint, max_age=COOKIE_MAX_AGE, domain="laundryweb.altafcreator.com", samesite="none", secure=True, path="/")
+ response.status_code = fastapi.status.HTTP_200_OK
+ return "subscription saved."
+ except Exception as err:
+ response.status_code = fastapi.status.HTTP_500_INTERNAL_SERVER_ERROR
+ return f"error, failed to save subscription {err}"
+
+
+# #### ADMIN PANEL API END POINTS ####
+
+# ## ADMIN PANEL SCHEDULER METHODS ##
+
+
+def delete_cookie_scheduler(cookie):
+ pass
+
+
+def authenticate_admin_check(cookie):
+ cursor.execute("SELECT * FROM admin_cookies WHERE cookie = ?", (cookie,))
+ rows = cursor.fetchall()
+
+ return len(rows) > 0
+
+
+# --- admin login
+@app.post("/admin_login", response_class=PlainTextResponse)
+def admin_login(data: PlaintextPasswordData, response: fastapi.Response):
+ print(data.password)
+
+ pwd = data.password.encode('utf-8')
+ stored_hash_pwd = getenv("ADMIN_PASSWORD_HASH").encode('utf-8')
+
+ if bcrypt.checkpw(pwd, stored_hash_pwd):
+ response.status_code = fastapi.status.HTTP_202_ACCEPTED
+
+ auth_cookie_str = secrets.token_hex(32)
+ AUTH_MAX_AGE = 60 * 10 # 10 minutes
+ response.set_cookie(key="admin_auth", value=auth_cookie_str, secure=True, max_age=AUTH_MAX_AGE, domain="backend.laundryweb.altafcreator.com", samesite="none")
+ cursor.execute("""INSERT INTO admin_cookies (cookie) VALUES (?);""", (auth_cookie_str,))
+ conn.commit()
+ cursor.execute("SELECT * FROM admin_cookies")
+ print(cursor.fetchall())
+
+ now = datetime.datetime.now(ZoneInfo(TZ))
+ end_date = now + datetime.timedelta(seconds=(AUTH_MAX_AGE))
+ scheduler.add_job(delete_cookie_scheduler, 'date', run_date=end_date, args=[auth_cookie_str])
+
+ return "hi admin you are Authenticated!!!11"
+
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return "Forbidden."
+
+
+# --- admin auth check
+@app.post("/admin_check", response_class=PlainTextResponse)
+def admin_check(response: fastapi.Response, admin_auth: Annotated[str | None, fastapi.Cookie()] = None):
+ print("admin check request, ", admin_auth)
+
+ if authenticate_admin_check(admin_auth):
+ response.status_code = fastapi.status.HTTP_202_ACCEPTED
+ return "Authorised."
+ else:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "Get out."
+
+
+# --- override each machine status
+@app.post("/override_status", response_class=PlainTextResponse)
+def override_status(data: OverrideMachineData, response: fastapi.Response, admin_auth: Annotated[str | None, fastapi.Cookie()] = None):
+ if not admin_auth:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "Unauthorised."
+
+ if authenticate_admin_check(admin_auth):
+ if (data.disabled):
+ machine_status[data.block - 1][data.machine_id - 1] = Status.OUTOFSERVICE.name
+ else:
+ cursor.execute("SELECT * FROM timers WHERE ((block = ?) AND (machine = ?))", (data.block, data.machine_id))
+ rows = cursor.fetchall()
+
+ if len(rows) > 0:
+ machine_status[data.block - 1][data.machine_id - 1] = Status.RUNNING.name
+ else:
+ machine_status[data.block - 1][data.machine_id - 1] = Status.EMPTY.name
+
+ response.status_code = fastapi.status.HTTP_200_OK
+ return "Set!"
+
+ print("set machine", data.machine_id, "of block", data.block, ".", machine_status)
+ else:
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return "Forbidden."
+
+
+# --- change admin password
+@app.post("/admin_change_password", response_class=PlainTextResponse)
+def admin_change_password(data: PlaintextPasswordData, response: fastapi.Response, admin_auth: Annotated[str | None, fastapi.Cookie()] = None):
+ if not admin_auth:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return "Unauthorised."
+
+ if authenticate_admin_check(admin_auth):
+ pass
+ else:
+ pass
+
+
+# --- get all blocks machine status for admin
+@app.post("/admin_machine_status")
+def admin_machine_status(response: fastapi.Response, admin_auth: Annotated[str | None, fastapi.Cookie()] = None):
+ if not admin_auth:
+ response.status_code = fastapi.status.HTTP_401_UNAUTHORIZED
+ return """{"reply": "Unauthorised."}"""
- scheduler.add_job(reminder_timer_finished, 'date', run_date=end_date, id=timer_id, args=[timer_id])
+ if authenticate_admin_check(admin_auth):
+ return machine_status
else:
- print("no session key")
+ response.status_code = fastapi.status.HTTP_403_FORBIDDEN
+ return """{"reply": "Forbidden."}"""
diff --git a/backend/notif.py b/backend/notif.py
new file mode 100644
index 0000000..eb25793
--- /dev/null
+++ b/backend/notif.py
@@ -0,0 +1,76 @@
+from pydantic import BaseModel
+import sqlite3
+from pywebpush import webpush
+from dotenv import load_dotenv
+from os import getenv
+import json
+
+class PushSubscriptionData(BaseModel):
+ endpoint: str
+ keys: object
+
+
+conn = sqlite3.connect("db.db", check_same_thread=False)
+cursor = conn.cursor()
+
+load_dotenv()
+
+PRIVATE_VAPID_KEY = getenv("PRIVATE_VAPID_KEY")
+
+cursor.execute("""
+CREATE TABLE IF NOT EXISTS subscriptions (
+ endpoint TEXT PRIMARY KEY,
+ keys_p256dh TEXT NOT NULL,
+ keys_auth TEXT NOT NULL
+);""")
+
+
+# --- subscribe
+def subscribe(data: PushSubscriptionData):
+ cursor.execute("SELECT * FROM subscriptions WHERE endpoint = ?", (data.endpoint,))
+ result = cursor.fetchall()
+
+ if len(result) > 0:
+ return data.endpoint
+
+ cursor.execute("""
+ INSERT INTO subscriptions (endpoint, keys_p256dh, keys_auth)
+ VALUES (?, ?, ?)""", (data.endpoint, data.keys["p256dh"], data.keys["auth"]))
+ conn.commit()
+
+ cursor.execute("SELECT * FROM subscriptions")
+ result = cursor.fetchall()
+
+ for row in result:
+ print(row)
+
+ return data.endpoint
+
+
+# --- send notification
+# ---- not used yet
+def send_notification(endpoint: str, notification_payload: object):
+ cursor.execute(f"""SELECT * FROM subscriptions WHERE endpoint = \"{endpoint}\"""")
+ row = cursor.fetchall()[0]
+ print(row)
+
+ subscription_info = {
+ "endpoint": endpoint,
+ "keys": {
+ "p256dh": row[1],
+ "auth": row[2],
+ },
+ }
+
+ try:
+ webpush(
+ subscription_info=subscription_info,
+ data=json.dumps(notification_payload),
+ vapid_private_key=PRIVATE_VAPID_KEY,
+ vapid_claims={
+ "sub": "mailto:dev@altafcreator.com",
+ },
+ headers={"Urgency": "high"}
+ )
+ except Exception as exception:
+ print(exception)
diff --git a/backend/notif_test.py b/backend/notif_test.py
new file mode 100644
index 0000000..b6f843e
--- /dev/null
+++ b/backend/notif_test.py
@@ -0,0 +1,14 @@
+import notif
+
+endpoint_to_test = "https://fcm.googleapis.com/fcm/send/f4Cw12WAYnE:APA91bHOZvh8HkExsem20UK_5uhzgCu0dEo01YV8G8-9hFBjEdsk1M49JVLD7Z51BDP7gxn_UAXVwvmYxIWajwQ9VyTS3ghNQzx92jex-isbz5IvzcpI0QOxaVnmoURLtK-qYt36ebic"
+endpoint_to_test = "https://fcm.googleapis.com/preprod/wp/dP2ULA5s2Fk:APA91bE6toPxR5LbakAronwoI2QfdSe6NIEAzVBoIq0Q5jUF8hkFskAT_PyHYwwUiJpJgF8xcCr_8CEzE83YTsUGZWyvcI53DiwjkDD4IK1UseK2bX-WaXVRRXBLM5wTHCKs1ZfG2rME"
+
+notif.send_notification(
+ endpoint_to_test,
+ {
+ "title": "Test Notification",
+ "body": "Hello, world pls work",
+ "requireInteraction": True,
+ "timerId": 0,
+ }
+)
diff --git a/backend/serviceAccountKey.json b/backend/serviceAccountKey.json
deleted file mode 100644
index e2f09e2..0000000
--- a/backend/serviceAccountKey.json
+++ /dev/null
@@ -1,13 +0,0 @@
-{
- "type": "service_account",
- "project_id": "victoria-hall-laundryweb-cm",
- "private_key_id": "597c9fc3dafbaf9a99fa51776fc0e78f7bde9e7d",
- "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDeEuAMyzZHDFOS\n4PPYMpTJtGyos14X2m7Tv5p+0ndJF8mb8g2yWGsq13wXebcXWzSwg98Dy0TBFsKN\njP+iXZ0KN7upG5LEW/M7sKl2oIC/IgnCvnrNqYHrK6BCKJ87jhxVoz5b/WE9yg+8\nVzBMDXKJaaZBVVV3p+UVQydiRd9qDYClRgU4wzGK3/B6hSYFmK66YslAUp/roERM\nkF3F+YRrli5iSvzf9+oyrv5W5SMQ5qdmhh2daNEFWyev9psOmKxVZ8iYi/tQdyrj\nlCX6ggcoJSDwKIbglasqscwwIV28PXDqLWKOvLa3lshaG7lZECra0ucTz+yv9KKx\nY+lJBkiLAgMBAAECggEAaN3J7lCxt1gzSOswStoL17n7C6jz2Fi+OADFMHkrehkh\nC6KAZEULikYKY72Gv6FCns4O7Gg69eFNCvHXr3D8XNxXojISmuX+1KOGSq3dTiSB\nLnSrYd3XK1nFCijLlKB1cd0t79uVZUu7JYmYlXlOKmR47WYh/2iJrYIyIV50c0u+\nRdtMQN4bIY2X9inlSd5IAeW9P6SCeHwaLGCJtKGbZ2d9Akseyi8r4yyzt2U2e8Cz\nAsY3qVlcdxqCY7BprhFbgOg/F6ahtLbZgEZBDqEjFBXUO78oTd1erKcqwq2hu+DB\n7D7JndCRGbJUGtX3HDNq79zxsCPMO7TrArAGZg0sBQKBgQD/nyTN7wqgshbYnTVh\nF3r9nw3NC3iuyo2EnVXDj6IC0EIxc9FzvuqDt7MaoN2PrvFEqfELX6AOYSQFdM4o\nWPz32jrmYiLpIUBfGwsedSF9PC1rwF/V2EKsVlpOJhM3ff0q+NNrCXOCcrO6S6ci\nYzEGu6nK/L8gVZgK3zxLF+ATjwKBgQDeZwUcVhBmEQRocuvnsPwapg+V3YZp6Oe+\nvdxHhfIOqXyTXokUBNL/tqyrMxduHgOsa50FcJQlt2T2CCEUKOZie2aW8h9jgzmK\nYy8H1hgqJ3PuVWod0pADmR6BucvewG7SYtJloi3FQoNwjPnpQPgs5nPOXn/NYPDd\nMZwsxNFNRQKBgQD4gt2jH7wzUjAH036rr8owe8tNBtQDCoTe4p8YirE1DwbrUO/W\nGUuhVpxXTQFZyPhiitz+866FjEcazt4GN81noNAWXl4WS+9rBr2elL1EJZb65IHH\nQoI/TYxp4XZZQrE4b8QKkZusvJqiiR4pvklRhGykHWgqpUw1daYGU2XTXQKBgQCS\no2RoQYrAcVnxsExA0J34yCMgkhZNAIbEahSyh+uIpVGBLWS+cUvZ1M1Wd/Jr5ZhQ\neAfpDUhw8dG8srF0xF7a8jg7MFtn1p760tiwlwYJzUeQDntQPIQ+CxhlOmbkcQbg\n6s2Eu4DoPinB7QHePLslh7E1kXM8rD1D6rDph7/k7QKBgA/mv928mWQ2WgcFEhZ3\nvKVFZgynklAHlby/RDyLETkvPwp3a54vWR1aeFhy39qcx6R6LsDkPbHUvaLPodHv\n+URzuZYr/bmVCqPbCDfXZ5A1OaUZDonwID6rJ7RLbo6VykEREQI0q7atLOInC6za\n/+9sIal321qEzmAKpEhvuUbi\n-----END PRIVATE KEY-----\n",
- "client_email": "firebase-adminsdk-fbsvc@victoria-hall-laundryweb-cm.iam.gserviceaccount.com",
- "client_id": "102261899996446059087",
- "auth_uri": "https://accounts.google.com/o/oauth2/auth",
- "token_uri": "https://oauth2.googleapis.com/token",
- "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
- "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fbsvc%40victoria-hall-laundryweb-cm.iam.gserviceaccount.com",
- "universe_domain": "googleapis.com"
-}