You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

app.py 3.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. from datetime import datetime
  2. import json
  3. import traceback
  4. import aiofiles
  5. import fastapi
  6. from fastapi import Cookie, File, Form, Request, UploadFile, WebSocket, WebSocketDisconnect
  7. from fastapi.responses import FileResponse, HTMLResponse, JSONResponse, RedirectResponse, PlainTextResponse
  8. from fastapi.staticfiles import StaticFiles
  9. from fastapi.templating import Jinja2Templates
  10. from fastapi.middleware.cors import CORSMiddleware
  11. import reporthook
  12. import sbenv
  13. import searchlib
  14. MAX_SEARCH_DAYS = 180
  15. MAX_SHOW_DAYS = 20
  16. ################################
  17. # Core configuration
  18. ################################
  19. app = fastapi.FastAPI(docs_url=None)
  20. origins = [
  21. 'https://prograde.gg',
  22. 'http://localhost',
  23. 'http://localhost:5000',
  24. 'http://localhost:8080',
  25. 'http://localhost:8000'
  26. ]
  27. app.add_middleware(
  28. CORSMiddleware,
  29. allow_origins=origins,
  30. allow_credentials=True,
  31. allow_methods=['*'],
  32. allow_headers=['*']
  33. )
  34. app.mount('/static', StaticFiles(directory='static'), name='static')
  35. tmplts = Jinja2Templates(directory='templates') # TODO Get the path correctly.
  36. ################################
  37. # User-facing endpoints
  38. ################################
  39. @app.exception_handler(Exception)
  40. async def handle_exception(req: Request, exc: Exception):
  41. tb = traceback.format_exc()
  42. await reporthook.send_report(tb)
  43. return PlainTextResponse('error', status_code=500)
  44. @app.get('/')
  45. async def render_main(req: Request):
  46. raw_articles = await load_days_from_file('testresults.json')
  47. converted = convert_days_from_articles(raw_articles)
  48. num_days = calc_num_days(converted)
  49. p = {
  50. 'sb': {
  51. 'num_days': num_days,
  52. 'days': converted
  53. },
  54. 'request': req,
  55. }
  56. return tmplts.TemplateResponse('main.htm', p)
  57. ################################
  58. # API endpoints
  59. ################################
  60. @app.post('/api/addarticle')
  61. async def handle_addarticle(req: Request):
  62. if not check_admin_token(req):
  63. return JSONResponse(status_code=403, content={'error': 'forbidden'})
  64. body = await req.json()
  65. add_article(body)
  66. return {'status': 'OK'}
  67. def add_article(article):
  68. # TODO
  69. pass
  70. ################################
  71. # Utilities
  72. ################################
  73. def check_admin_token(req: Request):
  74. ak = sbenv.get_admin_key()
  75. if ak is None:
  76. raise RuntimeError('checked api endpoint without key loaded')
  77. if ak == 'UNSAFE_TESTING':
  78. return True
  79. if 'Authorization' in req.headers:
  80. auth = req.headers['Authorization']
  81. if not auth.startswith('Bearer '):
  82. return False
  83. tok = auth[len('Bearer '):]
  84. return tok == sbenv.get_admin_key()
  85. else:
  86. return False
  87. async def load_days_from_file(path):
  88. async with aiofiles.open(path, mode='r') as f:
  89. contents = await f.read()
  90. return json.loads(contents)
  91. def convert_days_from_articles(rarts):
  92. processed = searchlib.process_results(rarts)
  93. output = []
  94. for dstr, arts in processed.items():
  95. day = {
  96. 'date': dstr,
  97. 'links': [convert_article(a) for a in arts['pass']],
  98. 'maybe_links': [convert_article(a) for a in arts['maybe']]
  99. }
  100. if len(day['links']) > 0:
  101. output.append(day)
  102. if len(output) > MAX_SHOW_DAYS:
  103. break
  104. return output
  105. def convert_article(a):
  106. return {
  107. 'url': a['url'],
  108. 'title': a['gtitle'],
  109. 'slug': a['slug'],
  110. }
  111. DATE_FORMAT = "%Y-%m-%d"
  112. def calc_num_days(dayslist):
  113. today = datetime.now()
  114. lowest = -1
  115. for d in dayslist:
  116. pd = datetime.strptime(d['date'], DATE_FORMAT)
  117. diff = today - pd
  118. ndays = diff.days
  119. if ndays < lowest or lowest == -1:
  120. lowest = ndays
  121. return lowest