app.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. import flask
  2. import lc.config as c
  3. import lc.error as e
  4. import lc.model as m
  5. import lc.request as r
  6. import lc.view as v
  7. from lc.web import Endpoint, endpoint, render
  8. app = c.app.app
  9. @endpoint("/")
  10. class Index(Endpoint):
  11. def html(self):
  12. pg = int(flask.request.args.get("page", 1))
  13. links, pages = m.Link.get_all(as_user=self.user, page=pg)
  14. linklist = v.LinkList(links=links, pages=pages, user="", tags=[])
  15. return render(
  16. "main",
  17. v.Page(
  18. title="main",
  19. content=render("linklist", linklist),
  20. user=self.user,
  21. ),
  22. )
  23. @endpoint("/auth")
  24. class Auth(Endpoint):
  25. def api_post(self):
  26. u, token = m.User.login(self.request_data(r.User))
  27. flask.session["auth"] = token
  28. return self.api_ok(u.base_url(), {"token": token})
  29. @endpoint("/login")
  30. class Login(Endpoint):
  31. def html(self):
  32. return render(
  33. "main",
  34. v.Page(
  35. title="login",
  36. content=render("login"),
  37. user=self.user,
  38. ),
  39. )
  40. @endpoint("/logout")
  41. class Logout(Endpoint):
  42. def html(self):
  43. if "auth" in flask.session:
  44. del flask.session["auth"]
  45. raise e.LCRedirect("/")
  46. def api_post(self):
  47. if "auth" in flask.session:
  48. del flask.session["auth"]
  49. return self.api_ok("/")
  50. @endpoint("/u")
  51. class CreateUser(Endpoint):
  52. def html(self):
  53. if self.user:
  54. raise e.LCRedirect(f"/u/{self.user.name}")
  55. token = flask.request.args.get("token")
  56. if not token:
  57. raise e.LCRedirect("/")
  58. add_user = v.AddUser(token=token)
  59. return render(
  60. "main",
  61. v.Page(
  62. title="add user",
  63. user=self.user,
  64. content=render("add_user", add_user),
  65. ),
  66. )
  67. def api_post(self):
  68. token = flask.request.args["token"]
  69. req = self.request_data(r.NewUser).to_user_request()
  70. u = m.User.from_invite(req, token)
  71. flask.session["auth"] = req.to_token()
  72. return self.api_ok(u.base_url(), u.to_dict())
  73. @endpoint("/u/<string:slug>")
  74. class GetUser(Endpoint):
  75. def html(self, slug: str):
  76. u = m.User.by_slug(slug)
  77. pg = int(flask.request.args.get("page", 1))
  78. tags = u.get_tags()
  79. links, pages = u.get_links(as_user=self.user, page=pg)
  80. linklist = v.LinkList(links=links, user=slug, pages=pages, tags=tags)
  81. return render(
  82. "main",
  83. v.Page(
  84. title=f"user {u.name}",
  85. content=render("linklist", linklist),
  86. user=self.user,
  87. ),
  88. )
  89. def api_get(self, slug: str):
  90. return m.User.by_slug(slug).to_dict()
  91. @endpoint("/u/<string:user>/config")
  92. class GetUserConfig(Endpoint):
  93. def html(self, user: str):
  94. u = self.require_authentication(user)
  95. status_msg = flask.request.args.get("m", None)
  96. if status_msg is not None:
  97. status_msg = int(status_msg)
  98. return render(
  99. "main",
  100. v.Page(
  101. title="configuration",
  102. content=render("config", u.get_config(status_msg)),
  103. user=self.user,
  104. ),
  105. )
  106. @endpoint("/u/<string:user>/invite")
  107. class CreateInvite(Endpoint):
  108. def api_post(self, user: str):
  109. u = self.require_authentication(user)
  110. invite = m.UserInvite.manufacture(u)
  111. return self.api_ok(f"/u/{user}/config", {"invite": invite.token})
  112. @endpoint("/u/<string:user>/password")
  113. class ChangePassword(Endpoint):
  114. def api_post(self, user: str):
  115. u = self.require_authentication(user)
  116. config_url = u.config_url()
  117. req = self.request_data(r.PasswordChange)
  118. try:
  119. req.require_match()
  120. except e.MismatchedPassword:
  121. raise e.LCRedirect(f"{config_url}?m=2")
  122. try:
  123. u.change_password(req)
  124. except e.BadPassword:
  125. raise e.LCRedirect(f"{config_url}?m=3")
  126. return self.api_ok(f"{config_url}?m=1")
  127. @endpoint("/u/<string:user>/l")
  128. class CreateLink(Endpoint):
  129. def html(self, user: str):
  130. u = self.require_authentication(user)
  131. url = flask.request.args.get("url", "")
  132. name = flask.request.args.get("name", "")
  133. tags = u.get_tags()
  134. defaults = v.AddLinkDefaults(
  135. user=user,
  136. name=name,
  137. url=url,
  138. all_tags=tags,
  139. )
  140. return render(
  141. "main",
  142. v.Page(
  143. title="login",
  144. content=render("add_link", defaults),
  145. user=self.user,
  146. ),
  147. )
  148. def api_post(self, user: str):
  149. u = self.require_authentication(user)
  150. req = self.request_data(r.Link)
  151. link = m.Link.from_request(u, req)
  152. return self.api_ok(link.link_url(), link.to_dict())
  153. @endpoint("/u/<string:user>/l/<string:link_id>")
  154. class GetLink(Endpoint):
  155. def api_get(self, user: str, link_id: str):
  156. u = self.require_authentication(user)
  157. link = u.get_link(int(link_id))
  158. return self.api_ok(link.link_url(), link.to_dict())
  159. def api_post(self, user: str, link_id: str):
  160. u = self.require_authentication(user)
  161. link = u.get_link(int(link_id))
  162. req = self.request_data(r.Link)
  163. link.update_from_request(u, req)
  164. raise e.LCRedirect(link.link_url())
  165. def api_delete(self, user: str, link_id: str):
  166. u = self.require_authentication(user)
  167. u.get_link(int(link_id)).full_delete()
  168. return self.api_ok(u.base_url())
  169. def html(self, user: str, link_id: str):
  170. link = m.User.by_slug(user).get_link(int(link_id))
  171. return render(
  172. "main",
  173. v.Page(
  174. title=f"link {link.name}",
  175. content=render(
  176. "linklist", v.LinkList([link.to_view(self.user)], [], user=user)
  177. ),
  178. user=self.user,
  179. ),
  180. )
  181. @endpoint("/u/<string:slug>/l/<string:link_id>/edit")
  182. class EditLink(Endpoint):
  183. def html(self, slug: str, link_id: str):
  184. u = self.require_authentication(slug)
  185. all_tags = u.get_tags()
  186. link = u.get_link(int(link_id))
  187. return render(
  188. "main",
  189. v.Page(
  190. title="login",
  191. content=render("edit_link", v.SingleLink(link, all_tags)),
  192. user=self.user,
  193. ),
  194. )
  195. @endpoint("/u/<string:user>/t/<path:tag>")
  196. class GetTaggedLinks(Endpoint):
  197. def html(self, user: str, tag: str):
  198. u = m.User.by_slug(user)
  199. pg = int(flask.request.args.get("page", 1))
  200. t = u.get_tag(tag)
  201. links, pages = t.get_links(as_user=self.user, page=pg)
  202. tags = u.get_related_tags(t)
  203. linklist = v.LinkList(links=links, pages=pages, tags=tags, user=user)
  204. return render(
  205. "main",
  206. v.Page(
  207. title=f"tag {tag}",
  208. content=render("linklist", linklist),
  209. user=self.user,
  210. ),
  211. )
  212. @endpoint("/u/<string:user>/search/<string:needle>")
  213. class GetStringSearch(Endpoint):
  214. def html(self, user: str, needle: str):
  215. u = m.User.by_slug(user)
  216. pg = int(flask.request.args.get("page", 1))
  217. links, pages = u.get_string_search(needle=needle, as_user=self.user, page=pg)
  218. tags = u.get_tags()
  219. linklist = v.LinkList(links=links, pages=pages, tags=tags, user=user)
  220. return render(
  221. "main",
  222. v.Page(
  223. title=f"search for '{needle}'",
  224. content=render("linklist", linklist),
  225. user=self.user,
  226. ),
  227. )
  228. @endpoint("/u/<string:user>/import")
  229. class PinboardImport(Endpoint):
  230. def html(self, user: str):
  231. _ = self.require_authentication(user)
  232. return render(
  233. "main",
  234. v.Page(
  235. title="import pinboard data",
  236. content=render("import"),
  237. user=self.user,
  238. ),
  239. )
  240. def api_post(self, user: str):
  241. u = self.require_authentication(user)
  242. if "file" not in flask.request.files:
  243. raise e.BadFileUpload("could not find attached file")
  244. file = flask.request.files["file"]
  245. if file.filename == "":
  246. raise e.BadFileUpload("no file selected")
  247. u.import_pinboard_data(file.stream)
  248. return self.api_ok(u.base_url())
  249. @endpoint("/service-worker.js")
  250. class ServiceWorker(Endpoint):
  251. def route(self, *args, **kwargs):
  252. return flask.send_file("../js/serviceWorker.js", mimetype="text/javascript")
  253. @endpoint("/add-link")
  254. class AddLink(Endpoint):
  255. def html(self):
  256. if not self.user:
  257. raise e.LCRedirect("/login")
  258. url = flask.request.args.get("text", None)
  259. name = flask.request.args.get("name", None)
  260. text = flask.request.args.get("text", None)
  261. # Android sets the text field to the url only
  262. if url is None and name is None and text is not None:
  263. args = f"url={text}"
  264. else:
  265. args = "&".join(f"{key}={value}" for key, value in flask.request.args.items())
  266. raise e.LCRedirect(f"/u/{self.user.name}/l?{args}")