Bulletins can be created and retrieved by the CLI now.
This commit is contained in:
@@ -4,11 +4,12 @@ from typing import Union, Optional
|
|||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
class BulletinWrapper:
|
class BulletinWrapper:
|
||||||
def __init__(self, data: dict):
|
def __init__(self, data: dict):
|
||||||
for i in ['author', 'id', 'subject', 'body', 'created_at', 'updated_at']:
|
for i in ['id', 'author', 'subject', 'body', 'created_at', 'updated_at']:
|
||||||
if i not in data:
|
if i not in data.keys():
|
||||||
raise ValueError("Was not given a bulletin dictionary.")
|
raise ValueError("Data dict was not a bulletin dictionary.")
|
||||||
self.data = data
|
self.data = data
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
@@ -16,7 +17,19 @@ class BulletinWrapper:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def id(self) -> int:
|
def id(self) -> int:
|
||||||
return self.data['id']
|
return int(self.data['id'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def author(self) -> str:
|
||||||
|
return str(self.data['author']).strip().upper()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subject(self) -> str:
|
||||||
|
return str(self.data['subject'])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def body(self) -> str:
|
||||||
|
return str(self.data['body'])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def created(self) -> datetime.datetime:
|
def created(self) -> datetime.datetime:
|
||||||
@@ -26,17 +39,19 @@ class BulletinWrapper:
|
|||||||
def updated(self) -> datetime.datetime:
|
def updated(self) -> datetime.datetime:
|
||||||
return datetime.datetime.fromisoformat(self.data['updated_at'])
|
return datetime.datetime.fromisoformat(self.data['updated_at'])
|
||||||
|
|
||||||
@property
|
def to_dict(self, json=True) -> dict:
|
||||||
def author(self) -> str:
|
d = {
|
||||||
return self.data['author']
|
'id': self.id,
|
||||||
|
'author': self.author,
|
||||||
@property
|
'subject': self.subject,
|
||||||
def subject(self) -> str:
|
'body': self.body,
|
||||||
return self.data['subject']
|
'created_at': self.created,
|
||||||
|
'updated_at': self.updated
|
||||||
@property
|
}
|
||||||
def body(self) -> str:
|
if json:
|
||||||
return self.data['body']
|
d['created_at'] = d['created_at'].isoformat()
|
||||||
|
d['updated_at'] = d['updated_at'].isoformat()
|
||||||
|
return d
|
||||||
|
|
||||||
def post_bulletin(client: Client, bbs_callsign: str, subject: str, body: str) -> int:
|
def post_bulletin(client: Client, bbs_callsign: str, subject: str, body: str) -> int:
|
||||||
req = Request.blank()
|
req = Request.blank()
|
||||||
@@ -48,22 +63,27 @@ def post_bulletin(client: Client, bbs_callsign: str, subject: str, body: str) ->
|
|||||||
raise RuntimeError(f"Posting bulletin failed: {response.status_code}: {response.payload}")
|
raise RuntimeError(f"Posting bulletin failed: {response.status_code}: {response.payload}")
|
||||||
return response.payload['bulletin_id']
|
return response.payload['bulletin_id']
|
||||||
|
|
||||||
def get_bulletin_by_id(client: Client, bbs_callsign: str, bid: int) -> BulletinWrapper:
|
def get_bulletin_by_id(client: Client, bbs_callsign: str, bid: int, only_subject=False) -> BulletinWrapper:
|
||||||
req = Request.blank()
|
req = Request.blank()
|
||||||
req.path = "bulletin"
|
req.path = "bulletin"
|
||||||
req.set_var('id', bid)
|
req.set_var('id', bid)
|
||||||
|
if only_subject:
|
||||||
|
req.set_var('no_body', True)
|
||||||
req.method = Request.Method.GET
|
req.method = Request.Method.GET
|
||||||
response = client.send_receive_callsign(req, bbs_callsign)
|
response = client.send_receive_callsign(req, bbs_callsign)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise RuntimeError(f"GET bulletin {bid} failed: {response.status_code}: {response.payload}")
|
raise RuntimeError(f"GET bulletin {bid} failed: {response.status_code}: {response.payload}")
|
||||||
return BulletinWrapper(response.payload)
|
return BulletinWrapper(response.payload)
|
||||||
|
|
||||||
def get_bulletins_recent(client: Client, bbs_callsign: str, limit: int = None) -> list[BulletinWrapper]:
|
def get_bulletins_recent(client: Client, bbs_callsign: str, limit: int = None,
|
||||||
|
only_subject=False) -> list[BulletinWrapper]:
|
||||||
req = Request.blank()
|
req = Request.blank()
|
||||||
req.path = "bulletin"
|
req.path = "bulletin"
|
||||||
req.method = Request.Method.GET
|
req.method = Request.Method.GET
|
||||||
if limit is not None:
|
if limit is not None:
|
||||||
req.set_var('limit', limit)
|
req.set_var('limit', limit)
|
||||||
|
if only_subject:
|
||||||
|
req.set_var('no_body', True)
|
||||||
response = client.send_receive_callsign(req, bbs_callsign)
|
response = client.send_receive_callsign(req, bbs_callsign)
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
raise RuntimeError(f"Listing bulletins failed: {response.status_code}: {response.payload}")
|
raise RuntimeError(f"Listing bulletins failed: {response.status_code}: {response.payload}")
|
||||||
@@ -71,3 +91,12 @@ def get_bulletins_recent(client: Client, bbs_callsign: str, limit: int = None) -
|
|||||||
for b in response.payload:
|
for b in response.payload:
|
||||||
out_list.append(BulletinWrapper(b))
|
out_list.append(BulletinWrapper(b))
|
||||||
return out_list
|
return out_list
|
||||||
|
|
||||||
|
def delete_bulletin_by_id(client: Client, bbs_callsign: str, bid: int):
|
||||||
|
req = Request.blank()
|
||||||
|
req.path = "bulletin"
|
||||||
|
req.set_var('id', bid)
|
||||||
|
req.method = Request.Method.DELETE
|
||||||
|
response = client.send_receive_callsign(req, bbs_callsign)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise RuntimeError(f"DELETE bulletin {bid} failed: {response.status_code}: {response.payload}")
|
||||||
@@ -8,6 +8,7 @@ from packetserver.client.cli.util import format_list_dicts, exit_client
|
|||||||
from packetserver.client.cli.job import job
|
from packetserver.client.cli.job import job
|
||||||
from packetserver.client.cli.object import objects
|
from packetserver.client.cli.object import objects
|
||||||
from packetserver.client.cli.message import message
|
from packetserver.client.cli.message import message
|
||||||
|
from packetserver.client.cli.bulletin import bulletin
|
||||||
import ZODB
|
import ZODB
|
||||||
import ZODB.FileStorage
|
import ZODB.FileStorage
|
||||||
import ax25
|
import ax25
|
||||||
@@ -191,6 +192,7 @@ cli.add_command(job, name='job')
|
|||||||
cli.add_command(objects, name='object')
|
cli.add_command(objects, name='object')
|
||||||
cli.add_command(set_user, name='set')
|
cli.add_command(set_user, name='set')
|
||||||
cli.add_command(message)
|
cli.add_command(message)
|
||||||
|
cli.add_command(bulletin)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
cli()
|
cli()
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import click
|
||||||
|
from packetserver.client.bulletins import (get_bulletins_recent, get_bulletin_by_id, delete_bulletin_by_id,
|
||||||
|
post_bulletin, BulletinWrapper)
|
||||||
|
from packetserver.client.cli.util import exit_client, format_list_dicts
|
||||||
|
from copy import deepcopy
|
||||||
|
import datetime
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
@click.group()
|
||||||
|
@click.pass_context
|
||||||
|
def bulletin(ctx):
|
||||||
|
"""List and create bulletins on the BBS."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("subject", type=str)
|
||||||
|
@click.argument("body", type=str)
|
||||||
|
@click.option('--from-file', '-f', is_flag=True, default=False,
|
||||||
|
help="Get body text from file or stdin ('-').")
|
||||||
|
@click.pass_context
|
||||||
|
def post(ctx, subject, body, from_file):
|
||||||
|
client = ctx.obj['client']
|
||||||
|
bbs = ctx.obj['bbs']
|
||||||
|
if subject.strip() == "":
|
||||||
|
exit_client(ctx.obj, 1, message="Can't have empty subject.")
|
||||||
|
|
||||||
|
text = ""
|
||||||
|
if from_file:
|
||||||
|
if body.strip() == "-":
|
||||||
|
text = sys.stdin.read()
|
||||||
|
else:
|
||||||
|
if not os.path.isfile(body):
|
||||||
|
exit_client(ctx.obj, 2, message=f"file {body} does not exist.")
|
||||||
|
text = open(body, 'r').read()
|
||||||
|
else:
|
||||||
|
text = body
|
||||||
|
|
||||||
|
try:
|
||||||
|
bid = post_bulletin(client, bbs, subject, text)
|
||||||
|
exit_client(ctx.obj,0, message=f"Created bulletin #{bid}!")
|
||||||
|
except Exception as e:
|
||||||
|
exit_client(ctx.obj, 4, message=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.option('--number', '-n', type=int, default=0, help="Number of bulletins to retrieve; default all.")
|
||||||
|
@click.option("--only-subject", '-S', is_flag=True, default=False, help="If set, don't retrieve body text.")
|
||||||
|
@click.option("--output-format", "-f", default="table", help="Print data as table[default], list, or JSON",
|
||||||
|
type=click.Choice(['table', 'json', 'list'], case_sensitive=False))
|
||||||
|
@click.pass_context
|
||||||
|
def list_bulletin(ctx, number, only_subject, output_format):
|
||||||
|
client = ctx.obj['client']
|
||||||
|
bbs = ctx.obj['bbs']
|
||||||
|
if number == 0:
|
||||||
|
number = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
bulletins = get_bulletins_recent(client, bbs, limit=number, only_subject=only_subject)
|
||||||
|
bulletin_dicts = [b.to_dict(json=True) for b in bulletins]
|
||||||
|
exit_client(ctx.obj, 0, message=format_list_dicts(bulletin_dicts, output_format=output_format))
|
||||||
|
except Exception as e:
|
||||||
|
exit_client(ctx.obj, 2, message=str(e))
|
||||||
|
|
||||||
|
|
||||||
|
@click.command()
|
||||||
|
@click.argument("bid", metavar="<BULLETIN ID>", type=int)
|
||||||
|
@click.option("--only-subject", '-S', is_flag=True, default=False, help="If set, don't retrieve body text.")
|
||||||
|
@click.option("--output-format", "-f", default="table", help="Print data as table[default], list, or JSON",
|
||||||
|
type=click.Choice(['table', 'json', 'list'], case_sensitive=False))
|
||||||
|
@click.pass_context
|
||||||
|
def get(ctx, bid, only_subject, output_format):
|
||||||
|
client = ctx.obj['client']
|
||||||
|
bbs = ctx.obj['bbs']
|
||||||
|
|
||||||
|
try:
|
||||||
|
bulletins = [get_bulletin_by_id(client, bbs, bid, only_subject=only_subject)]
|
||||||
|
bulletin_dicts = [b.to_dict(json=True) for b in bulletins]
|
||||||
|
exit_client(ctx.obj, 0, message=format_list_dicts(bulletin_dicts, output_format=output_format))
|
||||||
|
except Exception as e:
|
||||||
|
exit_client(ctx.obj, 2, message=str(e))
|
||||||
|
|
||||||
|
bulletin.add_command(post)
|
||||||
|
bulletin.add_command(list_bulletin, name = "list")
|
||||||
|
bulletin.add_command(get)
|
||||||
|
|||||||
@@ -82,6 +82,10 @@ def handle_bulletin_get(req: Request, conn: PacketServerConnection, db: ZODB.DB)
|
|||||||
logging.debug(f"bulletin get path: {sp}")
|
logging.debug(f"bulletin get path: {sp}")
|
||||||
bid = None
|
bid = None
|
||||||
limit = None
|
limit = None
|
||||||
|
only_subject = False
|
||||||
|
if 'no_body' in req.vars:
|
||||||
|
if type(req.vars['no_body']) is bool:
|
||||||
|
only_subject = req.vars['no_body']
|
||||||
if 'limit' in req.vars:
|
if 'limit' in req.vars:
|
||||||
try:
|
try:
|
||||||
limit = int(req.vars['limit'])
|
limit = int(req.vars['limit'])
|
||||||
@@ -107,6 +111,8 @@ def handle_bulletin_get(req: Request, conn: PacketServerConnection, db: ZODB.DB)
|
|||||||
bull = Bulletin.get_bulletin_by_id(bid, db.root())
|
bull = Bulletin.get_bulletin_by_id(bid, db.root())
|
||||||
if bull:
|
if bull:
|
||||||
response.payload = bull.to_dict()
|
response.payload = bull.to_dict()
|
||||||
|
if only_subject:
|
||||||
|
response.payload['body'] = ''
|
||||||
response.status_code = 200
|
response.status_code = 200
|
||||||
else:
|
else:
|
||||||
response.status_code = 404
|
response.status_code = 404
|
||||||
@@ -114,6 +120,9 @@ def handle_bulletin_get(req: Request, conn: PacketServerConnection, db: ZODB.DB)
|
|||||||
logging.debug(f"retrieving all bulletins")
|
logging.debug(f"retrieving all bulletins")
|
||||||
bulls = Bulletin.get_recent_bulletins(db.root(), limit=limit)
|
bulls = Bulletin.get_recent_bulletins(db.root(), limit=limit)
|
||||||
response.payload = [bulletin.to_dict() for bulletin in bulls]
|
response.payload = [bulletin.to_dict() for bulletin in bulls]
|
||||||
|
if only_subject:
|
||||||
|
for b in response.payload:
|
||||||
|
b['body'] = ''
|
||||||
response.status_code = 200
|
response.status_code = 200
|
||||||
|
|
||||||
send_response(conn, response, req)
|
send_response(conn, response, req)
|
||||||
@@ -139,10 +148,38 @@ def handle_bulletin_update(req: Request, conn: PacketServerConnection, db: ZODB.
|
|||||||
send_response(conn, response, req)
|
send_response(conn, response, req)
|
||||||
|
|
||||||
def handle_bulletin_delete(req: Request, conn: PacketServerConnection, db: ZODB.DB): # TODO
|
def handle_bulletin_delete(req: Request, conn: PacketServerConnection, db: ZODB.DB): # TODO
|
||||||
response = Response.blank()
|
username = ax25.Address(conn.remote_callsign).call.upper().strip()
|
||||||
|
sp = req.path.split("/")
|
||||||
|
if len(sp) > 1:
|
||||||
|
logging.debug(f"checking path for bulletin id")
|
||||||
|
try:
|
||||||
|
logging.debug(f"{sp[1]}")
|
||||||
|
bid = int(sp[1].strip())
|
||||||
|
except ValueError:
|
||||||
|
send_blank_response(conn, req, 400, "Invalid path.")
|
||||||
|
return
|
||||||
|
elif 'id' in req.vars:
|
||||||
|
try:
|
||||||
|
bid = int(req.vars['id'])
|
||||||
|
except ValueError:
|
||||||
|
send_blank_response(conn, req, 400, "Invalid id.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
send_blank_response(conn, req, 400)
|
||||||
|
return
|
||||||
|
|
||||||
with db.transaction() as db:
|
with db.transaction() as db:
|
||||||
pass
|
bull = Bulletin.get_bulletin_by_id(bid, db.root())
|
||||||
send_response(conn, response, req)
|
if bull:
|
||||||
|
if username != bull.author:
|
||||||
|
send_blank_response(conn, req, 401)
|
||||||
|
return
|
||||||
|
db.root.bulletins.remove(bull)
|
||||||
|
send_blank_response(conn, req, 200)
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
send_blank_response(conn, req, 404)
|
||||||
|
|
||||||
|
|
||||||
def bulletin_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
def bulletin_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.DB):
|
||||||
logging.debug(f"{req} being processed by bulletin_root_handler")
|
logging.debug(f"{req} being processed by bulletin_root_handler")
|
||||||
@@ -155,5 +192,7 @@ def bulletin_root_handler(req: Request, conn: PacketServerConnection, db: ZODB.D
|
|||||||
handle_bulletin_get(req, conn, db)
|
handle_bulletin_get(req, conn, db)
|
||||||
elif req.method is Request.Method.POST:
|
elif req.method is Request.Method.POST:
|
||||||
handle_bulletin_post(req, conn, db)
|
handle_bulletin_post(req, conn, db)
|
||||||
|
elif req.method is Request.Method.DELETE:
|
||||||
|
handle_bulletin_delete(req, conn ,db)
|
||||||
else:
|
else:
|
||||||
send_blank_response(conn, req, status_code=404)
|
send_blank_response(conn, req, status_code=404)
|
||||||
|
|||||||
Reference in New Issue
Block a user