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 time
|
||||
|
||||
|
||||
class BulletinWrapper:
|
||||
def __init__(self, data: dict):
|
||||
for i in ['author', 'id', 'subject', 'body', 'created_at', 'updated_at']:
|
||||
if i not in data:
|
||||
raise ValueError("Was not given a bulletin dictionary.")
|
||||
for i in ['id', 'author', 'subject', 'body', 'created_at', 'updated_at']:
|
||||
if i not in data.keys():
|
||||
raise ValueError("Data dict was not a bulletin dictionary.")
|
||||
self.data = data
|
||||
|
||||
def __repr__(self):
|
||||
@@ -16,7 +17,19 @@ class BulletinWrapper:
|
||||
|
||||
@property
|
||||
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
|
||||
def created(self) -> datetime.datetime:
|
||||
@@ -26,17 +39,19 @@ class BulletinWrapper:
|
||||
def updated(self) -> datetime.datetime:
|
||||
return datetime.datetime.fromisoformat(self.data['updated_at'])
|
||||
|
||||
@property
|
||||
def author(self) -> str:
|
||||
return self.data['author']
|
||||
|
||||
@property
|
||||
def subject(self) -> str:
|
||||
return self.data['subject']
|
||||
|
||||
@property
|
||||
def body(self) -> str:
|
||||
return self.data['body']
|
||||
def to_dict(self, json=True) -> dict:
|
||||
d = {
|
||||
'id': self.id,
|
||||
'author': self.author,
|
||||
'subject': self.subject,
|
||||
'body': self.body,
|
||||
'created_at': self.created,
|
||||
'updated_at': self.updated
|
||||
}
|
||||
if json:
|
||||
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:
|
||||
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}")
|
||||
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.path = "bulletin"
|
||||
req.set_var('id', bid)
|
||||
if only_subject:
|
||||
req.set_var('no_body', True)
|
||||
req.method = Request.Method.GET
|
||||
response = client.send_receive_callsign(req, bbs_callsign)
|
||||
if response.status_code != 200:
|
||||
raise RuntimeError(f"GET bulletin {bid} failed: {response.status_code}: {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.path = "bulletin"
|
||||
req.method = Request.Method.GET
|
||||
if limit is not None:
|
||||
req.set_var('limit', limit)
|
||||
if only_subject:
|
||||
req.set_var('no_body', True)
|
||||
response = client.send_receive_callsign(req, bbs_callsign)
|
||||
if response.status_code != 200:
|
||||
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:
|
||||
out_list.append(BulletinWrapper(b))
|
||||
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.object import objects
|
||||
from packetserver.client.cli.message import message
|
||||
from packetserver.client.cli.bulletin import bulletin
|
||||
import ZODB
|
||||
import ZODB.FileStorage
|
||||
import ax25
|
||||
@@ -191,6 +192,7 @@ cli.add_command(job, name='job')
|
||||
cli.add_command(objects, name='object')
|
||||
cli.add_command(set_user, name='set')
|
||||
cli.add_command(message)
|
||||
cli.add_command(bulletin)
|
||||
|
||||
if __name__ == '__main__':
|
||||
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}")
|
||||
bid = 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:
|
||||
try:
|
||||
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())
|
||||
if bull:
|
||||
response.payload = bull.to_dict()
|
||||
if only_subject:
|
||||
response.payload['body'] = ''
|
||||
response.status_code = 200
|
||||
else:
|
||||
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")
|
||||
bulls = Bulletin.get_recent_bulletins(db.root(), limit=limit)
|
||||
response.payload = [bulletin.to_dict() for bulletin in bulls]
|
||||
if only_subject:
|
||||
for b in response.payload:
|
||||
b['body'] = ''
|
||||
response.status_code = 200
|
||||
|
||||
send_response(conn, response, req)
|
||||
@@ -139,10 +148,38 @@ def handle_bulletin_update(req: Request, conn: PacketServerConnection, db: ZODB.
|
||||
send_response(conn, response, req)
|
||||
|
||||
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:
|
||||
pass
|
||||
send_response(conn, response, req)
|
||||
bull = Bulletin.get_bulletin_by_id(bid, db.root())
|
||||
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):
|
||||
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)
|
||||
elif req.method is Request.Method.POST:
|
||||
handle_bulletin_post(req, conn, db)
|
||||
elif req.method is Request.Method.DELETE:
|
||||
handle_bulletin_delete(req, conn ,db)
|
||||
else:
|
||||
send_blank_response(conn, req, status_code=404)
|
||||
|
||||
Reference in New Issue
Block a user