app.py 8.1 KB

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