- Lapis-chan is an open source imageboard web application written in Lua
- using the Lapis web framework.
-
-
-
-
What is Lapchan?
-
- Lapchan is a website that runs the latest version of Lapis-chan. It is
- both used as a small community and a testing platform for new and
- experimental features.
-
-
-
-
What Should I Know Before Posting?
-
- Before posting, you should read the rules for whichever board you want to
- post in. If you break the rules, your post may be deleted and you may
- also earn yourself a temporary or permanent ban from the board or entire
- website. Please read the rules!
-
-
-
-
What are the Basics?
-
- In general, "blue" boards are considered safe for work (SFW) and "red"
- boards are considered not safe for work (NSFW). The definition of
- work-safety is often loose, but in general it means there shouldn't be
- any direct pornographic material on a blue board. This may differ from
- site to site, but Lapis-chan by default offers blue and red themes that
- are nearly identical to 4chan's themes. New themes are expected in later
- releases.
-
-
- It is also worth noting that chan culture can be both friendly and
- abrasive. More often than not, users will be posting anonymously and are
- able to speak freely because if this. Be prepared for the best and worst
- of society when interacting with others in an anonymous forum.
-
-
-
-
How Do I Post Anonymously?
-
- By default, all users are anonymous to each other. By leaving the "Name"
- field empty in a post, your name will simply be fille din with the
- board's default name. Identifiable information such as your IP address is
- recorded to the Lapis-chan database when you post for legal reasons, but
- all posts are permanently purged after some time unless otherwise noted,
- including your IP address and any other information attached to your
- post.
-
-
-
-
Do I Have to Post an Image?
-
- Some boards require you to post an image or a comment, others do not. By
- default, Lapis-chan will place "(Required)" in or near a field that
- requires data. Currently, Lapis-chan's only optionally required data
- include a comment and an image.
-
-
-
-
How Do I Quote a Post?
-
- To quote (and link to) another post, simply type ">>" followed by
- the post number (e.g. >>2808). To quote a post that is on a
- different board, You must type ">>>" follow by a slash, the
- name of the board, another slash, and then the post number
- (e.g. >>>/a/2808).
-
-
-
-
What is a Tripcode?
-
- A tripcode is a uniquely identifiable hash attached to the end of your
- name, or in lieu of a name. It is a completely optional feature that
- allows users to de-anonymize if they so choose. Some boards benefit from
- de-anonymization such as content-creation boards where being named can
- help get your content seen and recognized.
-
-
- Tripcodes come in two sizes: insecure and secure. Insecure tripcodes use
- a weak hashing method that allows users to game the algorithm to generate
- a hash that reads out something similar to what they want. A secure
- tripcode uses a very secure hashing algorithm and a server-specific
- secret token that is not gameable, but also significantly more difficult
- to impersonate.
-
-
- To use an insecure tripcode, place a hash ("#") sign at the end of your
- name (or leave a name out entirely) and type your password after the
- hash. To use a secure tripcode, simply use two hashes instead of one
- (e.g. lapchan#insecure, lapchan##secure, #nameless, ##nameless).
-
-
-
-
Can I Mark an Image as a Spoiler?
-
- Yes. When you upload an image, there should be a check box beside the
- file input field. Checking that box will replace the thumbnail of your
- image with a spoiler image.
-
-
- You can also tag text within your post as a spoiler by writing your text
- with [spoiler]a spoiler tag[/spoiler].
-
-
-
-
What Are Post Options?
-
- Post options are optional features you can use to modify how your post
- affects the thread or board you are posting in. To apply an options,
- simply type the option code into the options field in your post.
- Currently, the options available include:
-
-
-
sage - Do not bump the thread with your post.
-
-
-
-
How Can I Interact With Posts?
-
- To interact with a post, click on the menu icon ("▶") on the left of the
- post. The menu currently has the following interactions:
-
-
-
- Report Post - Report a post to moderators that you believe is breaking
- the rules of the board.
-
-
- Delete Post - Delete your own post. Lapis-chan saves a unique password
- to your user session when you make your first post and will allow you
- to delete any post you make as long as you are using the same session.
-
-
- Remix Image - Draw boards have a remix feature that allows you to copy
- the image from a post into the drawing canvas and draw on top of it.
- You can then post your new image in the thread to show off your
- updated image.
-
-
-
-
-
What Types of Boards are Supported?
-
- Lapis-chan has several different types of boards with more planned in the
- future. Currently, Lapis-chan supports the following boards:
-
-
-
- Image boards - Upload images and chat with other people about various
- topics or sub-topics. Common image boards include discussing your
- favourite TV shows, video games, or characters within.
-
-
- Text boards - Strictly text. Common text boards include discussing
- latest events, breaking news, politics, or writing stories.
-
-
- Draw boards - Upload, draw, and remix images. Common draw boards
- include art critiquing and art remixing.
-
-
-
-
-]]
-
-local success = [[
-
- Congratulations! Lapis-chan is now installed! Please rename or delete the
- `install.lua` file to see your new board. Visit "/admin" to get started!
-
-
Thank you for installing Lapis-chan! <3
-]]
-
-return {
- GET = function(self)
- self.page_title = "Install Lapis-chan"
- self.board = { theme = "yotsuba_b" }
-
- -- Do we already have data?
- local users = Users:get_users()
- local boards = Boards:get_boards()
- local pages = Pages:get_pages()
-
- -- We did it!
- if #users > 0 and #boards > 0 and #pages > 0 then
- return success
- end
-
- -- Get list of themes
- self.themes = {}
- for file in lfs.dir("."..self.styles_url) do
- local name, ext = string.match(file, "^(.+)(%..+)$")
- if name ~= "reset" and
- name ~= "posts" and
- name ~= "style" and
- name ~= "tegaki" and
- ext == ".css" then
- table.insert(self.themes, name)
- end
- end
-
- return { render = "install" }
- end,
- POST = function(self)
- self.page_title = "Install Lapis-chan"
- self.board = { theme = "yotsuba_b" }
-
- -- Do we already have data?
- local users = Users:get_users()
- local boards = Boards:get_boards()
- local pages = Pages:get_pages()
-
- -- We did it!
- if #users > 0 and #boards > 0 and #pages > 0 then
- return success
- end
-
- local errs = validate(self.params, {
- { "user_username", exists=true, max_length=255 },
- { "user_password", min_length=4, max_length=255 },
- { "name", exists=true, max_length=10 },
- { "title", min_length=2, max_length=255 },
- { "subtext", max_length=255 },
- { "rules" },
- { "ban_message", max_length=255 },
- { "anon_name", max_length=255 },
- { "theme", exists=true },
- { "pages", exists=true },
- { "threads_per_page", exists=true },
- { "thread_file_limit", exists=true },
- { "post_limit", exists=true },
- { "thread_file", exists=true },
- { "thread_comment", exists=true },
- { "post_file", exists=true },
- { "post_comment", exists=true },
- { "text_only", exists=true },
- { "filetype_image", exists=true },
- { "filetype_audio", exists=true },
- { "draw", exists=true },
- { "archive", exists=true },
- { "archive_time", exists=true },
- { "group", exists=true }
- })
-
- local out
- if errs then
- out = "
\n"
- for _, err in ipairs(errs) do
- out = out .. "
" .. err .. "
\n"
- end
- out = out .. [[
-
-
- ]]
- end
-
- if out then
- return out
- end
-
- -- Add new user
- Users:create_user {
- username = self.params.user_username,
- password = self.params.user_password,
- admin = true,
- mod = false,
- janitor = false
- }
-
- -- Add new board
- Boards:create_board {
- name = self.params.name,
- title = self.params.title,
- subtext = self.params.subtext,
- rules = self.params.rules,
- anon_name = self.params.anon_name,
- theme = self.params.theme,
- posts = 0,
- pages = self.params.pages,
- threads_per_page = self.params.threads_per_page,
- text_only = self.params.text_only,
- filetype_image = self.params.filetype_image,
- filetype_audio = self.params.filetype_audio,
- draw = self.params.draw,
- thread_file = self.params.thread_file,
- thread_comment = self.params.thread_comment,
- thread_file_limit = self.params.thread_file_limit,
- post_file = self.params.post_file,
- post_comment = self.params.post_comment,
- post_limit = self.params.post_limit,
- archive = self.params.archive,
- archive_time = self.params.archive_time * 24 * 60 * 60,
- group = self.params.group
- }
-
- -- Add FAQ page
- Pages:create_page {
- title = "Frequently Asked Questions",
- slug = "faq",
- content = faq
- }
- end
-}
diff --git a/misc/website/backend/app/src/apps/web/pages.lua b/misc/website/backend/app/src/apps/web/pages.lua
deleted file mode 100755
index 54d4547..0000000
--- a/misc/website/backend/app/src/apps/web/pages.lua
+++ /dev/null
@@ -1,14 +0,0 @@
-local lapis = require "lapis"
-local app = lapis.Application()
-app.__base = app
-app.name = "web.pages."
-app.handle_404 = require "apps.web.internal.code_404"
-
-app:match("index", "/", require "apps.web.pages.index")
-app:match("c404", "/404", require "apps.web.internal.code_404") -- FIXME: remove this route
-app:match("faq", "/faq", require "apps.web.pages.rules") -- FIXME: need a faq page
-app:match("rules", "/rules", require "apps.web.pages.rules")
-app:match("logout", "/logout", require "apps.web.pages.logout")
-app:match("page", "/:page", require "apps.web.pages.page")
-
-return app
diff --git a/misc/website/backend/app/src/apps/web/pages/index.lua b/misc/website/backend/app/src/apps/web/pages/index.lua
deleted file mode 100755
index 64c32e4..0000000
--- a/misc/website/backend/app/src/apps/web/pages/index.lua
+++ /dev/null
@@ -1,10 +0,0 @@
-return function(self)
-
- -- Page title
- self.page_title = self.i18n("index")
-
- -- Display a theme
- self.board = { theme = "yotsuba_b" }
-
- return { render = "index" }
-end
diff --git a/misc/website/backend/app/src/apps/web/pages/logout.lua b/misc/website/backend/app/src/apps/web/pages/logout.lua
deleted file mode 100755
index b7d0ccd..0000000
--- a/misc/website/backend/app/src/apps/web/pages/logout.lua
+++ /dev/null
@@ -1,9 +0,0 @@
-return function(self)
- -- Logout
- self.session.name = nil
- self.session.admin = nil
- self.session.mod = nil
- self.session.janitor = nil
-
- return { redirect_to = self:url_for("web.pages.index") }
-end
diff --git a/misc/website/backend/app/src/apps/web/pages/page.lua b/misc/website/backend/app/src/apps/web/pages/page.lua
deleted file mode 100755
index 36add7f..0000000
--- a/misc/website/backend/app/src/apps/web/pages/page.lua
+++ /dev/null
@@ -1,28 +0,0 @@
-local Boards = require "models.boards"
-local Pages = require "models.pages"
-local markdown = require "markdown"
-
-return function(self)
- -- Get all board data
- self.boards = Boards:get_boards()
-
- -- Get page
- self.page = Pages:get_page(self.params.page)
-
- if not self.page then
- return self:write({ redirect_to = self:url_for("web.pages.c404") })
- end
-
- -- Page title
- self.page_title = self.page.title
-
- -- Markdown
- if self.page.content then
- self.page.content = markdown(self.page.content)
- end
-
- -- Display a theme
- self.board = { theme = "yotsuba_b" }
-
- return { render = "page" }
-end
diff --git a/misc/website/backend/app/src/apps/web/pages/rules.lua b/misc/website/backend/app/src/apps/web/pages/rules.lua
deleted file mode 100755
index f45e50e..0000000
--- a/misc/website/backend/app/src/apps/web/pages/rules.lua
+++ /dev/null
@@ -1,19 +0,0 @@
-return function(self)
-
- -- Page title
- self.page_title = self.i18n("rules")
-
- -- Display a theme
- self.board = { theme = "yotsuba_b" }
-
- for _, board in ipairs(self.boards) do
- board.url = self:url_for("web.boards.board", { board=board.name })
- if board.rules then
- board.rules = _G.markdown(board.rules)
- else
- board.rules = ""
- end
- end
-
- return { render = "rules" }
-end
diff --git a/misc/website/backend/app/src/locale/fr.lua b/misc/website/backend/app/src/locale/fr.lua
deleted file mode 100755
index 654f586..0000000
--- a/misc/website/backend/app/src/locale/fr.lua
+++ /dev/null
@@ -1,234 +0,0 @@
-return { fr = {
-
- --==[[ Navigation ]]==--
-
- archive = "Archive",
- bottom = "Bas",
- catalog = "Catalogue",
- index = "Index",
- refresh = "Rafraîchir",
- ["return"] = "Retourner",
- return_board = "Retourner au babillard",
- return_index = "Retourner à l'index",
- return_thread = "Retourner au fil de discussion",
- top = "Haut",
-
- --==[[ Error Messages ]]==--
-
- -- Controller error messages
- err_ban_reason = "Aucune Reçue.",
- err_board_used = "Le nom du Babillard est déjà utilisé.",
- err_not_admin = "Vous n'êtes pas un administrateur.",
- err_orphaned = "Le Fil de Discussion nº%s est orphelin.",
- err_slug_used = "Le slug de la page est déjà utilisée.",
- err_user_used = "Le nom d'utilisateur est dèja utilisé.",
-
- -- Model error messages
- err_contribute = "Vous devez poster un commentaire ou un fichier.",
- err_locked_thread = "Le fil de discussion No.%s est verrouillé.",
- err_no_files = "Les fichiers ne sont pas acceptés sur ce babillard.",
-
- err_comment_post = "Un commentaire est requis pour poster sur ce babillard.",
- err_comment_thread = "Un commentaire est requis pour poster un fil de discussion sur ce babillard.",
-
- err_create_ann = "Impossible de créer un annoncement: %s.",
- err_create_ban = "Impossible de bannir l'IP: %s.",
- err_create_board = "Impossible de créer le babillard: /%s/ - %s.",
- err_create_page = "Impossible de créer la page: /%s/ - %s.",
- err_create_post = "Impossible de soumettre le poste.",
- err_create_report = "Impossible de reporter le post No.%s.",
- err_create_thread = "Impossible de créer un nouveau fil de discussion.",
-
- err_delete_board = "Impossible de supprimer le babillard: /%s/ - %s.",
- err_delete_post = "Impossible de supprimer le poste No.%s.",
- err_create_user = "Impossible de créer l'utilisateur: %s.",
-
- err_file_exists = "Le fichier existe déjà sur ce babillard.",
- err_file_limit = "Le fil de discussion No.%s a atteint sa limite de fichier.",
- err_file_post = "Un fichier est requis pour poster sur ce babillard.",
- err_file_thread = "Un fichier est requis pour poster un fil de discussion sur ce babillard.",
-
- err_invalid_board = "Babillard invalide: /%s/.",
- err_invalid_ext = "Type de fichier invalide: %s.",
- err_invalid_image = "Données d'image invalides.",
- err_invalid_post = "Le poste No.%s n'est pas valide.",
- err_invalid_user = "Nom d'utilisateur ou mot de passe invalide.",
-
- --==[[ 404 ]]==--
-
- ["404"] = "404 - Page Introuvable",
-
- --==[[ Administration ]]==--
-
- -- General
- admin_panel = "Panneau Administratif",
- administrator = "Administrateur",
- announcement = "Annoncement",
- archive_days = "Nombre de jours à garder les Fils de Discussion dans l'Archive",
- archive_pruned = "Archiver les Fils de Discussion Réduites",
- board = "Babillard",
- board_group = "Group de babillard",
- board_title = "Nom de Babillard",
- bump_limit = "Limite pour remonter un fil de discussion",
- content_md = "Contenu (Markdown)",
- default_name = "Nom par Défaut",
- draw_board = "Babillard à Dessin",
- file = "Fichier",
- file_limit = "Limite de fichiers dans un Fil de Discussion",
- global = "Global",
- index_boards = "Babillards Présent",
- janitor = "Concierge",
- login = "Connexion",
- logout = "Déconnexion",
- moderator = "Modérateur",
- num_pages = "Pages Actives",
- num_threads = "Fils de Discussion par Page",
- password = "Mot de Passe",
- password_old = "Ancient Mot de Passe",
- password_retype = "Confirmer le Mot de Passe",
- post_comment_required = "Exiger un commentaire pour pouvoir poster",
- post_file_required = "Exiger un Fichier pour pouvoir poster",
- regen_thumb = "Régénérer les Miniatures",
- reply = "Répondre",
- rules = "Les Règles",
- name = "Nom",
- subtext = "Sous-texte",
- success = "Succès",
- text_board = "Babillard à Texte",
- theme = "Thème",
- thread_comment_required = "Exiger un Commentaire ",
- thread_file_required = "Exiger un Fil de Discussion",
- slug = "Slug",
- username = "Nom d'utilisateur",
- yes = "Oui",
- no = "Non",
-
- -- Announcements
- create_ann = "Créer un Annoncement",
- modify_ann = "Modifier un Annoncement",
- delete_ann = "Supprimer un Annoncement",
- created_ann = "Création de l'annoncement: %s réussie.",
- modified_ann = "Modification de l'annoncement: %s réussie.",
- deleted_ann = "Suppression de l'annoncement: %s réussie.",
-
- -- Boards
- create_board = "Créer un Babillard",
- modify_board = "Modifier un Babillard",
- delete_board = "Supprimer un Babillard",
- created_board = "Création du Babillard: /%s/ - %s réussie.",
- modified_board = "Modification du Babillard : /%s/ - %s réussie.",
- deleted_board = "Suppression du Babillard : /%s/ - %s réussie.",
-
- -- Pages
- create_page = "Créer une Page",
- modify_page = "Modifier une Page",
- delete_page = "Supprimer une Page",
- created_page = "Création de la page : /%s/ - %s réussie.",
- modified_page = "Modification de la page : /%s/ - %s réussie.",
- deleted_page = "Suppression de la page : /%s/ - %s réussie.",
-
- -- Reports
- view_report = "Afficher un Rapport",
- delete_report = "Supprimer un Rapport",
- deleted_report = "Suppression du Rapport: %s réussie.",
-
- -- Users
- create_user = "Créer un Utilisateur",
- modify_user = "Modifier un Utilisateur",
- delete_user = "Supprimer un Utilisateur",
- created_user = "Création de l'utilisateur: %s réussie.",
- modified_user = "Modification de l'utilisateur: %s réussie.",
- deleted_user = "Suppression de l'utilisateur: %s réussie.",
-
- --==[[ Archive ]]==--
-
- arc_display = "Affichage de %{n_thread} %{p_thread} expirés depuis %{n_day} %{p_day} ",
- arc_number = "nº",
- arc_name = "Nom",
- arc_excerpt = "Extrait",
- arc_replies = "Réponses",
- arc_view = "Afficher",
-
- --==[[ Ban ]]==--
-
- ban_title = "Banni!",
- ban_reason = "Vous avez été banni pour la raison suivante:",
- ban_expire = "Votre ban expirera le %{expire}.",
- ban_ip = "Selon avec notre serveur, votre IP est: %{ip}.",
-
- --==[[ Catalog ]]==--
-
- cat_stats = "R: %{replies} / F: %{files}",
-
- --==[[ Copyright ]]==--
-
- copy_software = "Réalisé avec %{software} %{version}",
- copy_download = "Télécharger à partir de %{github}",
-
- --==[[ Forms ]]==--
-
- form_ban = "Bannir l'Utilisateur",
- form_ban_display = "Afficher le Ban",
- form_ban_board = "Ban Local",
- form_ban_reason = "Raison du ban",
- form_ban_time = "Durée (en jours) à bannir l'utilisateur",
- form_clear = "Effacer",
- form_delete = "Supprimer le Poste",
- form_draw = "Dessiner",
- form_lock = "Verrouiller le Fil de Discussion",
- form_override = "Fichiers Illimités",
- form_readme = "Veuillez lire [%{rules}] et la [%{faq}] avant de poster.",
- form_remix = "Remixer l'Image",
- form_report = "Reporter le Poste",
- form_required = "Champ Requis",
- form_save = "Épargner le Fil de Discussion",
- form_sticky = "Épingler le Fil de Discussion",
- form_submit = "Soumettre le Poste",
- form_submit_name = "Nom",
- form_submit_name_help = "Donnez-vous un nom , un tripcode ou les deux (facultatif)",
- form_submit_subject = "Sujet",
- form_submit_subject_help = "Définir le sujet de la discussion (facultatif)",
- form_submit_options = "Options",
- form_submit_options_help = "Sage: poster sans faire monter le fil de discussion (À venir) (facultatif)",
- form_submit_comment = "Commentaire",
- form_submit_comment_help = "Contribuer à la discussion (ou non)",
- form_submit_file = "Fichier",
- form_submit_file_help = "Télécharger un fichier",
- form_submit_draw = "Dessiner",
- form_submit_draw_help = "Dessiner ou remixer une image",
- form_submit_spoiler = "Spoiler",
- form_submit_spoiler_help = "Replacer la miniature avec une image sans spoiler",
- form_submit_mod = "Modérateur",
- form_submit_mod_help = "Mettre un drapeau sur ce fil de discussion",
- form_width = "Largeur",
- form_height = "Hauteur",
-
- --==[[ Posts ]]==--
-
- post_link = "Lier à ce poste",
- post_lock = "Le fil de discussion est verrouillé",
- post_hidden = "%{n_post} %{p_post} et %{n_file} %{p_file} omis. %{click} pour afficher.",
- post_override = "Ce fil de discussion accepte un nombre illimité de fichiers",
- post_reply = "Répondre à ce poste",
- post_sticky = "Le fil de discussion est épinglé",
- post_save = "Le fil de discussion ne sera pas enlevé automatiquement",
-
- --==[[ Plurals ]]==--
-
- days = {
- one = "jour",
- other = "jours"
- },
- files = {
- one = "fichier",
- other = "fichiers"
- },
- posts = {
- one = "poste",
- other = "postes"
- },
- threads = {
- one = "fil de discussion",
- other = "fils de discussion"
- },
-}}
diff --git a/misc/website/backend/app/src/locale/phpceo.lua b/misc/website/backend/app/src/locale/phpceo.lua
deleted file mode 100755
index 8f399d9..0000000
--- a/misc/website/backend/app/src/locale/phpceo.lua
+++ /dev/null
@@ -1,234 +0,0 @@
-return { phpceo = {
-
- --==[[ Navigation ]]==--
-
- archive = "Gallery Of Our Greatness",
- bottom = "Best For Last",
- catalog = "Products And Services",
- index = "Home Page",
- refresh = "Experience A Rejuvinating Meeting",
- ["return"] = "Reconsider",
- return_board = "Reconsider that product",
- return_index = "Reconsider that home page",
- return_thread = "Reconsider that email chain",
- top = "I'm The Best",
-
- --==[[ Error Messages ]]==--
-
- -- Controller error messages
- err_ban_reason = "Hit 'enter' too early.",
- err_board_used = "That product name is in use, and we don't want to spend money on lawyers.",
- err_not_admin = "Call HR.",
- err_orphaned = "I accidentally lost the email chain about %s, do you know where it went?",
- err_slug_used = "SEO CONFLICT, GOOGLE GROWTHHACK ERROR.",
- err_user_used = "You can't be Brandon, *I'm* Brandon!",
-
- -- Model error messages
- err_contribute = "Your argument needs more pictures or more words. I can't decide which. I'm fickle like that.",
- err_locked_thread = "Email chain about %s is locked, I blame Outlook.",
- err_no_files = "The cloud is busy, it does not want your attachments.",
-
- err_comment_post = "Your pitch is great, except I don't know what you're talking about. Use your words.",
- err_comment_thread = "Did you seriously just try to send an email without a body? At least put in your signature. Have some pride, Intern.",
-
- err_create_ann = "I couldn't stop yelling long enough to say: %s.",
- err_create_ban = "These lottery numbers didn't work: %s.",
- err_create_board = "I don't know how to make the product: /%s/ - %s.",
- err_create_page = "Pages? No, we're paperless now, we don't need: /%s/ - %s.",
- err_create_post = "I didn't read your email, let's set up a meeting in a few minutes where you read it to me.",
- err_create_report = "I don't see anything wrong with an email about %s and I have no inclination to in my life.",
- err_create_thread = "The reply button is broke! This is effecting production.",
-
- err_delete_board = "I tried to delete those emails about: /%s/ - but I just couldn't %s.",
- err_delete_post = "I clicked the flag, did that delete %s? No? Well what else am I supposed to do?",
- err_create_user = "I don't know who %s is and frankly I don't give a big enough damn to learn.",
-
- err_file_exists = "Johnson! I already have that spreadsheet! I think. Probably.",
- err_file_limit = "THE CLOUD IS FULL, IF YOU SUBMIT ANYTHING ELSE TO %s THE INTERNET WILL GO DOWN.",
- err_file_post = "If you want my attention you better use something other than words because I sure as hell ain't reading anything that comes across this desk today.",
- err_file_thread = "This email chain said 'post your favorite confidential documents' but you forgot to attach any. Try that one again.",
-
- err_invalid_board = "CORRUPT EMAIL: /%s/.",
- err_invalid_ext = "Do I look like I know what a %s is?",
- err_invalid_image = "It looks like you sent a picture but what I got was a digital clown.",
- err_invalid_post = "Whatever %s was about, I've decided to politely disregard it as 'wrong opinion'.",
- err_invalid_user = "I don't know who you are or what you're doing. I'm calling the police.",
-
- --==[[ 404 ]]==--
-
- ["404"] = "404 - That's 3 better than a 401k!",
-
- --==[[ Administration ]]==--
-
- -- General
- admin_panel = "CEO DASHBOARD",
- administrator = "CEO",
- announcement = "Important things that come out of my mouth",
- archive_days = "Days to Archive Threads",
- archive_pruned = "Gas these.",
- board = "Chain",
- board_group = "Chain Group",
- board_title = "Email Subject",
- bump_limit = "Burp Excusal Tolerance",
- content_md = "Markers",
- default_name = "What is this?",
- draw_board = "MSPAINT but for the Internet",
- file = "Datum gap",
- file_limit = "Maximum Cloud Precipitation Ratio",
- global = "Everyone Has To Deal With",
- index_boards = "Product Selection",
- janitor = "Unpaid Intern",
- login = "Clock In",
- logout = "Clock Out",
- moderator = "Enforcer",
- num_pages = "Reasons this company is great",
- num_threads = "How much I can stand of this",
- password = "Digital Hash Salt",
- password_old = "That old thing.",
- password_retype = "Do it again, it'll be funny.",
- post_comment_required = "Need Context",
- post_file_required = "Insert Meme",
- regen_thumb = "Rectify Pixels",
- reply = "Interject",
- rules = "Things That Don't Apply To Me",
- name = "Bob",
- subtext = "sub-who?",
- success = "Me, The Physical Embodiment of Greatness",
- text_board = "Stuff I Won't Read",
- theme = "Birthday Party",
- thread_comment_required = "CONVERSE BEFORE CLOUD",
- thread_file_required = "INSERT FILE FOR CLOUD",
- slug = "CLOUD RESOURCE IDENTIFIER",
- username = "User Identifcation String",
- yes = "I Am Glad To Blindly Accept This",
- no = "Not Exactly",
-
- -- Announcements
- create_ann = "Open Mouth, Insert Foot",
- modify_ann = "Damage Control",
- delete_ann = "I never said that, you can't prove it.",
- created_ann = "I believe %s is the lifeblood of this company.",
- modified_ann = "What I meant to say was actually %s.",
- deleted_ann = "What announcement? %s you say? Doesn't ring a bell.",
-
- -- Boards
- create_board = "More Email!",
- modify_board = "Different Email",
- delete_board = "Less Email!",
- created_board = "How do you feel about /%s/ - %s? How about agilefall?",
- modified_board = "I hope we fixed the issue about /%s/ - %s because I was getting tired of covering for him.",
- deleted_board = "I forgot I don't even like /%s/ - %s.",
-
- -- Pages
- create_page = "Create new effigy to my greatness.",
- modify_page = "Make this less bad.",
- delete_page = "I ain't readin' this.",
- created_page = "I heard about /%s/ - %s online, let me tell you how we can pivot this.",
- modified_page = "I saw some stuff about /%s/ - %s so I fixed it.",
- deleted_page = "Good news, I got rid of those books about: /%s/ - %s.",
-
- -- Reports
- view_report = "I said *what*?",
- delete_report = "Forget about that.",
- deleted_report = "I changed my mind about %s and you can too.",
-
- -- Users
- create_user = "Hire",
- modify_user = "Rectify Person",
- delete_user = "Get Rid Of",
- created_user = "So %s is this bright young talent we've been hearing about.",
- modified_user = "Whatever was wrong with %s we squared away.",
- deleted_user = "I got rid of that %s character for you.",
-
- --==[[ Archive ]]==--
-
- arc_display = "Displaying %{n_thread} expired %{p_thread} from the past %{n_day} %{p_day}",
- arc_number = "In Britan they say 'pound'.",
- arc_name = "Who?",
- arc_excerpt = "TL;DR",
- arc_replies = "Responses",
- arc_view = "Investigate",
-
- --==[[ Ban ]]==--
-
- ban_title = "Not allowed back!",
- ban_reason = "I decided I don't like you anymore, because: ",
- ban_expire = "I might forget about this on %{expire}.",
- ban_ip = "And your stupid raffle ticket was: %{ip}.",
-
- --==[[ Catalog ]]==--
-
- cat_stats = "R: %{replies} / F: %{files}",
-
- --==[[ Copyright ]]==--
-
- copy_software = "You paid HOW MUCH for %{software}? And it's only version %{version}?! We should have used phpBB, it's way more mature.",
- copy_download = "Negotiate a license from %{github}",
-
- --==[[ Forms ]]==--
-
- form_ban = "Escort Off Property",
- form_ban_display = "Not allowed in public",
- form_ban_board = "Not allowed in my hosue",
- form_ban_reason = "Why don't I like this person",
- form_ban_time = "How long (in digital ages) to pretend I don't know this guy.",
- form_clear = "Forget About It",
- form_delete = "Hide Evidence",
- form_draw = "Sketch Out",
- form_lock = "END THIS",
- form_override = "CLOUD IS LOOSE AND HUNGRY FOR DATA",
- form_readme = "Frankly, I don't read the [%{rules}] and [%{faq}] so I won't ask you to, either. Hell, I don't even know what they say. And I wrote 'em!",
- form_remix = "CLAIM AS YOUR OWN",
- form_report = "I DON'T LIKE IT",
- form_required = "MANDATORY IF YOU WANT TO KEEP WORKING HERE",
- form_save = "Fwd to Offshore Account",
- form_sticky = "IMMORTIALIZE SHAME",
- form_submit = "HEMMORAGE BRILLIANCE",
- form_submit_name = "EMPLOYEE ID NUMBER (PRE-ACQUISITION)",
- form_submit_name_help = "Who are you again? A trip-what?",
- form_submit_subject = "What Am I On About? Oh, Right.",
- form_submit_subject_help = "What I've brought you all here today to discuss",
- form_submit_options = "Stock Options",
- form_submit_options_help = "sage: Don't let anyone know you're desperate for attention.",
- form_submit_comment = "What you have to say.",
- form_submit_comment_help = "Tell us about your great idea.",
- form_submit_file = "DATUMS",
- form_submit_file_help = "FOREFIT YOUR DATA TO THE CLOUD",
- form_submit_draw = "MSPAINT",
- form_submit_draw_help = "MSPAINT OR GIMPIFY",
- form_submit_spoiler = "Paywalled Content",
- form_submit_spoiler_help = "Increase viewer engagement 10%%",
- form_submit_mod = "Enforcer of Marketability",
- form_submit_mod_help = "Mark as landmine. Minesweeper humor. It means I think there's a bomb here.",
- form_width = "Digital Horizon",
- form_height = "Digital Embiggenment",
-
- --==[[ Posts ]]==--
-
- post_link = "FORWARD THIS EMAIL",
- post_lock = "EMAIL IS IN ARCHIVE MODE, STOP REPLYING TO THINGS I FORGOT ABOUT.",
- post_hidden = "%{n_post} %{p_post} and %{n_file} %{p_file} omitted. %{click} to view.",
- post_override = "CLOUD STORAGE ENABLED",
- post_reply = "Reply to this email",
- post_sticky = "reply-all'd, cc'd the company, 10/10",
- post_save = "I refuse to cut down on the fat, on account of all the words I said being fluff meant to distract you.",
-
- --==[[ Plurals ]]==--
-
- days = {
- one = "digital age",
- other = "digital aegis"
- },
- files = {
- one = "datum",
- other = "datums"
- },
- posts = {
- one = "email",
- other = "emailadoodles"
- },
- threads = {
- one = "email chain",
- other = "clusterfuck"
- },
-}}
diff --git a/misc/website/backend/app/src/locale/pl.lua b/misc/website/backend/app/src/locale/pl.lua
deleted file mode 100755
index bdadaf3..0000000
--- a/misc/website/backend/app/src/locale/pl.lua
+++ /dev/null
@@ -1,240 +0,0 @@
-return { pl = {
-
- --==[[ Navigation ]]==--
-
- archive = "Archiwum",
- bottom = "Przewiń na dół",
- catalog = "Katalog",
- index = "Spis treści",
- refresh = "Odśwież",
- ["return"] = "Powróć",
- return_board = "Powróć do boardu",
- return_index = "Powróć do spisu treści",
- return_thread = "Powróć do wątku",
- top = "Przewiń na górę",
-
- --==[[ Error Messages ]]==--
-
- -- Controller error messages
- err_ban_reason = "Brak powodu.",
- err_board_used = "Nazwa boardu jest już w użyciu.",
- err_not_admin = "Nie jesteś administratorem.",
- err_orphaned = "Wątek %s został osierocony.",
- err_slug_used = "Adres slug jest już w użyciu.",
- err_user_used = "Nazwa użytkownika jest już w użyciu.",
-
- -- Model error messages
- err_contribute = "Musisz załączyć treść posta lub załącznik.",
- err_locked_thread = "Wątek %s jest zablokowany.",
- err_no_files = "Dodawanie plików jest wyłączone na tym boardzie.",
-
- err_comment_post = "Na tym boardzie, by odpowiedzieć na wątek, musisz zamieścić komentarz.",
- err_comment_thread = "Na tym boardzie, by stworzyć wątek, musisz zamieścić komentarz.",
-
- err_create_ann = "Nie można utworzyć ogłoszenia: %s.",
- err_create_ban = "Nie można zbananować IP: %s.",
- err_create_board = "Nie można utworzyć boarda: /%s/ - %s.",
- err_create_page = "Nie można utworzyć strony: /%s/ - %s.",
- err_create_post = "Nie można wysłać posta.",
- err_create_report = "Nie można zgłosić posta %s.",
- err_create_thread = "Nie można utworzyć wątku.",
-
- err_delete_board = "Nie można usunąć boarda: /%s/ - %s.",
- err_delete_post = "Nie można usunąć posta %s.",
- err_create_user = "Nie można utworzyć użytkownika: %s.",
-
- err_file_exists = "Ten plik już istnieje na tym boardzie.",
- err_file_limit = "Wątek %s osiągnął już swój limit plików.",
- err_file_post = "Na tym boardzie, by odpowiedzieć na wątek, musisz załączyć plik.",
- err_file_thread = "Na tym boardzie, by stworzyć wątek, musisz załączyć plik.",
-
- err_invalid_board = "Nieznany board: /%s/.",
- err_invalid_ext = "Nieznany typ pliku: %s.",
- err_invalid_image = "Załączony plik nie jest prawidłowym obrazkiem.",
- err_invalid_post = "Post %s nie jest poprawnym postem.",
- err_invalid_user = "Niepoprawna nazwa użytkownika lub hasło.",
-
- --==[[ 404 ]]==--
-
- ["404"] = "404 - Strona nie znaleziona",
-
- --==[[ Administration ]]==--
-
- -- General
- admin_panel = "Panel cwela",
- administrator = "Cwel",
- announcement = "Ogłoszenie",
- archive_days = "Dni do archiwizowania wątków",
- archive_pruned = "Archiwizuj wątki",
- board = "Board",
- board_group = "Grupa boarda",
- board_title = "Nazwa boarda",
- bump_limit = "Limit przyjebek",
- content_md = "Opis (Markdown)",
- default_name = "Domyślna nazwa",
- draw_board = "Board do rysowania",
- file = "Plik",
- file_limit = "Limit plików w wątku",
- global = "Globalnie",
- index_boards = "Aktualne boardy",
- janitor = "Woźny",
- login = "Zaloguj",
- logout = "Wyloguj",
- moderator = "Moderator",
- num_pages = "Aktywne strony",
- num_threads = "Fredy na stronę",
- password = "Hasło",
- password_old = "Powtórz hasło",
- password_retype = "Stare hasło",
- post_comment_required = "Wymagany komentarz do posta",
- post_file_required = "Wymagany plik do posta",
- regen_thumb = "Odśwież miniaturki",
- reply = "Odpowiedz",
- rules = "Zasady",
- name = "Nazwa",
- subtext = "Podtekst",
- success = "Sukces",
- text_board = "Board tekstowy",
- theme = "Motyw",
- thread_comment_required = "Wymagany komentarz do freda",
- thread_file_required = "Wymagany plik do freda",
- slug = "Slug",
- username = "Nazwa użytkownika",
- yes = "Tak",
- no = "Nie",
-
- -- Announcements
- create_ann = "Utwórz ogłoszenie",
- modify_ann = "Zmień ogłoszenie",
- delete_ann = "Wyjeb ogłoszenie",
- created_ann = "Utworzyłeś ogłoszenie %s.",
- modified_ann = "Zmieniłeś ogłoszenie %s.",
- deleted_ann = "Wyjebałeś ogłoszenie %s.",
-
- -- Boards
- create_board = "Utwórz boarda",
- modify_board = "Zmień boarda",
- delete_board = "Wyjeb boarda",
- created_board = "Utworzyłeś boarda /%s/ - %s.",
- modified_board = "Zmieniłeś boarda /%s/ - %s.",
- deleted_board = "Wyjebałeś boarda /%s/ - %s.",
-
- -- Pages
- create_page = "Utwórz stronę",
- modify_page = "Zmień stronę",
- delete_page = "Wyjeb stronę",
- created_page = "Utworzyłeś stronę /%s/ - %s.",
- modified_page = "Zmieniłeś stronę /%s/ - %s.",
- deleted_page = "Wyjebałeś stronę /%s/ - %s.",
-
- -- Reports
- view_report = "Przejrzyj zgłoszenie",
- delete_report = "Usuń zgłoszenie",
- deleted_report = "Wyjebałeś zgłoszenie %s.",
-
- -- Users
- create_user = "Utwórz użytkownika",
- modify_user = "Zmień użytkownika",
- delete_user = "Wyjeb użytkownika",
- created_user = "Utworzyłeś użytkownika %s.",
- modified_user = "Zmieniłeś użytkownika %s.",
- deleted_user = "Wyjebałeś użytkownika %s.",
-
- --==[[ Archive ]]==--
-
- arc_display = "Wyświetlanie %{n_thread} %{p_thread} (zarchiwizowane) z %{n_day} %{p_day}",
- arc_number = "nr ",
- arc_name = "Nazwa",
- arc_excerpt = "Kawał freda",
- arc_replies = "Odpowiedzi",
- arc_view = "Wyślij",
-
- --==[[ Ban ]]==--
-
- ban_title = "Zbanowany!",
- ban_reason = "Zostałeś zbananowany z powodu o takiego:",
- ban_expire = "Twój banan usunie się %{expire}.",
- ban_ip = "Według naszych serwerów NASA, twoje IP to %{ip}.",
-
- --==[[ Catalog ]]==--
-
- cat_stats = "Odp.: %{replies} / plików: %{files}",
-
- --==[[ Copyright ]]==--
-
- copy_software = "Fredy napędzane przez %{software} %{version}",
- copy_download = "Pobierz z %{github}",
-
- --==[[ Forms ]]==--
-
- form_ban = "Zbanuj użytkownika",
- form_ban_display = "Wyświetl bana",
- form_ban_board = "Ban lokalny",
- form_ban_reason = "Powód bana",
- form_ban_time = "Długość bana (w minutach)",
- form_clear = "Wyczyść",
- form_delete = "Usuń posta",
- form_draw = "Rysuj",
- form_lock = "Zablokuj wątek",
- form_override = "Nielimitowane pliki",
- form_readme = "Przeczytaj [%{rules}] oraz [%{faq}] zanim zapostujesz.",
- form_remix = "Przerób obrazek",
- form_report = "Zgłoś posta",
- form_required = "pole wymagane",
- form_save = "Zapisz wątek",
- form_sticky = "Przyklej freda",
- form_submit = "Wyślij posta",
- form_submit_name = "Pseudonim",
- form_submit_name_help = "Nadaj sobie nazwę lub tripkod (opcjonalne)",
- form_submit_subject = "Temat",
- form_submit_subject_help = "Temat dyskusji (opcjonalne)",
- form_submit_options = "Opcje",
- form_submit_options_help = "sage: zapostuj bez podbijania freda (opcjonalne)",
- form_submit_comment = "Komentarz",
- form_submit_comment_help = "Dodaj coś do dyskusji",
- form_submit_file = "Plik",
- form_submit_file_help = "Załącz plik",
- form_submit_draw = "Rysuj",
- form_submit_draw_help = "Narysuj lub przerób obrazek",
- form_submit_spoiler = "Spoiler",
- form_submit_spoiler_help = "Podmień miniaturkę na spoiler",
- form_submit_mod = "Moderator",
- form_submit_mod_help = "Oznacz tego freda",
- form_width = "Szerokość",
- form_height = "Wysokość",
-
- --==[[ Posts ]]==--
-
- post_link = "Link do tego posta",
- post_lock = "Wątek jest zablokowany",
- post_hidden = "%{n_post} %{p_post} i %{n_file} %{p_file} zostały pominięte. %{click} aby wyświetlić.",
- post_override = "Wątek akceptuje nielimitowaną ilość plików",
- post_reply = "Odpowiedz na ten post",
- post_sticky = "Fred jest przyklejony",
- post_save = "Wątek nie będzie archiwizowany",
-
- --==[[ Plurals ]]==--
-
- days = {
- one = "dzień",
- other = "dni"
- },
- files = {
- one = "plik",
- few = "pliki",
- many = "plików",
- other = "pliki"
- },
- posts = {
- one = "post",
- few = "posty",
- many = "postów",
- other = "posty"
- },
- threads = {
- one = "wątek",
- few = "wątki",
- many = "wątków",
- other = "wątki"
- },
-}}
diff --git a/misc/website/backend/app/src/models/announcements.lua b/misc/website/backend/app/src/models/announcements.lua
deleted file mode 100755
index 35c9fbf..0000000
--- a/misc/website/backend/app/src/models/announcements.lua
+++ /dev/null
@@ -1,96 +0,0 @@
-local Model = require("lapis.db.model").Model
-local Announcements = Model:extend("announcements", {
- relations = {
- { "board", belongs_to="Boards" }
- }
-})
-
-Announcements.valid_record = {
- { "board_id", is_integer=true },
- { "text", exists=true }
-}
-
---- Create an announcement
--- @tparam table params Announcement parameters
--- @treturn boolean success
--- @treturn string error
-function Announcements:new(params)
- local announcement = self:create(params)
- return announcement and announcement or nil, { "err_create_ann", { params.text } }
-end
-
---- Modify an announcement
--- @tparam table params Announcement parameters
--- @treturn boolean success
--- @treturn string error
-function Announcements:modify(params)
- local announcement = self:get(params.id)
- if not announcement then
- return nil, { "err_create_ann", { params.text } } -- FIXME: wrong error
- end
-
- local success, err = announcement:update(params)
- return success and announcement or nil, "FIXME: " .. tostring(err)
-end
-
---- Delete an announcement
--- @tparam number id Announcement ID
--- @treturn boolean success
--- @treturn string error
-function Announcements:delete(id)
- local announcement = self:get(id)
- if not announcement then
- return nil, "FIXME"
- end
-
- local success = announcement:delete()
- return success and announcement or nil, "FIXME"
-end
-
---- Get all announcements
--- @treturn boolean success
--- @treturn string error
-function Announcements:get_all()
- local announcements = self:select("order by board_id asc")
- return announcements
-end
-
---- Get announcements
--- @treturn boolean success
--- @treturn string error
-function Announcements:get_global()
- local announcements = self:select("where board_id=0")
- return announcements
-end
-
---- Get board announcements
--- @tparam number board_id Board ID
--- @treturn boolean success
--- @treturn string error
-function Announcements:get_board(board_id)
- local announcements = self:select("where board_id=?", board_id)
- return announcements
-end
-
---- Get announcement
--- @tparam number id Announcement ID
--- @treturn boolean success
--- @treturn string error
-function Announcements:get(id)
- local announcement = self:find(id)
- return announcement and announcement or nil, "FIXME"
-end
-
-function Announcements.format_to_db(_, params)
- if not params.board_id then
- params.board_id = 0
- end
-end
-
-function Announcements.format_from_db(_, params)
- if params.board_id == 0 then
- params.board_id = nil
- end
-end
-
-return Announcements
diff --git a/misc/website/backend/app/src/models/bans.lua b/misc/website/backend/app/src/models/bans.lua
index 66e6c8f..7d6cd68 100755
--- a/misc/website/backend/app/src/models/bans.lua
+++ b/misc/website/backend/app/src/models/bans.lua
@@ -1,151 +1,23 @@
-local Model = require("lapis.db.model").Model
-local Bans = Model:extend("bans", {
- relations = {
- { "board", belongs_to="Boards" },
- }
-})
-
-Bans.valid_record = {
- { "board_id", is_integer=true },
- { "ip", max_length=255, exists=true },
- { "time", exists=true }
-}
-
---- Create a ban
--- @tparam table params Ban parameters
--- @treturn boolean success
--- @treturn string err
-function Bans:new(params)
- local ban = self:create(params)
- return ban and ban or nil, { "err_create_ban", { params.ip } }
-end
-
---- Modify a ban
--- @tparam table params Board parameters
--- @treturn boolean success
--- @treturn string error
-function Bans:modify(params)
- local ban = self:get(params.id)
- if not ban then
- return nil, { "err_create_board", { params.name, params.title } } -- FIXME: wrong error message
- end
-
- local success, err = ban:update(params)
- return success and ban or nil, "FIXME: " .. tostring(err)
-end
-
---- Delete a ban
--- @tparam number id Ban's ID
--- @treturn boolean success
--- @treturn string err
-function Bans:delete(id)
- local ban = self:get(id)
- if not ban then
- return nil, "FIXME"
- end
-
- local success = ban:delete()
- return success and ban or nil, "FIXME"
-end
-
---- Get all bans
--- @treturn table users List of bans
-function Bans:get_all()
- local bans = self:select("order by board_id asc, time + duration desc, ip asc")
-
- for i=#bans, 1, -1 do
- local ban = bans[i]
- if not self:validate(ban) then
- table.remove(bans, i)
- end
- end
-
- return bans
-end
-
---- Get board bans
--- @treturn table users List of bans
-function Bans:get_board(board_id)
- local bans = self:select("where board_id=? order by board_id asc, time + duration desc, ip asc", board_id)
-
- for i=#bans, 1, -1 do
- local ban = bans[i]
- if not self:validate(ban) then
- table.remove(bans, i)
- end
- end
-
- return bans
-end
-
---- Get ban data
--- @tparam number id Ban's ID
--- @treturn table ban
-function Bans:get(id)
- local ban = self:find(id)
- if not ban then
- return nil, "FIXME: ALART!"
- end
-
- local valid, err = self:validate(ban)
- if not valid then
- return nil, err
- end
-
- return ban
-end
-
---- Get bans for specific ip
--- @tparam string ip IP address
--- @treturn table ban
-function Bans:get_ip(ip)
- local bans = self:select("where ip=?", ip)
-
- for i=#bans, 1, -1 do
- local ban = bans[i]
- if not self:validate(ban) then
- table.remove(bans, i)
- end
- end
-
- return bans
-end
-
---- Validate ban
--- @tparam table ban Ban data
--- @treturn boolean valid
-function Bans:validate(ban)
- local time = os.time()
- local finish = ban.time + ban.duration
-
- if time >= finish then
- self:delete(ban)
- return nil, "FIXME: ban has exired"
- end
-
- return true
-end
-
---- Format ban paramaters for DB insertion
--- @tparam table params Ban parameters
-function Bans.format_to_db(_, params)
- -- Convert duration from days to seconds
- params.duration = (tonumber(params.duration) or 0) * 86400
-
- if not params.board_id then
- params.board_id = 0
- end
-end
-
---- Format ban parameters for User consumption
--- @tparam table params Ban parameters
-function Bans.format_from_db(_, params)
- -- Convert duration from seconds to days
- params.duration = tonumber(params.duration) / 86400
-
- if params.board_id == 0 then
- params.board_id = nil
- end
-end
-
-return Bans
+local lapis = require "lapis"
+local capture = require("lapis.application").capture_errors_json
+local r2 = require("lapis.application").respond_to
+local handle = require("utils.error").handle
+local app = lapis.Application()
+app.__base = app
+app.name = "api.bans."
+app.path = "/api/bans"
+
+app:match("bans", "", capture({
+ on_error = handle,
+ r2(require "apps.api.bans.bans"),
+}))
+app:match("ban", "/:uri_ban[%d]", capture({
+ on_error = handle,
+ r2(require "apps.api.bans.ban"),
+}))
+app:match("bans_ip", "/ip/:uri_ip", capture({
+ on_error = handle,
+ r2(require "apps.api.bans.bans_ip"),
+}))
+
+return app
diff --git a/misc/website/backend/app/src/models/boards.lua b/misc/website/backend/app/src/models/boards.lua
deleted file mode 100755
index 46ee3ce..0000000
--- a/misc/website/backend/app/src/models/boards.lua
+++ /dev/null
@@ -1,226 +0,0 @@
-local db = require "lapis.db"
-local giflib = require "giflib"
-local lfs = require "lfs"
-local magick = require "magick"
-local Model = require("lapis.db.model").Model
-local Boards = Model:extend("boards", {
- relations = {
- { "announcements", has_many="Announcements" },
- { "bans", has_many="Bans" },
- { "posts", has_many="Posts" },
- { "reports", has_many="Reports" },
- { "threads", has_many="Threads", where={ archive=false }, order="sticky desc, last_active desc" },
- { "archived", has_many="Threads", where={ archive=true }, order="last_active desc" },
- }
-})
-
-Boards.valid_record = {
- { "name", max_length=255, exists=true },
- { "title", max_length=255, exists=true },
- { "subtext", max_length=255 },
- { "ban_message", max_length=255 },
- { "anon_name", max_length=255 },
- { "theme", max_length=255 },
- { "pages", exists=true },
- { "threads_per_page", exists=true },
- { "thread_file_limit", exists=true },
- { "post_limit", exists=true },
- { "archive_time", exists=true },
- { "group", exists=true }
-}
-
---- Create a board
--- @tparam table params Board parameters
--- @treturn boolean success
--- @treturn string error
-function Boards:new(params)
- local board = self:create(params)
- if not board then
- return false, { "err_create_board", { params.name, params.title } }
- end
-
- lfs.mkdir(string.format("./static/%s/", board.name))
- return board
-end
-
---- Modify a board
--- @tparam table params Board parameters
--- @tparam old_name Board's current short name
--- @treturn boolean success
--- @treturn string error
-function Boards:modify(params, old_name)
- local board = self:get(old_name)
- if not board then
- return false, { "err_create_board", { params.name, params.title } } -- FIXME: wrong error message
- end
-
- local success, err = board:update(params)
- if not success then
- return false, "FIXME: " .. tostring(err)
- end
-
- if board.name ~= old_name then
- local old = string.format("./static/%s/", old_name)
- local new = string.format("./static/%s/", board.name)
- os.rename(old, new)
- end
-
- return board
-end
-
---- Delete a board
--- @tparam string name Board's short name
--- @treturn boolean success
--- @treturn string error
-function Boards:delete(name)
- local board = self:get(name)
- if not board then
- return false, { "err_create_board", { name, name } } -- FIXME: wrong error message
- end
-
- local announcements = board:get_announcements()
- local bans = board:get_bans()
- local posts = board:get_posts()
- local reports = board:get_reports()
- local threads = board:get_threads()
- local dir = string.format("./static/%s/", board.name)
-
- -- Clear data
- for _, announcement in ipairs(announcements) do announcement:delete() end
- for _, ban in ipairs(bans) do ban:delete() end
- for _, post in ipairs(posts) do post:delete() end
- for _, report in ipairs(reports) do report:delete() end
- for _, thread in ipairs(threads) do thread:delete() end
-
- -- Clear filesystem
- if lfs.attributes(dir, "mode") == "directory" then
- -- Delete files
- for file in lfs.dir(dir) do
- os.remove(dir .. file)
- end
-
- -- Delete directory
- lfs.rmdir(dir)
- end
-
- -- Clear board
- local success = board:delete()
- return success and board or false, { "err_delete_board", { board.name, board.title } }
-end
-
---- Get all boards
--- @treturn table boards
-function Boards:get_all()
- local boards = self:select("order by boards.group asc, name asc")
- return boards and boards or false, "FIXME: ALART!"
-end
-
---- Get board data
--- @tparam string name Board's short name
--- @treturn table board
-function Boards:get(name)
- local board = self:find { name=name }
- return board and board or false, "FIXME: ALART!"
-end
-
---- Format board paramaters for DB insertion
--- @tparam table params Board parameters
-function Boards.format_to_db(_, params)
- -- Convert archive_time from days to seconds
- params.archive_time = (tonumber(params.archive_time) or 0) * 86400
-end
-
---- Format board parameters for User consumption
--- @tparam table params Board parameters
-function Boards.format_from_db(_, params)
- -- Convert archive_time from seconds to days
- params.archive_time = tonumber(params.archive_time) / 86400
-end
-
---- Regenerate thumbnails for all posts
--- @treturn none
-function Boards.regen_thumbs(_)
- local sql = [[
- select
- boards.name as board,
- posts.thread_id,
- posts.file_path,
- posts.file_width,
- posts.file_height,
- posts.file_type
- from posts
- left join boards on
- board_id = boards.id
- where
- file_path is not null and
- file_width is not null and
- file_height is not null and
- file_type = 'image' and
- file_spoiler = false
- order by
- board_id asc,
- thread_id asc,
- post_id asc
- ]]
- local dir
- local board
- local thread = 0
- local results = db.query(sql)
-
- for _, result in ipairs(results) do
- -- Change board, reset thread counter and image directory
- if result.board ~= board then
- board = result.board
- thread = 0
- dir = string.format("./static/%s/", board)
- end
-
- -- Filesystem paths
- local name, ext = result.file_path:match("^(.+)(%..+)$")
- ext = string.lower(ext)
-
- local full_path = dir .. result.file_path
- local thumb_path = dir .. "s" .. result.file_path
-
- -- Generate a thumbnail
- if ext == ".webm" then
- thumb_path = dir .. "s" .. name .. ".png"
-
- -- Create screenshot of first frame
- os.execute(string.format("ffmpeg -i %s -ss 00:00:01 -vframes 1 %s -y", full_path, thumb_path))
- end
-
- -- Save thumbnail
- local w, h
- if result.thread_id > thread then
- thread = result.thread_id
- w = result.file_width < 250 and result.file_width or 250
- h = result.file_height < 250 and result.file_height or 250
- else
- w = result.file_width < 125 and result.file_width or 125
- h = result.file_height < 125 and result.file_height or 125
- end
-
- -- Grab first frame from video
- if ext == ".webm" then
- magick.thumb(thumb_path, string.format("%sx%s", w, h), thumb_path)
- elseif ext == ".svg" then
- thumb_path = dir .. "s" .. name .. ".png"
- os.execute(string.format("convert -background none -resize %dx%d %s %s", w, h, full_path, thumb_path))
- elseif ext == ".gif" then
- -- Grab first frame of a gif instead of the last
- local gif, err = giflib.load_gif(full_path)
-
- if err then
- magick.thumb(full_path, string.format("%sx%s", w, h), thumb_path)
- else
- gif:write_first_frame(thumb_path)
- magick.thumb(thumb_path, string.format("%sx%s", w, h), thumb_path)
- end
- else
- magick.thumb(full_path, string.format("%sx%s", w, h), thumb_path)
- end
- end
-end
-
-return Boards
diff --git a/misc/website/backend/app/src/models/pages.lua b/misc/website/backend/app/src/models/pages.lua
deleted file mode 100755
index 82ce112..0000000
--- a/misc/website/backend/app/src/models/pages.lua
+++ /dev/null
@@ -1,90 +0,0 @@
-local Model = require("lapis.db.model").Model
-local Pages = Model:extend("pages")
-
-Pages.valid_record = {
- { "slug", exists=true, type="String" },
- { "title", exists=true, type="String" },
- { "content", exists=true, type="String" }
-}
-
---- Create a new page
--- @tparam table page Page data
--- @treturn boolean success
--- @treturn string error
-function Pages:new(params)
- local unique, err = self:is_unique(params.slug, params.title)
- if not unique then
- return nil, err
- end
-
- local page = self:create(params)
- return page and page or nil, { "err_create_page", { page.slug, page.title } }
-end
-
---- Modify a page
--- @tparam table page Page data
--- @treturn boolean success
--- @treturn string error
-function Pages:modify(params, slug)
- local page = self:get(slug)
- if not page then
- return nil, "FIXME"
- end
-
- -- Check to see if the page we are modifying is the one that fails validation.
- -- If it is, that's fine since we're updating the unique values with themselves.
- -- If #pages > 1 then this will always fail since either the new slug or new
- -- title is going to belong to some other page.
- do
- local unique, err, pages = self:is_unique(params.slug, params.title)
- if not unique then
- for _, p in ipairs(pages) do
- if page.id ~= p.id then
- return nil, err
- end
- end
- end
- end
-
- local success, err = page:update(params)
- return success and page or nil, "FIXME: " .. tostring(err)
-end
-
---- Delete page
--- @tparam table page Page data
--- @treturn boolean success
--- @treturn string error
-function Pages:delete(slug)
- local page = self:get(slug)
- if not page then
- return nil, "FIXME"
- end
-
- local success = page:delete()
- return success and page or nil, "FIXME"
-end
-
---- Get all pages
--- @treturn table pages List of pages
-function Pages:get_all()
- return self:select("order by slug asc")
-end
-
---- Get page
--- @tparam string slug Page slug
--- @treturn table page
-function Pages:get(slug)
- local page = self:find { slug=slug:lower() }
- return page and page or nil, "FIXME"
-end
-
-function Pages:is_unique(slug, title)
- local pages = self:select("where slug=? or lower(title)=?", slug, title:lower())
- return #pages == 0 and true or nil, "FIXME", pages
-end
-
-function Pages.format_to_db(_, params)
- params.slug = params.slug:lower()
-end
-
-return Pages
diff --git a/misc/website/backend/app/src/models/posts.lua b/misc/website/backend/app/src/models/posts.lua
deleted file mode 100755
index e53d792..0000000
--- a/misc/website/backend/app/src/models/posts.lua
+++ /dev/null
@@ -1,387 +0,0 @@
-local encoding = require "lapis.util.encoding"
-local Model = require("lapis.db.model").Model
-local giflib = require "giflib"
-local magick = require "magick"
-local md5 = require "md5"
-local filetypes = require "utils.file_whitelist"
-local generate = require "utils.generate"
-local Posts = Model:extend("posts", {
- relations = {
- { "board", belongs_to="Boards" },
- { "thread", belongs_to="Threads" },
- }
-})
-local sf = string.format
-
-local function get_duration(path)
- local cmd = sf("ffprobe -i %s -show_entries format=duration -v quiet -of csv=\"p=0\" -sexagesimal", path)
- local f = io.popen(cmd, "r")
- local s = f:read("*a")
- f:close()
-
- local hr, mn, sc = string.match(s, "(%d+):(%d+):(%d+).%d+")
- local d = mn..":"..sc
-
- if hr ~= "0" then
- d = hr..":"..d
- end
-
- return d
-end
-
---- Prepare post for insertion
--- @tparam table params Input from the user
--- @tparam table session User session
--- @tparam table board Board data
--- @tparam table thread Thread data
--- @tparam number files Number of files in thread
--- @treturn boolean success
--- @treturn string error
-function Posts:prepare_post(params, session, board, thread, files)
- -- FIXME: this whole function should be web-side, not api-side
- local time = os.time()
-
- -- Prepare session stuff
- session.password = session.password or generate.password(time)
-
- -- Save names on individual boards
- -- TODO: Put this code elsewhere
- if params.name then
- session.names[board.name] = params.name
- end
-
- -- Files take presidence over drawings, but if there is no file, fill in
- -- the file fields with draw data. This should avoid ugly code branching
- -- later on in the file.
- -- TODO: send file data through api!
- params.file = params.file or {
- filename = "",
- content = ""
- }
-
- if #params.file.content == 0 and params.draw then
- local pattern = ".-,(.+)"
- params.draw = params.draw:match(pattern)
- params.file.filename = "tegaki.png"
- params.file.content = encoding.decode_base64(params.draw)
- end
-
- -- Check board flags
- if thread then
- if thread.lock and not session.admin and not session.mod then
- return false, { "err_locked_thread", { thread.post_id } }
- end
-
- if board.post_comment and not params.comment then
- return false, { "err_comment_post" }
- end
-
- if board.post_file and #params.file.content == 0 then
- return false, { "err_file_post" }
- end
- else
- if board.thread_comment and not params.comment then
- return false, { "err_comment_thread" }
- end
-
- if board.thread_file and #params.file.content == 0 then
- return false, { "err_file_thread" }
- end
- end
-
- -- Parse name
- if params.name then
- params.name, params.trip = generate.tripcode(params.name)
- end
-
- -- Set file
- if #params.file.content > 0 then
-
- -- Reject files in text-only boards
- if board.text_only then
- return false, { "err_no_files" }
- end
-
- -- Thread limit is already met.
- if thread then
- if files >= board.thread_file_limit and not thread.size_override then
- return false, { "err_file_limit", { thread.post_id } }
- end
- end
-
- local name = sf("%s%s", time, generate.random())
- local ext = params.file.filename:match("^.+(%..+)$")
- ext = string.lower(ext)
-
- -- Figure out how to deal with the file
- if filetypes.image[ext] and board.filetype_image then
- params.file_type = "image"
-
- if ext ~= ".webm" then
- -- Check if valid image
- local image = magick.load_image_from_blob(params.file.content)
-
- if not image then
- return false, { "err_invalid_image" }
- end
-
- params.file_width = image:get_width()
- params.file_height = image:get_height()
- end
- elseif filetypes.audio[ext] and board.filetype_audio then
- params.file_type = "audio"
- else
- return false, { "err_invalid_ext", { ext } }
- end
-
- params.file_name = params.file.filename
- params.file_path = name .. ext
- params.file_md5 = md5.sumhexa(params.file.content)
- params.file_size = #params.file.content
-
- if params.file_spoiler then
- params.file_spoiler = true
- else
- params.file_spoiler = false
- end
-
- -- Check if file already exists
- local file = self:find_file(board.id, params.file_md5)
- if file then
- return false, { "err_file_exists" }
- end
- else
- params.file_spoiler = false
- end
-
- -- Check contributions
- if not params.comment and not params.file_name then
- return false, { "err_contribute" }
- end
-
- return true
-end
-
---- Create a new post
--- @tparam table params Post parameters
--- @tparam table board Board data
--- @tparam boolean op OP flag
--- @treturn boolean success
--- @treturn string error
-function Posts:new(params, board, op)
-
- -- Create post
- local post = self:create(params)
- if not post then
- return false, { "err_create_post" }
- end
-
- -- Save file
- if post.file_path then
- local dir = sf("./static/%s/", board.name)
- local name, ext = post.file_path:match("^(.+)%.(.+)$")
- ext = string.lower(ext)
-
- -- Filesystem paths
- local full_path = dir .. post.file_path
- local thumb_path = dir .. "s" .. post.file_path
-
- -- Save file
- local file = io.open(full_path, "w")
- file:write(params.file_content)
- file:close()
-
- -- Audio file
- if post.file_type == "audio" then
- post.file_duration = get_duration(full_path)
- post:update("file_duration")
- return post
- end
-
- -- Image file
- if post.file_type == "image" and not post.file_spoiler then
-
- -- Save thumbnail
- local w, h
- if op then
- w = post.file_width < 250 and post.file_width or 250
- h = post.file_height < 250 and post.file_height or 250
- else
- w = post.file_width < 125 and post.file_width or 125
- h = post.file_height < 125 and post.file_height or 125
- end
-
- -- Generate a thumbnail
- if ext == "webm" then
- thumb_path = dir .. "s" .. name .. ".png"
-
- -- Create screenshot of first frame
- os.execute(sf("ffmpeg -i %s -ss 00:00:01 -vframes 1 %s -y", full_path, thumb_path))
-
- -- Update post info
- local image = magick.load_image(thumb_path)
- post.file_width = image:get_width()
- post.file_height = image:get_height()
- post.file_duration = get_duration(full_path)
- post:update("file_width", "file_height", "file_duration")
-
- -- Resize thumbnail
- magick.thumb(thumb_path, sf("%sx%s", w, h), thumb_path)
-
- elseif ext == "svg" then
- thumb_path = dir .. "s" .. name .. ".png"
- os.execute(sf("convert -background none -resize %dx%d %s %s", w, h, full_path, thumb_path))
-
- elseif ext == "gif" then
- local gif, err = giflib.load_gif(full_path)
-
- if err then
- -- Not animated I presume? TODO: check what err represents
- magick.thumb(full_path, sf("%sx%s", w, h), thumb_path)
- else
- -- Grab first frame of a gif instead of the last
- gif:write_first_frame(thumb_path)
-
- -- Update post info
- local width, height = gif:dimensions()
- post.file_width = width
- post.file_height = height
- post:update("file_width", "file_height")
-
- -- Resize thumbnail
- magick.thumb(thumb_path, sf("%sx%s", w, h), thumb_path)
- end
- else
- magick.thumb(full_path, sf("%sx%s", w, h), thumb_path)
- end
- end
- end
-
- -- Update board
- board:update("total_posts")
-
- return post
-end
-
---- Delete post data
--- @tparam number id Post ID
--- @treturn boolean success
--- @treturn string error
-function Posts:delete(id)
-
- -- Get post
- local post, err = self:get_post_by_id(id)
- if not post then
- return false, err
- end
-
- -- Delete post
- local success, err = post:delete()
- if not success then
- return false, err--{ "err_delete_post", { post.post_id } }
- end
-
- -- Delete files
- if post.file_path then
- local board = post:get_board()
- local dir = sf("./static/%s/", board.name)
- local name, ext = post.file_path:match("^(.+)%.(.+)$")
- ext = string.lower(ext)
- os.remove(dir .. post.file_path)
-
- -- Change thumbnail path to png
- if ext == "webm" or ext == "svg" then
- post.file_path = name .. ".png"
- end
-
- os.remove(dir .. "s" .. post.file_path)
- end
-
- return post
-end
-
---- Get op and last 5 posts of a thread to display on board index
--- @tparam number thread_id Thread ID
--- @treturn table posts
-function Posts:get_index_posts(thread_id)
- local sql = "where thread_id=? order by post_id desc limit 5"
- local posts = self:select(sql, thread_id)
-
- if self:count_posts(thread_id) > 5 then
- local thread = posts[1]:get_thread()
- local op = thread:get_op()
- table.insert(posts, op)
- end
-
- return posts
-end
-
---- Get post data
--- @tparam number board_id Board ID
--- @tparam number post_id Local Post ID
--- @treturn table post
-function Posts:get(board_id, post_id)
- local post = self:find {
- board_id = board_id,
- post_id = post_id
- }
- return post and post or false, "FIXME"
-end
-
---- Get post data
--- @tparam number id Post ID
--- @treturn table post
-function Posts:get_post_by_id(id)
- local post = self:find(id)
- return post and post or false, "FIXME"
-end
-
---- Find file in active posts
--- @tparam number board Board ID
--- @tparam string file_md5 Unique hash of file
--- @treturn boolean success
--- @treturn string error
-function Posts:find_file(board_id, file_md5)
- local sql = "where board_id=? and file_md5=? limit 1"
- return unpack(self:select(sql, board_id, file_md5))
-end
-
-
---- Count hidden posts in a thread
--- @tparam number thread_id Thread ID
--- @treturn table hidden
-function Posts:count_hidden_posts(thread_id)
- local posts = self:get_index_posts(thread_id)
- local num_posts = self:count_posts(thread_id)
- local num_files = self:count_files(thread_id)
-
- for _, post in ipairs(posts) do
- -- Reduce number of posts hidden
- num_posts = num_posts - 1
-
- -- Reduce number of files hidden
- if post.file_name then
- num_files = num_files - 1
- end
- end
-
- return { posts=num_posts, files=num_files }
-end
-
---- Count posts in a thread
--- @tparam number thread_id Thread ID
--- @treturn number posts
-function Posts:count_posts(thread_id)
- local sql = "thread_id=?"
- return self:count(sql, thread_id)
-end
-
---- Count posts with images in a thread
--- @tparam number thread_id Thread ID
--- @treturn number files
-function Posts:count_files(thread_id)
- local sql = "thread_id=? and file_name is not null"
- return self:count(sql, thread_id)
-end
-
-return Posts
diff --git a/misc/website/backend/app/src/models/reports.lua b/misc/website/backend/app/src/models/reports.lua
deleted file mode 100755
index bbe1f9c..0000000
--- a/misc/website/backend/app/src/models/reports.lua
+++ /dev/null
@@ -1,76 +0,0 @@
-local trim = require("lapis.util").trim_filter
-local Model = require("lapis.db.model").Model
-local Reports = Model:extend("reports")
-
---- Create a new report
--- @tparam table report Report data
--- @treturn boolean success
--- @treturn string error
-function Reports:create_report(report)
- -- Trim white space
- trim(report, {
- "board_id", "thread_id", "post_id",
- "timestamp", "num_reports"
- }, nil)
-
- local r = self:create {
- board_id = report.board_id,
- thread_id = report.thread_id,
- post_id = report.post_id,
- timestamp = report.timestamp,
- num_reports = report.num_reports
- }
-
- if r then
- return r
- end
-
- return false, { "err_create_report" }
-end
-
---- Modify a report
--- @tparam table report Report data
--- @treturn boolean success
--- @treturn string error
-function Reports:modify_report(report)
- local columns = {}
- for col in pairs(report) do
- table.insert(columns, col)
- end
-
- return report:update(unpack(columns))
-end
-
---- Delete report
--- @tparam table report Report data
--- @treturn boolean success
--- @treturn string error
-function Reports:delete_report(report)
- return report:delete()
-end
-
---- Get all reports
--- @treturn table reports List of reports
-function Reports:get_reports()
- return self:select("order by timestamp asc")
-end
-
---- Get report
--- @tparam string board_id Board ID
--- @tparam string post_id Post ID
--- @treturn table report
-function Reports:get_report(board_id, post_id)
- return unpack(self:select(
- "where board_id=? and post_id=? limit 1",
- board_id, post_id
- ))
-end
-
---- Get report
--- @tparam string id Report ID
--- @treturn table report
-function Reports:get_report_by_id(id)
- return unpack(self:select("where id=? limit 1", id))
-end
-
-return Reports
diff --git a/misc/website/backend/app/src/models/threads.lua b/misc/website/backend/app/src/models/threads.lua
deleted file mode 100755
index d33d00a..0000000
--- a/misc/website/backend/app/src/models/threads.lua
+++ /dev/null
@@ -1,106 +0,0 @@
-local Model = require("lapis.db.model").Model
-local Threads = Model:extend("threads", {
- relations = {
- { "board", belongs_to="Boards" },
- { "posts", has_many="Posts" },
- { "op", has_one="Posts", order="post_id asc" },
- }
-})
-
-Threads.valid_record = {
- { "board_id", exists=true }
-}
-
---- Create thread
--- @tparam table params Thread parameters
--- @treturn boolean success
--- @treturn string error
-function Threads:new(params)
- local thread = self:create(params)
- return thread and thread or false, { "err_create_thread" }
-end
-
---- Modify a thread
--- @tparam table params Thread parameters
--- @treturn boolean success
--- @treturn string error
-function Threads:modify(params)
- local thread = self:get(params.id)
- if not thread then
- return false, { "err_create_board" } -- FIXME: wrong error message
- end
-
- local success, err = thread:update(params)
- return success and thread or false, "FIXME: " .. tostring(err)
-end
-
---- Delete entire thread
--- @tparam number id Thread ID
--- @treturn boolean success
--- @treturn string error
-function Threads:delete(id)
- -- FIXME: API needs to create a user object for better auth checking
- local thread, err = self:get(id)
- if not thread then
- return false, err
- end
-
- local op = thread:get_op()
- local success = thread:delete()
- return success and thread or false, { "err_delete_thread", { op.post_id } }
-end
-
---- Get thread data
--- @tparam number id Thread ID
--- @treturn table thread
-function Threads:get(id)
- local thread = self:find(id)
- return thread and thread or false, "FIXME"
-end
-
---- Get archived threads
--- @tparam number board_id Board ID
--- @treturn table threads
-function Threads:get_archived(board_id)
- local sql = "where board_id=? and archive=true order by last_active desc"
- return self:select(sql, board_id)
-end
-
---- Bump threads to archive
--- @tparam number board_id Board ID
--- @tparam number max_threads Maximum number of threads on this board
--- @treturn boolean success
--- @treturn string error
-function Threads:archive_threads(board_id, max_threads)
- local threads = self:get_threads(board_id)
-
- if #threads > max_threads then
- for i=max_threads+1, #threads do
- local _, err = self:archive_thread(threads[i])
-
- if err then
- return false, err
- end
- end
- end
-end
-
---- Archive a thread
--- @tparam table thread Thread data
--- @treturn boolean success
--- @treturn string error
-function Threads:archive_thread(thread)
- thread.sticky = false
- thread.lock = true
- thread.archive = true
- thread.last_active = os.time()
- return thread:update("sticky", "lock", "archive", "last_active")
-end
-
---- Find threads with no posts
--- @treturn table threads
-function Threads:find_orphans()
- return self:select("where id not in (select distinct thread_id from posts)")
-end
-
-return Threads
diff --git a/misc/website/backend/app/src/models/users.lua b/misc/website/backend/app/src/models/users.lua
index 469d6f2..11d5168 100755
--- a/misc/website/backend/app/src/models/users.lua
+++ b/misc/website/backend/app/src/models/users.lua
@@ -1,30 +1,34 @@
local bcrypt = require "bcrypt"
-local uuid = require "resty.jit-uuid"
+local uuid = require "resty.jit-uuid"
local config = require("lapis.config").get()
-local Model = require("lapis.db.model").Model
-local Users = Model:extend("users")
-local token = config.secret
+local Model = require("lapis.db.model").Model
+local Users = Model:extend("users")
+local token = config.secret
Users.role = {
- [-1] = "INVALID",
- [1] = "USER",
- [6] = "JANITOR",
- [7] = "MOD",
- [8] = "ADMIN",
- [9] = "OWNER",
-
- INVALID = -1,
- USER = 1,
- JANITOR = 6,
- MOD = 7,
- ADMIN = 8,
- OWNER = 9
+ [-1] = "INVALID",
+ [1] = "USER",
+ [6] = "JANITOR",
+ [7] = "MOD",
+ [8] = "ADMIN",
+ [9] = "OWNER",
+
+ INVALID = -1,
+ USER = 1,
+ JANITOR = 6,
+ MOD = 7,
+ ADMIN = 8,
+ OWNER = 9,
}
-Users.valid_record = {
- { "username", exists=true },
- { "role", exists=true, is_integer=true }
-}
+Users.valid_record = {{
+ "username",
+ exists = true,
+}, {
+ "role",
+ exists = true,
+ is_integer = true,
+}}
Users.default_key = "00000000-0000-0000-0000-000000000000"
@@ -34,31 +38,37 @@ Users.default_key = "00000000-0000-0000-0000-000000000000"
-- @treturn string error
function Users:new(params, raw_password)
- -- Check if username is unique
- do
- local unique, err = self:is_unique(params.username)
- if not unique then return nil, err end
- end
-
- -- Verify password
- do
- local valid, err = self:validate_password(params.password, params.confirm, raw_password)
- if not valid then return nil, err end
-
- params.confirm = nil
- params.password = bcrypt.digest(params.username:lower() .. params.password .. token, 12)
- end
-
- -- Generate unique API key
- do
- local api_key, err = self:generate_api_key()
- if not api_key then return nil, err end
-
- params.api_key = api_key
- end
-
- local user = self:create(params)
- return user and user or nil, { "err_create_user", { params.username } }
+ -- Check if username is unique
+ do
+ local unique, err = self:is_unique(params.username)
+ if not unique then
+ return nil, err
+ end
+ end
+
+ -- Verify password
+ do
+ local valid, err = self:validate_password(params.password, params.confirm, raw_password)
+ if not valid then
+ return nil, err
+ end
+
+ params.confirm = nil
+ params.password = bcrypt.digest(params.username:lower() .. params.password .. token, 12)
+ end
+
+ -- Generate unique API key
+ do
+ local api_key, err = self:generate_api_key()
+ if not api_key then
+ return nil, err
+ end
+
+ params.api_key = api_key
+ end
+
+ local user = self:create(params)
+ return user and user or nil, {"err_create_user", {params.username}}
end
--- Modify a user
@@ -66,34 +76,42 @@ end
-- @treturn boolean success
-- @treturn string error
function Users:modify(params, raw_username, raw_password)
- local user = self:get(raw_username)
- if not user then return nil, "FIXME" end
-
- -- Check if username is unique
- do
- local unique, err, u = self:is_unique(params.username)
- if not unique and user.id ~= u.id then return nil, err end
- end
-
- -- Verify password
- if params.password then
- local valid, err = self:validate_password(params.password, params.confirm, raw_password)
- if not valid then return nil, err end
-
- params.confirm = nil
- params.password = bcrypt.digest(params.username:lower() .. params.password .. token, 12)
- end
-
- -- Generate unique API key
- if params.api_key then
- local api_key, err = self:generate_api_key()
- if not api_key then return nil, err end
-
- params.api_key = api_key
- end
-
- local success, err = user:update(params)
- return success and user or nil, "FIXME: " .. tostring(err)
+ local user = self:get(raw_username)
+ if not user then
+ return nil, "FIXME"
+ end
+
+ -- Check if username is unique
+ do
+ local unique, err, u = self:is_unique(params.username)
+ if not unique and user.id ~= u.id then
+ return nil, err
+ end
+ end
+
+ -- Verify password
+ if params.password then
+ local valid, err = self:validate_password(params.password, params.confirm, raw_password)
+ if not valid then
+ return nil, err
+ end
+
+ params.confirm = nil
+ params.password = bcrypt.digest(params.username:lower() .. params.password .. token, 12)
+ end
+
+ -- Generate unique API key
+ if params.api_key then
+ local api_key, err = self:generate_api_key()
+ if not api_key then
+ return nil, err
+ end
+
+ params.api_key = api_key
+ end
+
+ local success, err = user:update(params)
+ return success and user or nil, "FIXME: " .. tostring(err)
end
--- Delete user
@@ -101,13 +119,13 @@ end
-- @treturn boolean success
-- @treturn string error
function Users:delete(username)
- local user = self:get(username)
- if not user then
- return nil, "FIXME"
- end
+ local user = self:get(username)
+ if not user then
+ return nil, "FIXME"
+ end
- local success = user:delete()
- return success and user or nil, "FIXME"
+ local success = user:delete()
+ return success and user or nil, "FIXME"
end
--- Verify user
@@ -115,67 +133,73 @@ end
-- @treturn boolean success
-- @treturn string error
function Users:login(params)
- local user = self:get(params.username)
- if not user then return nil, { "err_invalid_user" } end
+ local user = self:get(params.username)
+ if not user then
+ return nil, {"err_invalid_user"}
+ end
- local password = user.username .. params.password .. token
- local verified = bcrypt.verify(password, user.password)
+ local password = user.username .. params.password .. token
+ local verified = bcrypt.verify(password, user.password)
- return verified and user or nil, { "err_invalid_user" }
+ return verified and user or nil, {"err_invalid_user"}
end
--- Get all users
-- @treturn table users List of users
function Users:get_all()
- local users = self:select("order by username asc")
- return users
+ local users = self:select("order by username asc")
+ return users
end
--- Get user
-- @tparam string username Username
-- @treturn table user
function Users:get(username)
- local users = self:select("where lower(username)=? limit 1", username:lower())
- return #users == 1 and users[1] or nil, "FIXME"
+ local users = self:select("where lower(username)=? limit 1", username:lower())
+ return #users == 1 and users[1] or nil, "FIXME"
end
function Users:get_api(params)
- local user = self:find(params)
- return user and user or nil, "FIXME"
+ local user = self:find(params)
+ return user and user or nil, "FIXME"
end
function Users:format_to_db(params)
- if not params.role then
- params.role = self.role.INVALID
- end
+ if not params.role then
+ params.role = self.role.INVALID
+ end
end
function Users.format_from_db(_, params)
- params.password = nil
- params.api_key = nil
+ params.password = nil
+ params.api_key = nil
end
function Users:is_unique(username)
- local user = self:get(username)
- return not user and true or nil, "FIXME", user
+ local user = self:get(username)
+ return not user and true or nil, "FIXME", user
end
function Users.validate_password(_, password, confirm, old_password)
- if password ~= confirm or password ~= old_password then
- return nil, "FIXME"
- end
+ if password ~= confirm or password ~= old_password then
+ return nil, "FIXME"
+ end
- return true
+ return true
end
function Users:generate_api_key()
- for _ = 1, 10 do
- local api_key = uuid()
- local user = self:find { api_key=api_key }
- if not user then return api_key end
- end
-
- return nil, "FIXME"
+ for _ = 1, 10 do
+ local api_key = uuid()
+ local user = self:find{
+ api_key = api_key,
+ }
+ if not user then
+ return api_key
+ end
+ end
+
+ return nil, "FIXME"
end
return Users
diff --git a/misc/website/backend/app/src/sass/posts.scss b/misc/website/backend/app/src/sass/posts.scss
deleted file mode 100755
index a42c6d3..0000000
--- a/misc/website/backend/app/src/sass/posts.scss
+++ /dev/null
@@ -1,109 +0,0 @@
-$spacing: 10px;
-
-.thread_container, .post_container {
- margin: $spacing 0 0 0;
- padding: $spacing;
-}
-
-.thread_container {
- overflow: visible;
-
- .op, .post_container {
- position: relative;
- }
-
- .op {
- .post_admin {
- margin-left: $spacing * 35;
- }
- }
-
- .post_container {
- display: table;
- min-width: 300px;
- max-width: 100%;
-
- .post_admin {
- margin-left: $spacing * 2;
- }
- }
-
- .post_file {
- margin: 0 0 3px 0;
-
- a {
- text-decoration: underline;
- }
- }
-
- .post_image {
- float: left;
- margin: 0 $spacing * 2 0 $spacing;
-
- img {
- max-width: 1000px;
- max-height: 1000px;
- }
- }
-
- .post_subject, .post_name {
- font-weight: bold;
- }
-
- .post_comment {
- padding: $spacing $spacing * 1.5;
-
- a {
- text-decoration: underline;
- }
-
- .broken_link {
- text-decoration: line-through;
- }
-
- .spoiler {
- background-color: #000000;
- color: #000000;
-
- &:hover {
- color: #ffffff;
- }
- }
-
- .post_banned {
- color: #ff0000;
- font-weight: bold;
- margin-top: $spacing * 2;
- }
- }
-
- .post_flag, .post_menu {
- cursor: pointer;
- }
-
- .post_menu {
- position: relative;
- display: inline-block;
-
- .menu {
- display: none;
- position: absolute;
- z-index: 9;
-
- div {
- padding: 3px;
-
- form {
- margin: 0;
- padding: 0;
-
- input[type="submit"], button {
- background: none;
- border: none;
- padding: 0;
- }
- }
- }
- }
- }
-}
diff --git a/misc/website/backend/app/src/sass/reset.scss b/misc/website/backend/app/src/sass/reset.scss
deleted file mode 100755
index 887bf88..0000000
--- a/misc/website/backend/app/src/sass/reset.scss
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- HTML5 Reset :: style.css
- ----------------------------------------------------------
- We have learned much from/been inspired by/taken code where offered from:
-
- Eric Meyer :: http://meyerweb.com
- HTML5 Doctor :: http://html5doctor.com
- and the HTML5 Boilerplate :: http://html5boilerplate.com
-
--------------------------------------------------------------------------------*/
-
-/* Let's default this puppy out
--------------------------------------------------------------------------------*/
-
-html, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary {
- margin: 0;
- padding: 0;
- border: 0;
- font-size: 100%;
- font-weight: normal;
- vertical-align: baseline;
- background: transparent;
-}
-
-article, aside, figure, footer, header, nav, section, details, summary {display: block;}
-
-/* Handle box-sizing while better addressing child elements:
- http://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
-html {
- box-sizing: border-box;
-}
-
-*,
-*:before,
-*:after {
- box-sizing: inherit;
-}
-
-/* consider resetting the default cursor: https://gist.github.com/murtaugh/5247154 */
-
-/* Responsive images and other embedded objects */
-/* if you don't have full control over `img` tags (if you have to overcome attributes), consider adding height: auto */
-img,
-object,
-embed {max-width: 100%;}
-
-/*
- Note: keeping IMG here will cause problems if you're using foreground images as sprites.
- In fact, it *will* cause problems with Google Maps' controls at small size.
- If this is the case for you, try uncommenting the following:
-
-#map img {
- max-width: none;
-}
-*/
-
-/* force a vertical scrollbar to prevent a jumpy page */
-html {overflow-y: scroll;}
-
-/* we use a lot of ULs that aren't bulleted.
- you'll have to restore the bullets within content,
- which is fine because they're probably customized anyway */
-ul {list-style: none;}
-
-blockquote, q {quotes: none;}
-
-blockquote:before,
-blockquote:after,
-q:before,
-q:after {content: ''; content: none;}
-
-a {margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent;}
-
-del {text-decoration: line-through;}
-
-abbr[title], dfn[title] {border-bottom: 1px dotted #000; cursor: help;}
-
-/* tables still need cellspacing="0" in the markup */
-table {border-collapse: collapse; border-spacing: 0;}
-th {font-weight: bold; vertical-align: bottom;}
-td {font-weight: normal; vertical-align: top;}
-
-hr {display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0;}
-
-input, select {vertical-align: middle;}
-
-pre {
- white-space: pre; /* CSS2 */
- white-space: pre-wrap; /* CSS 2.1 */
- white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
- word-wrap: break-word; /* IE */
-}
-
-input[type="radio"] {vertical-align: text-bottom;}
-.ie7 input[type="checkbox"] {vertical-align: baseline;}
-.ie6 input {vertical-align: text-bottom;}
-
-select, input, textarea {font: 99% sans-serif;}
-
-table {font-size: inherit; font: 100%;}
-
-small {font-size: 85%;}
-
-strong {font-weight: bold;}
-
-td, td img {vertical-align: top;}
-
-/* Make sure sup and sub don't mess with your line-heights http://gist.github.com/413930 */
-sub, sup {font-size: 75%; line-height: 0; position: relative;}
-sup {top: -0.5em;}
-sub {bottom: -0.25em;}
-
-/* standardize any monospaced elements */
-pre, code, kbd, samp {font-family: monospace, sans-serif;}
-
-/* hand cursor on clickable elements */
-.clickable,
-input[type=button],
-input[type=submit],
-input[type=file],
-button {cursor: pointer;}
-
-/* Webkit browsers add a 2px margin outside the chrome of form elements */
-button, input, select, textarea {margin: 0;}
-
-/* make buttons play nice in IE */
-button,
-input[type=button] {width: auto; overflow: visible;}
-
-/* scale images in IE7 more attractively */
-.ie7 img {-ms-interpolation-mode: bicubic;}
-
-/* prevent BG image flicker upon hover
- (commented out as usage is rare, and the filter syntax messes with some pre-processors)
-.ie6 html {filter: expression(document.execCommand("BackgroundImageCache", false, true));}
-*/
-
-/* let's clear some floats */
-.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
-.clearfix:after { clear: both; }
-.clearfix { zoom: 1; }
diff --git a/misc/website/backend/app/src/sass/style.scss b/misc/website/backend/app/src/sass/style.scss
deleted file mode 100755
index 83fae1f..0000000
--- a/misc/website/backend/app/src/sass/style.scss
+++ /dev/null
@@ -1,322 +0,0 @@
-@import "reset";
-@import "posts";
-
-@mixin display-flex {
- display: -webkit-box-flex;
- display: -webkit-flexbox;
- display: -ms-flexbox;
- display: -webkit-box;
- display: flex;
-}
-
-@mixin flex($args) {
- -webkit-flex: $args;
- -ms-flex: $args;
- flex: $args;
-}
-
-@mixin flex-direction($dir) {
- $alt: vertical !default;
- @if $dir == column {
- $alt: vertical;
- } @else {
- $alt: horizontal;
- }
- -webkit-flex-direction: column;
- -webkit-box-orient: $alt;
- -ms-flex-direction: column;
- flex-direction: column;
-}
-
-$spacing: 10px;
-
-body {
- margin: $spacing;
- font-size: 10pt;
- font-family: Helvetica, "Noto Sans", "Roboto Sans", Arial, sans-serif;
-}
-
-a {
- text-decoration: none;
-}
-
-input[type="text"],
-input[type="password"],
-textarea,
-select,
-.admin_main {
- padding: 4px;
- width: 280px;
-}
-
-input[type="checkbox"] {
- margin: 4px 0;
-}
-
-input[type="file"] {
- padding: 4px 0;
-}
-
-h1 {
- font-size: 240%;
- font-weight: bolder;
- margin-top: 10px;
- text-align: center;
-}
-
-h2 {
- margin: $spacing 0;
- text-align: center;
-}
-
-hr {
- clear: left;
-}
-
-form {
- display: inline-block;
- margin: $spacing / 2 auto;
- padding: $spacing;
-}
-
-table {
- border-collapse: separate;
- border-spacing: 1px;
- margin: 0 auto;
- max-width: 80%;
-
- thead {
- vertical-align: middle;
-
- td {
- font-weight: bolder;
- padding: $spacing / 2 $spacing;
- }
- }
-
- td {
- padding: 3px $spacing / 2;
- text-align: center;
- }
-
- .rules {
- text-align: left;
- }
-}
-
-ul {
- list-style: disc;
-
- li {
- margin-left: $spacing * 2;
- }
-}
-
-ol {
- list-style: decimal;
-
- li {
- margin-left: $spacing * 2;
- }
-}
-
-#error {
- background-color: #ff0000;
- color: #ffffff;
- margin-bottom: $spacing;
- padding: $spacing;
- text-align: center;
-
- h3 {
- font-size: 220%;
- font-weight: bolder;
-
- }
-
- p {
- font-weight: bold;
- }
-}
-
-#post_form, .admin_form {
- text-align: center;
-
- form {
- div {
- @include display-flex;
-
- .title {
- @include flex(0);
- font-weight: bold;
- text-align: right;
- min-width: 120px;
- margin: 1px;
- padding: 4px;
-
- span {
- cursor: pointer;
- }
- }
-
- .fields {
- margin: 1px;
-
- textarea {
- min-height: 100px !important;
- }
- }
- }
-
- button {
- margin: $spacing 0 0 0;
- }
- }
-}
-
-.admin_form {
- form {
- margin: 0;
- padding: 0;
-
- button {
- margin: 0;
- }
- }
-}
-
-.required {
- color: #ff0000;
-}
-
-.boards, .return, .copyright {
- text-align: center;
-}
-
-.archive_stats {
- text-align: center;
- margin-bottom: $spacing;
-}
-
-.announcement {
- text-align: center;
- font-weight: bold;
-}
-
-.banned {
- border: 7px dashed #000000;
- display: table;
- font-size: 180%;
- min-width: 200px;
- max-width: 600px;
- margin: $spacing * 2 auto;
- padding: $spacing;
-
- p {
- margin: $spacing;
- }
-
- div {
- text-align: center;
- }
-}
-
-.medium {
- width: 120px !important;
-}
-
-.short {
- width: 40px !important;
-}
-
-.catalog_container {
- display: inline-block;
- overflow: hidden;
- text-align: center;
- vertical-align: top;
- width: 135px;
-
- .catalog_stats {
- font-size: 70%;
- }
-
- img {
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);
- max-width: 125px;
- max-height: 125px;
- }
-}
-
-.logout {
- top: $spacing;
- right: $spacing;
- position: absolute;
-}
-
-.copyright {
- margin-top: $spacing;
-}
-
-.front_page {
- h2 {
- font-size: 180%;
- }
-
- h3 {
- font-size: 140%;
- margin-top: $spacing;
- }
-
- .board {
- display: inline-block;
- max-width: 300px;
- margin: $spacing * 2;
- vertical-align: top;
-
- a {
- font-size: 160%;
- font-weight: bold;
- }
-
- .subtext {
- display: block;
- font-size: 120%;
- }
- }
-}
-
-.table_of_contents {
- display: table;
- margin: 0 auto;
- max-width: 400px;
-}
-
-.answers,{
- max-width: 600px;
- margin: 0 auto;
-
- h2 {
- font-size: 160%;
- }
-}
-
-.install {
- max-width: 300px;
- margin: 0 auto;
-
- h2 {
- font-size: 160%;
- margin-top: $spacing * 2;
- }
-
- label {
- font-weight: bold;
- margin-top: $spacing * 2;
- }
-
- button {
- font-size: 120%;
- font-weight: bold;
- margin-top: $spacing * 2;
- padding: $spacing / 2;
- }
-}
diff --git a/misc/website/backend/app/src/sass/yotsuba.scss b/misc/website/backend/app/src/sass/yotsuba.scss
deleted file mode 100755
index 5c6b253..0000000
--- a/misc/website/backend/app/src/sass/yotsuba.scss
+++ /dev/null
@@ -1,113 +0,0 @@
-$background: #ffffee;
-$bg_light: #ffffff;
-$border: #d9bfb7;
-$subject: #cc1105;
-$header: #800000;
-$link: #0000ee;
-$hover: #ff0000;
-$quote: #000080;
-$post: #f0e0d6;
-$name: #117743;
-$black: #000000;
-$green: #789922;
-$blue: #292299;
-$active: #f0c0b0;
-$active_border: #d99f91;
-
-body {
- background-color: $background;
-}
-
-body, h1, h2 {
- color: $header;
-}
-
-a, a:visited, .broken_link {
- color: $link;
-}
-
-a:hover, .quote_link:hover, .broken_link:hover {
- color: $hover;
-}
-
-.quote_link {
- color: $quote;
-}
-
-.quote_green {
- color: $green;
-}
-
-.quote_blue {
- color: $blue;
-}
-
-.announcement {
- color: $header;
-}
-
-#post_form, .admin_form {
- form {
- .title {
- background-color: $active;
- border: 1px solid $active_border;
- }
- }
-}
-
-table {
- thead {
- td {
- background-color: $post;
- border: 1px solid $border;
- }
- }
-
- tr:nth-of-type(odd) {
- background-color: $background;
- }
-
- tr:nth-of-type(even) {
- background-color: $bg_light;
- }
-}
-
-.post_container {
- background-color: $post;
- border-right: 1px solid $border;
- border-bottom: 1px solid $border;
-
- &:target {
- background-color: $active;
- border-right: 1px solid $active_border;
- border-bottom: 1px solid $active_border;
- }
-}
-
-.post_subject {
- color: $subject;
-}
-
-.post_name, .post_trip {
- color: $name;
-}
-
-.post_menu .menu {
- background-color: $post;
- border:1px solid $border;
-
- div {
- border-right: 1px solid $border;
- border-bottom: 1px solid $border;
-
- form {
- button {
- color: $header;
-
- &:hover {
- color: $hover;
- }
- }
- }
- }
-}
diff --git a/misc/website/backend/app/src/sass/yotsuba_b.scss b/misc/website/backend/app/src/sass/yotsuba_b.scss
deleted file mode 100755
index 732c1db..0000000
--- a/misc/website/backend/app/src/sass/yotsuba_b.scss
+++ /dev/null
@@ -1,109 +0,0 @@
-$background: #eef2ff;
-$bg_light: #f7f9ff;
-$border: #b7c5d9;
-$subject: #0f0c5d;
-$header: #af0a0f;
-$link: #34345c;
-$hover: #dd0000;
-$post: #d6daf0;
-$name: #117743;
-$black: #000000;
-$green: #789922;
-$blue: #292299;
-$active: #d6bad0;
-$active_border: #ba9dbf;
-
-body {
- background-color: $background;
- color: $black;
-}
-
-h1, h2 {
- color: $header;
-}
-
-a, a:visited, .broken_link {
- color: $link;
-}
-
-a:hover, .quote_link, .quote_link:visited, .broken_link:hover {
- color: $hover;
-}
-
-.quote_green {
- color: $green;
-}
-
-.quote_blue {
- color: $blue;
-}
-
-.announcement {
- color: $header;
-}
-
-#post_form, .admin_form {
- form {
- .title {
- background-color: $active;
- border: 1px solid $active_border;
- }
- }
-}
-
-table {
- thead {
- td {
- background-color: $post;
- border: 1px solid $border;
- }
- }
-
- tr:nth-of-type(odd) {
- background-color: $background;
- }
-
- tr:nth-of-type(even) {
- background-color: $bg_light;
- }
-}
-
-.post_container {
- background-color: $post;
- border-right: 1px solid $border;
- border-bottom: 1px solid $border;
-
- &:target {
- background-color: $active;
- border-right: 1px solid $active_border;
- border-bottom: 1px solid $active_border;
- }
-}
-
-.post_subject {
- color: $subject;
-}
-
-.post_name, .post_trip {
- color: $name;
-}
-
-.post_menu .menu {
- background-color: $post;
- border:1px solid $border;
-
- div {
- border-right: 1px solid $border;
- border-bottom: 1px solid $border;
-
- form {
- button {
- color: $link;
-
- &:hover {
- color: $hover;
- }
- }
- }
- }
-}
diff --git a/misc/website/backend/app/src/utils/capture.lua b/misc/website/backend/app/src/utils/capture.lua
index 2b5816b..fe2795f 100755
--- a/misc/website/backend/app/src/utils/capture.lua
+++ b/misc/website/backend/app/src/utils/capture.lua
@@ -1,35 +1,37 @@
-local ngx = _G.ngx
-local json = require "cjson"
+local ngx = _G.ngx
+local json = require"utils.json"
local function capture(method, uri, body)
- local response = ngx.location.capture(uri, {
- method = method,
- body = json.encode(body)
- })
+ local response = ngx.location.capture(uri, {
+ method = method,
+ body = json.encode(body),
+ })
- if response.truncated then return end
+ if response.truncated then
+ return
+ end
- if response.status ~= ngx.HTTP_OK then
- return nil, json.decode(response.body)
- end
+ if response.status ~= ngx.HTTP_OK then
+ return nil, json.decode(response.body)
+ end
- return json.decode(response.body)
+ return json.decode(response.body)
end
return {
- get = function(...)
- return capture(ngx.HTTP_GET, ...)
- end,
+ get = function(...)
+ return capture(ngx.HTTP_GET, ...)
+ end,
- post = function(...)
- return capture(ngx.HTTP_POST, ...)
- end,
+ post = function(...)
+ return capture(ngx.HTTP_POST, ...)
+ end,
- put = function(...)
- return capture(ngx.HTTP_PUT, ...)
- end,
+ put = function(...)
+ return capture(ngx.HTTP_PUT, ...)
+ end,
- delete = function(...)
- return capture(ngx.HTTP_DELETE, ...)
- end,
+ delete = function(...)
+ return capture(ngx.HTTP_DELETE, ...)
+ end,
}
diff --git a/misc/website/backend/app/src/utils/error.lua b/misc/website/backend/app/src/utils/error.lua
index c6644bc..c157d06 100755
--- a/misc/website/backend/app/src/utils/error.lua
+++ b/misc/website/backend/app/src/utils/error.lua
@@ -1,64 +1,94 @@
-local ngx = _G.ngx
+local ngx = _G.ngx
local get_error = {}
-local status = {}
+local status = {}
---[[ API Error Codes ]]--
+--[[ API Error Codes ]] --
-- Authorization
-- email:api_key format in Authorization HTTP header is invalid
function get_error.malformed_authorization()
- return { code=100 }
+ return {
+ code = 100,
+ }
end
-- email:api_key in Authorization HTTP header does not match any user
-- login credentials do not match any user
function get_error.invalid_authorization()
- return { code=101 }
+ return {
+ code = 101,
+ }
end
-- Attempting to access endpoint that requires higher priviliges
function get_error.unauthorized_access()
- return { code=102 }
+ return {
+ code = 102,
+ }
end
-- Data Validation
function get_error.field_not_found(field)
- return { code=200, field=field }
+ return {
+ code = 200,
+ field = field,
+ }
end
function get_error.field_invalid(field)
- return { code=201, field=field }
+ return {
+ code = 201,
+ field = field,
+ }
end
function get_error.field_not_unique(field)
- return { code=202, field=field }
+ return {
+ code = 202,
+ field = field,
+ }
end
function get_error.token_expired(field)
- return { code=203, field=field }
+ return {
+ code = 203,
+ field = field,
+ }
end
function get_error.password_not_match()
- return { code=204 }
+ return {
+ code = 204,
+ }
end
-- Database I/O
function get_error.database_unresponsive()
- return { code=300 }
+ return {
+ code = 300,
+ }
end
function get_error.database_create()
- return { code=301 }
+ return {
+ code = 301,
+ }
end
function get_error.database_modify()
- return { code=302 }
+ return {
+ code = 302,
+ }
end
function get_error.database_delete()
- return { code=303 }
+ return {
+ code = 303,
+ }
end
function get_error.database_select()
- return { code=304 }
+ return {
+ code = 304,
+ }
end
---[[ API -> HTTP Code Map ]]--
+--[[ API -> HTTP Code Map ]] --
-- Authorization
status[100] = ngx.HTTP_BAD_REQUEST
@@ -79,26 +109,26 @@ status[303] = ngx.HTTP_INTERNAL_SERVER_ERROR
status[304] = ngx.HTTP_INTERNAL_SERVER_ERROR
return {
- get_error = get_error,
- handle = function(self)
-
- -- Inject localized error messages
- for _, err in ipairs(self.errors) do
- --err.message = self.i18n(err.code)
- if type(err) == "table" then
- for k, v in pairs(err) do
- print(k, ": ", v)
- end
- else
- print(err)
- end
- end
-
- print(#self.errors)
-
- return self:write {
- status = 401,--status[self.errors[1].code],
- json = self.errors
- }
- end
+ get_error = get_error,
+ handle = function(self)
+
+ -- Inject localized error messages
+ for _, err in ipairs(self.errors) do
+ -- err.message = self.i18n(err.code)
+ if type(err) == "table" then
+ for k, v in pairs(err) do
+ print(k, ": ", v)
+ end
+ else
+ print(err)
+ end
+ end
+
+ print(#self.errors)
+
+ return self:write{
+ status = 401, -- status[self.errors[1].code],
+ json = self.errors,
+ }
+ end,
}
diff --git a/misc/website/backend/app/src/utils/file_whitelist.lua b/misc/website/backend/app/src/utils/file_whitelist.lua
index 78aa552..aefd14d 100755
--- a/misc/website/backend/app/src/utils/file_whitelist.lua
+++ b/misc/website/backend/app/src/utils/file_whitelist.lua
@@ -1,22 +1,22 @@
-- A whitelist of filetypes
return {
- -- Image formats
- image = {
- [".bmp"] = true,
- [".png"] = true,
- [".gif"] = true,
- [".jpg"] = true,
- [".jpeg"] = true,
- [".webp"] = true,
- [".webm"] = true,
- [".svg"] = true
- },
+ -- Image formats
+ image = {
+ [".bmp"] = true,
+ [".png"] = true,
+ [".gif"] = true,
+ [".jpg"] = true,
+ [".jpeg"] = true,
+ [".webp"] = true,
+ [".webm"] = true,
+ [".svg"] = true,
+ },
- -- Audio formats
- audio = {
- [".wav"] = true,
- [".flac"] = true,
- [".mp3"] = true,
- [".ogg"] = true
- }
+ -- Audio formats
+ audio = {
+ [".wav"] = true,
+ [".flac"] = true,
+ [".mp3"] = true,
+ [".ogg"] = true,
+ },
}
diff --git a/misc/website/backend/app/src/utils/generate.lua b/misc/website/backend/app/src/utils/generate.lua
index 2dd32a4..d95d2f9 100755
--- a/misc/website/backend/app/src/utils/generate.lua
+++ b/misc/website/backend/app/src/utils/generate.lua
@@ -1,29 +1,28 @@
-
-local config = require("lapis.config").get()
+local config = require("lapis.config").get()
local encoding = require "lapis.util.encoding"
-local sha256 = require "resty.sha256"
-local ffi = require "ffi"
-local posix = require "posix"
-local salt = loadfile("../data/secrets/salt.lua")()
-local token = config.secret
-local sf = string.format
-local ss = string.sub
+local sha256 = require "resty.sha256"
+local ffi = require "ffi"
+local posix = require "posix"
+local salt = loadfile("../data/secrets/salt.lua")()
+local token = config.secret
+local sf = string.format
+local ss = string.sub
local function get_chunks(str)
- -- Secure trip
- local name, tripcode = str:match("(.-)(##.+)")
+ -- Secure trip
+ local name, tripcode = str:match("(.-)(##.+)")
- -- Insecure trip
- if not name then
- name, tripcode = str:match("(.-)(#.+)")
+ -- Insecure trip
+ if not name then
+ name, tripcode = str:match("(.-)(#.+)")
- -- Just a name
- if not name then
- return str:match("(.+)")
- end
- end
+ -- Just a name
+ if not name then
+ return str:match("(.+)")
+ end
+ end
- return name, tripcode
+ return name, tripcode
end
local generate = {}
@@ -31,23 +30,23 @@ local generate = {}
-- math.random isn't reliable for this use case, so instead we're gonna snag
-- some bytes from /dev/urandom, create a uint32, and grab the last 3 digits.
function generate.random()
- -- Read uint32_t from /dev/urandom
- local r = io.open("/dev/urandom", "rb")
- local bytes = r:read(4)
- r:close()
+ -- Read uint32_t from /dev/urandom
+ local r = io.open("/dev/urandom", "rb")
+ local bytes = r:read(4)
+ r:close()
- -- Build number
- local num = ffi.new("unsigned int[1]")
- ffi.copy(num, bytes, 4)
+ -- Build number
+ local num = ffi.new("unsigned int[1]")
+ ffi.copy(num, bytes, 4)
- return sf("%03d", num[0] % 1000)
+ return sf("%03d", num[0] % 1000)
end
-- Generate an insecure password
function generate.password(time)
- local hasher = sha256:new()
- hasher:update(sf("%s%s", time, generate.random()))
- return encoding.encode_base64(hasher:final())
+ local hasher = sha256:new()
+ hasher:update(sf("%s%s", time, generate.random()))
+ return encoding.encode_base64(hasher:final())
end
-- Generate a secure or insecure tripcode based off the name a user supplies
@@ -55,40 +54,40 @@ end
-- Secure tripcodes use sha256 + the app's secret token.
-- Insecure tripcodes use standard posix crypt + the app's secret salt.
function generate.tripcode(raw_name)
- local name, tripcode = get_chunks(raw_name)
-
- if tripcode then
- local pattern = "^([^=]*)"
- tripcode = tripcode:sub(2) -- remove leading '#'
-
- -- Secure tripcode
- if tripcode:sub(1, 1) == "#" then
- local hasher = sha256:new()
- tripcode = token .. tripcode:sub(2) -- remove leading '#'
- hasher:update(tripcode)
- local hash = encoding.encode_base64(hasher:final())
- tripcode = "!!" .. ss(hash:match(pattern), -10)
- -- Insecure tripcode
- else
- local hash = posix.crypt(tripcode, salt)
- tripcode = "!" .. ss(hash, -10)
- end
- end
-
- return name, tripcode
+ local name, tripcode = get_chunks(raw_name)
+
+ if tripcode then
+ local pattern = "^([^=]*)"
+ tripcode = tripcode:sub(2) -- remove leading '#'
+
+ -- Secure tripcode
+ if tripcode:sub(1, 1) == "#" then
+ local hasher = sha256:new()
+ tripcode = token .. tripcode:sub(2) -- remove leading '#'
+ hasher:update(tripcode)
+ local hash = encoding.encode_base64(hasher:final())
+ tripcode = "!!" .. ss(hash:match(pattern), -10)
+ -- Insecure tripcode
+ else
+ local hash = posix.crypt(tripcode, salt)
+ tripcode = "!" .. ss(hash, -10)
+ end
+ end
+
+ return name, tripcode
end
function generate.errors(i18n, errors)
- local err = {}
+ local err = {}
- if #errors > 0 then
- for _, error in ipairs(errors) do
- local e = i18n(unpack(error))
- table.insert(err, e)
- end
- end
+ if #errors > 0 then
+ for _, error in ipairs(errors) do
+ local e = i18n(unpack(error))
+ table.insert(err, e)
+ end
+ end
- return err
+ return err
end
return generate
diff --git a/misc/website/backend/app/src/utils/json.lua b/misc/website/backend/app/src/utils/json.lua
new file mode 100644
index 0000000..27f4a21
--- /dev/null
+++ b/misc/website/backend/app/src/utils/json.lua
@@ -0,0 +1,42 @@
+local cjson = require("cjson")
+local xpcall = xpcall
+
+-- 设置 处理稀疏数组
+-- https://www.kyne.com.au/~mark/software/lua-cjson-manual.html#encode_sparse_array
+cjson.encode_sparse_array(true, 1, 1)
+
+-- https://github.com/cloudwu/lua-cjson/pull/8
+cjson.encode_empty_table_as_array("on")
+
+-- https://github.com/cloudwu/lua-cjson/pull/10
+cjson.encode_number_precision(16)
+
+local json = {}
+
+function json.encode(var)
+ local function _logExce(err)
+ print("json encode", err)
+ end
+
+ local status, result = xpcall(cjson.encode, _logExce, var)
+ if status then
+ return result
+ end
+end
+
+function json.decode(text)
+ if not text then
+ return
+ end
+
+ local function _logExce(err)
+ print("json decode", text, err)
+ end
+
+ local status, result = xpcall(cjson.decode, _logExce, text)
+ if status then
+ return result
+ end
+end
+
+return json
diff --git a/misc/website/backend/app/src/utils/request_processor.lua b/misc/website/backend/app/src/utils/request_processor.lua
deleted file mode 100755
index 3e4cec7..0000000
--- a/misc/website/backend/app/src/utils/request_processor.lua
+++ /dev/null
@@ -1,298 +0,0 @@
-local Bans = require "models.bans"
-local Threads = require "models.threads"
-local Posts = require "models.posts"
-local Reports = require "models.reports"
-local process = {}
-
-function process.create_thread(params, session, board)
-
- -- Prepare data for entry
- local _, err = Posts:prepare_post(params, session, board)
- if err then
- return false, err
- end
-
- -- Archive old threads
- local max_threads = board.threads_per_page * board.pages
- Threads:archive_threads(board.id, max_threads)
-
- -- Delete old archived threads
- local time = os.time()
- local threads = Threads:get_archived_threads(board.id)
-
- for _, t in ipairs(threads) do
- if time - t.last_active > board.archive_time and not t.save then
- local posts = Posts:get_thread_posts(t.id)
- Threads:delete_thread("override", t, posts[1])
-
- -- Delete all associated posts
- for _, post in ipairs(posts) do
- Posts:delete_post("override", board, post)
-
- -- Delete associated report
- local report = Reports:get_report(post.id)
- if report then
- Reports:delete_report(report)
- end
- end
- end
- end
-
- -- Insert post data into database
- local post, err = Posts:create_post(
- params,
- session,
- board,
- thread,
- true
- )
- if err then
- return false, err
- end
-
- return post
-end
-
-function process.create_post(params, session, board, thread)
- local posts = Posts:count_posts(thread.id)
- local files = Posts:count_files(thread.id)
-
- -- Prepare data for entry
- local _, err = Posts:prepare_post(
- params, session, board, thread, files
- )
- if err then
- return false, err
- end
-
- -- Insert post data into database
- local post, err = Posts:create_post(
- params,
- session,
- board,
- thread,
- false
- )
- if err then
- return false, err
- end
-
- posts = posts + 1
-
- -- Check for [auto]sage
- if params.options ~= "sage" and
- posts <= board.post_limit then
- -- Update thread
- thread.last_active = os.time()
- thread:update("last_active")
- end
-
- return post
-end
-
-function process.delete_thread(params, session, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.thread_id)
- if not post then
- return false, { "err_invalid_post", { params.thread_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- local posts = Posts:get_posts_by_thread(thread.id)
-
- -- Delete thread
- local _, err = Threads:delete_thread(session, thread, posts[1])
- if err then
- return false, err
- end
-
- -- Delete all associated posts
- for _, post in ipairs(posts) do
- Posts:delete_post("override", board, post)
-
- -- Delete associated report
- local report = Reports:get_report(board.id, post.id)
- if report then
- Reports:delete_report(report)
- end
- end
-
- return true
-end
-
-function process.delete_post(params, session, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- -- Delete post
- local _, err = Posts:delete_post(session, board, post)
- if err then
- return false, err
- end
-
- -- Update thread
- local posts = Posts:get_posts_by_thread(thread.id)
- thread.last_active = posts[#posts].timestamp
- thread:update("last_active")
-
- return true
-end
-
-function process.report_post(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.thread } }
- end
-
- local report = Reports:get_report(board.id, post.post_id)
-
- -- If report exists, update it
- if report then
- report.num_reports = report.num_reports + 1 -- FIXME: race condition
- local _, err = Reports:modify_report(report)
- if err then
- return false, err
- end
- -- If report is new, create it
- else
- local _, err = Reports:create_report {
- board_id = board.id,
- thread_id = post.thread_id,
- post_id = post.post_id,
- timestamp = os.time(),
- num_reports = 1
- }
- if err then
- return false, err
- end
- end
-
- return post
-end
-
--- Sticky thread
-function process.sticky_thread(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- thread.sticky = not thread.sticky
- thread:update("sticky")
-
- return true
-end
-
--- Lock thread
-function process.lock_thread(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- thread.lock = not thread.lock
- thread:update("lock")
-
- return true
-end
-
--- Save thread
-function process.save_thread(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- thread.save = not thread.save
- thread:update("save")
-
- return true
-end
-
--- Override thread
-function process.override_thread(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- -- Validate thread
- local thread = Threads:get_thread(post.thread_id)
- if not thread then
- return false, { "err_invalid_thread" }
- end
-
- thread.size_override = not thread.size_override
- thread:update("size_override")
-
- return true
-end
-
--- Ban user
-function process.ban_user(params, board)
- -- Validate post
- local post = Posts:get_post(board.id, params.post_id)
- if not post then
- return false, { "err_invalid_post", { params.post_id } }
- end
-
- params.ip = post.ip
-
- -- Convert board name to id if checkbox is set
- if params.board_id then
- params.board_id = board.id
- end
-
- -- Ban user
- local _, err = Bans:create_ban(params)
- if err then
- return false, err
- end
-
- -- Flag post
- if params.banned then
- post.banned = true
- post:update("banned")
- end
-
- return true
-end
-
-return process
diff --git a/misc/website/backend/app/src/utils/role.lua b/misc/website/backend/app/src/utils/role.lua
index 87062ce..f4c22a0 100755
--- a/misc/website/backend/app/src/utils/role.lua
+++ b/misc/website/backend/app/src/utils/role.lua
@@ -1,31 +1,31 @@
-local models = require "models"
-local get_error = require "utils.error".get_error
-local Users = models.users
-local role = {}
+local models = require "models"
+local get_error = require"utils.error".get_error
+local Users = models.users
+local role = {}
-- User must be the Owner
function role.owner(user)
- return user.role == Users.role.OWNER and true or nil, get_error.unauthorized_access()
+ return user.role == Users.role.OWNER and true or nil, get_error.unauthorized_access()
end
-- User must be an Admin or higher
function role.admin(user)
- return user.role >= Users.role.ADMIN and true or nil, get_error.unauthorized_access()
+ return user.role >= Users.role.ADMIN and true or nil, get_error.unauthorized_access()
end
-- User must be a Mod or higher
function role.mod(user)
- return user.role >= Users.role.MOD and true or nil, get_error.unauthorized_access()
+ return user.role >= Users.role.MOD and true or nil, get_error.unauthorized_access()
end
-- User must be a Janitor or higher
function role.janitor(user)
- return user.role >= Users.role.JANITOR and true or nil, get_error.unauthorized_access()
+ return user.role >= Users.role.JANITOR and true or nil, get_error.unauthorized_access()
end
-- User must be signed in
function role.user(user)
- return user.role >= Users.role.USER and true or nil, get_error.unauthorized_access()
+ return user.role >= Users.role.USER and true or nil, get_error.unauthorized_access()
end
return role
diff --git a/misc/website/backend/app/src/utils/text_formatter.lua b/misc/website/backend/app/src/utils/text_formatter.lua
index ae1f44a..f88e1ef 100755
--- a/misc/website/backend/app/src/utils/text_formatter.lua
+++ b/misc/website/backend/app/src/utils/text_formatter.lua
@@ -1,20 +1,20 @@
-local Posts = require "models.posts"
-local escape = require("lapis.html").escape
-local sf = string.format
+local Posts = require "models.posts"
+local escape = require("lapis.html").escape
+local sf = string.format
local formatter = {}
--- Sanitize text for HTML safety
-- @tparam string text Raw text
-- @treturn string formatted
function formatter.sanitize(text)
- return escape(text)
+ return escape(text)
end
--- Format new lines to 'br' tags
-- @tparam string text Raw text
-- @treturn string formatted
function formatter.new_lines(text)
- return text:gsub("\n", " \n")
+ return text:gsub("\n", " \n")
end
--- Format words that begin with '>>'
@@ -24,137 +24,150 @@ end
-- @tparam table post Post data
-- @treturn string formatted
function formatter.quote(text, request, board, post)
- local function get_url(board, post_id)
- if tonumber(post_id) then
- local p = Posts:get(board.id, post_id)
- if not p then return false end
-
- local thread = p:get_thread()
- if not thread then return false end
-
- local op = thread:get_op()
- return
- request:url_for("web.boards.thread", { board=board.name, thread=op.post_id }),
- op
- else
- return request:url_for("web.boards.board", { board=board.name })
- end
- end
-
- -- >>1234 ur a fag
- -- >>(%d+)
- local match_pattern = ">>(%d+)"
- local sub_pattern = ">>%s"
-
- -- Get all the matches and store them in an ordered list
- local posts = {}
- for post_id in text:gmatch(match_pattern) do
- table.insert(posts, { board=board, id=post_id })
- end
-
- -- Format each match
- for i, p in ipairs(posts) do
- local text = sf(sub_pattern, p.id)
- local url, op = get_url(p.board, p.id)
- if url then
- if op.thread_id == post.thread_id then
- posts[i] = sf("%s", url, p.id, text)
- else
- posts[i] = sf("%s→", url, p.id, text)
- end
- else
- posts[i] = sf("%s", text)
- end
- end
-
- -- Substitute each match with the formatted match
- local i = 0
- text = text:gsub(match_pattern, function()
- i = i + 1
- return posts[i]
- end)
-
- -- >>>/a/1234 check over here
- -- >>>/(%w+)/(%d*)
- match_pattern = ">>>/(%w+)/(%d*)"
- sub_pattern = ">>>/%s/%s"
-
- -- Get all the matches and store them in an ordered list
- posts = {}
- for b, post_id in text:gmatch(match_pattern) do
- local response = request.api.boards.GET(request)
- b = response.json or b
- table.insert(posts, { board=b, id=post_id })
- end
-
- -- Format each match
- for i, p in ipairs(posts) do
- if type(p.board) == "table" then
- local text = sf(sub_pattern, p.board.name, p.id)
- local url, op = get_url(p.board, p.id)
- if op then
- posts[i] = sf("%s", url, p.id, text)
- else
- posts[i] = sf("%s", url, text)
- end
- else
- local text = sf(sub_pattern, p.board, p.id)
- posts[i] = sf("%s", text)
- end
- end
-
- -- Substitute each match with the formatted match
- i = 0
- text = text:gsub(match_pattern, function()
- i = i + 1
- return posts[i]
- end)
-
- return text
+ local function get_url(board, post_id)
+ if tonumber(post_id) then
+ local p = Posts:get(board.id, post_id)
+ if not p then
+ return false
+ end
+
+ local thread = p:get_thread()
+ if not thread then
+ return false
+ end
+
+ local op = thread:get_op()
+ return request:url_for("web.boards.thread", {
+ board = board.name,
+ thread = op.post_id,
+ }), op
+ else
+ return request:url_for("web.boards.board", {
+ board = board.name,
+ })
+ end
+ end
+
+ -- >>1234 ur a fag
+ -- >>(%d+)
+ local match_pattern = ">>(%d+)"
+ local sub_pattern = ">>%s"
+
+ -- Get all the matches and store them in an ordered list
+ local posts = {}
+ for post_id in text:gmatch(match_pattern) do
+ table.insert(posts, {
+ board = board,
+ id = post_id,
+ })
+ end
+
+ -- Format each match
+ for i, p in ipairs(posts) do
+ local text = sf(sub_pattern, p.id)
+ local url, op = get_url(p.board, p.id)
+ if url then
+ if op.thread_id == post.thread_id then
+ posts[i] = sf("%s", url, p.id, text)
+ else
+ posts[i] = sf("%s→", url, p.id, text)
+ end
+ else
+ posts[i] = sf("%s", text)
+ end
+ end
+
+ -- Substitute each match with the formatted match
+ local i = 0
+ text = text:gsub(match_pattern, function()
+ i = i + 1
+ return posts[i]
+ end)
+
+ -- >>>/a/1234 check over here
+ -- >>>/(%w+)/(%d*)
+ match_pattern = ">>>/(%w+)/(%d*)"
+ sub_pattern = ">>>/%s/%s"
+
+ -- Get all the matches and store them in an ordered list
+ posts = {}
+ for b, post_id in text:gmatch(match_pattern) do
+ local response = request.api.boards.GET(request)
+ b = response.json or b
+ table.insert(posts, {
+ board = b,
+ id = post_id,
+ })
+ end
+
+ -- Format each match
+ for i, p in ipairs(posts) do
+ if type(p.board) == "table" then
+ local text = sf(sub_pattern, p.board.name, p.id)
+ local url, op = get_url(p.board, p.id)
+ if op then
+ posts[i] = sf("%s", url, p.id, text)
+ else
+ posts[i] = sf("%s", url, text)
+ end
+ else
+ local text = sf(sub_pattern, p.board, p.id)
+ posts[i] = sf("%s", text)
+ end
+ end
+
+ -- Substitute each match with the formatted match
+ i = 0
+ text = text:gsub(match_pattern, function()
+ i = i + 1
+ return posts[i]
+ end)
+
+ return text
end
--- Format lines that begin with '>'
-- @tparam string text Raw text
-- @treturn string formatted
function formatter.green_text(text)
- local formatted = ""
+ local formatted = ""
- for line in text:gmatch("[^\n]+") do
- local first = line:sub(1, 4)
+ for line in text:gmatch("[^\n]+") do
+ local first = line:sub(1, 4)
- -- >implying
- if first == ">" then
- line = sf("%s%s%s", "", line, "")
- end
+ -- >implying
+ if first == ">" then
+ line = sf("%s%s%s", "", line, "")
+ end
- formatted = sf("%s%s%s", formatted, line, "\n")
- end
+ formatted = sf("%s%s%s", formatted, line, "\n")
+ end
- return formatted
+ return formatted
end
--- Format lines that begin with '<'
-- @tparam string text Raw text
-- @treturn string formatted
function formatter.blue_text(text)
- local formatted = ""
+ local formatted = ""
- for line in text:gmatch("[^\n]+") do
- local first = line:sub(1, 4)
+ for line in text:gmatch("[^\n]+") do
+ local first = line:sub(1, 4)
- -- ", line, "")
- end
+ -- ", line, "")
+ end
- formatted = sf("%s%s%s", formatted, line, "\n")
- end
+ formatted = sf("%s%s%s", formatted, line, "\n")
+ end
- return formatted
+ return formatted
end
function formatter.spoiler(text)
- return text:gsub("(%[spoiler%])(.-)(%[/spoiler%])", "%2")
+ return text:gsub("(%[spoiler%])(.-)(%[/spoiler%])", "%2")
end
return formatter
diff --git a/misc/website/backend/app/src/views/admin/admin.etlua b/misc/website/backend/app/src/views/admin/admin.etlua
deleted file mode 100755
index 0619f36..0000000
--- a/misc/website/backend/app/src/views/admin/admin.etlua
+++ /dev/null
@@ -1,247 +0,0 @@
-
-<%
- local posts = thread.posts or {}
- for i=#posts, 1, -1 do
- local post = posts[i]
- local op = posts[#posts]
- if post.post_id == op.post_id then
- render('views.fragments.op_content', { thread=thread, post=post, posts=posts, is_board=true })
- else
- render('views.fragments.post_content', { thread=thread, post=post, posts=posts, is_board=true, op=op })
- end
- end
-%>
-
diff --git a/misc/website/backend/app/src/views/fragments/list_boards.etlua b/misc/website/backend/app/src/views/fragments/list_boards.etlua
deleted file mode 100755
index d6ac290..0000000
--- a/misc/website/backend/app/src/views/fragments/list_boards.etlua
+++ /dev/null
@@ -1,16 +0,0 @@
-<% if boards then %>
-
- [ <%= i18n('index') %> ]
- <% local group = 1 %>
- <% for i, board in ipairs(boards) do %>
- <% local sub = sub_page or '' %>
- <% if board.group ~= group then %>
- <% group = board.group %> ] [
- <% else %>
- <%= i == 1 and '[' or '/' %>
- <% end %>
- <%= board.name %>
- <%= i == #boards and ']' or '' %>
- <% end %>
-
- <%= i18n("file") %>:
- <%
- if post.file_name:len() > 35 then
- local short = string.sub(post.file_name, 1, 25)
- local ext = post.file_name:match("^.+(%..+)$")
- local name = string.format("%s(...)%s", short, ext)
- %>
- <%= name %>
- <% else %>
- <%= post.file_name %>
- <% end%>
- (<%= post.file_size %> KB<%= post.file_dimensions %><%= post.file_duration %>)
-
-
-
-
- <% end %>
-
- <% render("views.fragments.post_menu", { post=post, op=true }) %>
- <% if post.subject then %>
- <%= post.subject %>
- <% end %>
- <%= post.name %>
- <% if post.trip then %>
- <%= post.trip %>
- <% end %>
- <%= post.timestamp %>
- No.<%= post.post_id %>
- <% if thread.sticky or thread.lock or thread.save or thread.size_override then %>
-
- <% if thread.sticky then %>
- 📌
- <% end %>
- <% if thread.lock then %>
- 🔒
- <% end %>
- <% if thread.save then %>
- 💾
- <% end %>
- <% if thread.size_override then %>
- ✔️
- <% end %>
-
- <% end %>
- <% if is_board then %>
- [<%= i18n('reply') %>]
- <% end %>
-
-
- <%- post.comment %>
- <% if post.banned then %>
-
(<%= board.ban_message %>)
- <% end %>
-
-
-<% if thread.hidden and thread.hidden.posts > 0 then %>
-
diff --git a/misc/website/backend/app/src/views/thread.etlua b/misc/website/backend/app/src/views/thread.etlua
deleted file mode 100755
index 2230e92..0000000
--- a/misc/website/backend/app/src/views/thread.etlua
+++ /dev/null
@@ -1,26 +0,0 @@
-<% render('views.fragments.board_title') %>
-<% if not thread.lock or session.admin or session.mod then
- render('views.fragments.form_submit')
-end %>
-<% render('views.fragments.announcements') %>
-
-[<%= i18n('return') %>]
-[<%= i18n('catalog') %>]
-[<%= i18n('bottom') %>]
-[<%= i18n('refresh') %>]
-
-
-<% for i, post in ipairs(posts) do
- if i == 1 then
- render('views.fragments.op_content', { thread=thread, post=post })
- else
- render('views.fragments.post_content', { thread=thread, post=post, op=posts[1] })
- end
-end %>
-
(<%= board.ban_message %>)
- <% end %> -