@waline/vercel 1.36.5 → 1.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/development.js +1 -0
  2. package/index.js +4 -3
  3. package/package.json +2 -2
  4. package/src/config/adapter.js +8 -5
  5. package/src/config/config.js +10 -5
  6. package/src/controller/article.js +2 -2
  7. package/src/controller/comment.js +24 -21
  8. package/src/controller/oauth.js +7 -4
  9. package/src/controller/rest.js +1 -1
  10. package/src/controller/token.js +3 -1
  11. package/src/controller/user.js +30 -8
  12. package/src/controller/verification.js +3 -2
  13. package/src/extend/think.js +33 -19
  14. package/src/locales/de.json +19 -0
  15. package/src/locales/es.json +19 -0
  16. package/src/locales/fr.json +19 -0
  17. package/src/locales/index.js +18 -2
  18. package/src/locales/jp.json +19 -0
  19. package/src/locales/pt-BR.json +19 -0
  20. package/src/locales/ru.json +19 -0
  21. package/src/locales/vi-VN.json +19 -0
  22. package/src/logic/base.js +26 -24
  23. package/src/logic/comment.js +7 -4
  24. package/src/logic/db.js +1 -1
  25. package/src/logic/token.js +2 -2
  26. package/src/logic/user.js +25 -0
  27. package/src/middleware/dashboard.js +1 -0
  28. package/src/middleware/plugin.js +1 -1
  29. package/src/service/akismet.js +10 -3
  30. package/src/service/avatar.js +1 -1
  31. package/src/service/markdown/highlight.js +1 -1
  32. package/src/service/markdown/utils.js +5 -5
  33. package/src/service/markdown/xss.js +5 -10
  34. package/src/service/notify.js +56 -54
  35. package/src/service/storage/base.js +1 -1
  36. package/src/service/storage/cloudbase.js +9 -7
  37. package/src/service/storage/github.js +24 -18
  38. package/src/service/storage/leancloud.js +33 -26
  39. package/src/service/storage/mongodb.js +10 -6
  40. package/src/service/storage/mysql.js +4 -4
  41. package/src/service/storage/postgresql.js +5 -5
@@ -0,0 +1,19 @@
1
+ {
2
+ "import data format not support!": "Format de fichier non pris en charge",
3
+ "USER_EXIST": "L'utilisateur existe déjà",
4
+ "USER_NOT_EXIST": "L'utilisateur n'existe pas",
5
+ "USER_REGISTERED": "Utilisateur déjà enregistré",
6
+ "TOKEN_EXPIRED": "Le jeton a expiré",
7
+ "TWO_FACTOR_AUTH_ERROR_DETAIL": "Échec de l'authentification à deux facteurs",
8
+ "[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】Mail de confirmation d'inscription",
9
+ "Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "Veuillez cliquer sur <a href=\"{{url}}\">{{url}}</a> pour confirmer l'inscription. Le lien est valable 1 heure. Si vous ne vous êtes pas inscrit, ignorez cet e-mail.",
10
+ "[{{name | safe}}] Reset Password": "【{{name | safe}}】Réinitialisation du mot de passe",
11
+ "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "Veuillez cliquer sur <a href=\"{{url}}\">{{url}}</a> pour vous connecter et changer votre mot de passe dès que possible !",
12
+ "Duplicate Content": "Contenu dupliqué envoyé",
13
+ "Comment too fast": "Vous commentez trop rapidement, veuillez ralentir !",
14
+ "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "Échec de l'envoi du mail de confirmation. Veuillez {%- if isAdmin -%}vérifier votre configuration d'e-mail{%- else -%}vérifier votre adresse e-mail et contacter l'administrateur{%- endif -%}.",
15
+ "MAIL_SUBJECT": "{{parent.nick | safe}},Votre commentaire sur '{{site.name | safe}}' a reçu une réponse",
16
+ "MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Votre commentaire sur <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> a une nouvelle réponse </h2>{{parent.nick}}, vous avez commenté : <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> a répondu :</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Vous pouvez voir la réponse complète <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>ici</a>, et revenez visiter <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>.</p><br/> </div></div>",
17
+ "MAIL_SUBJECT_ADMIN": "Nouveau commentaire sur {{site.name | safe}}",
18
+ "MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Votre article sur <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> a de nouveaux commentaires </h2> <p><strong>{{self.nick}}</strong> a écrit :</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Vous pouvez voir la réponse complète <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>ici</a></p><br/> </div>"
19
+ }
@@ -2,14 +2,30 @@ const en = require('./en.json');
2
2
  const it = require('./it.json');
3
3
  const zhCN = require('./zh-CN.json');
4
4
  const zhTW = require('./zh-TW.json');
5
+ const jp = require('./jp.json');
6
+ const de = require('./de.json');
7
+ const esMX = require('./es.json');
8
+ const fr = require('./fr.json');
9
+ const ru = require('./ru.json');
10
+ const vi = require('./vi-VN.json');
11
+ const ptBR = require('./pt-BR.json');
5
12
 
6
13
  module.exports = {
7
14
  'zh-cn': zhCN,
8
15
  'zh-tw': zhTW,
9
16
  en: en,
10
17
  'en-us': en,
11
- jp: en,
12
- 'jp-jp': en,
13
18
  it,
14
19
  'it-it': it,
20
+ jp,
21
+ 'jp-jp': jp,
22
+ de,
23
+ esMX,
24
+ fr,
25
+ 'fr-fr': fr,
26
+ ru,
27
+ 'ru-ru': ru,
28
+ vi,
29
+ 'vi-vn': vi,
30
+ 'pt-br': ptBR,
15
31
  };
@@ -0,0 +1,19 @@
1
+ {
2
+ "import data format not support!": "ファイル形式はサポートされていません",
3
+ "USER_EXIST": "ユーザーは既に存在します",
4
+ "USER_NOT_EXIST": "ユーザーが存在しません",
5
+ "USER_REGISTERED": "ユーザーは既に登録されています",
6
+ "TOKEN_EXPIRED": "トークンの有効期限が切れています",
7
+ "TWO_FACTOR_AUTH_ERROR_DETAIL": "二段階認証に失敗しました",
8
+ "[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】 登録確認メール",
9
+ "Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "登録を確認するには <a href=\"{{url}}\">{{url}}</a> をクリックしてください。リンクの有効期限は1時間です。登録していない場合は、このメールを無視してください。",
10
+ "[{{name | safe}}] Reset Password": "【{{name | safe}}】 パスワードリセット",
11
+ "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "できるだけ早く <a href=\"{{url}}\">{{url}}</a> をクリックしてログインし、パスワードを変更してください!",
12
+ "Duplicate Content": "重複した内容が送信されました",
13
+ "Comment too fast": "コメントが速すぎます。少し待ってください!",
14
+ "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "登録確認メールの送信に失敗しました。{%- if isAdmin -%}メール設定を確認してください{%- else -%}メールアドレスを確認し、管理者に連絡してください{%- endif -%}。",
15
+ "MAIL_SUBJECT": "{{parent.nick | safe}}、『{{site.name | safe}}』にあなたのコメントへの返信があります",
16
+ "MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> にあなたのコメントへの新しい返信があります </h2>{{parent.nick}}さん、あなたは次のようにコメントしました: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> が次のように返信しました:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>返信の全文は <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>こちら</a> で確認できます。<a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a> にまたお越しください。</p><br/> </div></div>",
17
+ "MAIL_SUBJECT_ADMIN": "{{site.name | safe}} に新しいコメントがあります",
18
+ "MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> に新しいコメントがあります </h2> <p><strong>{{self.nick}}</strong> が次のようにコメントしました:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>全文は <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>こちら</a> で確認できます。</p><br/> </div>"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "import data format not support!": "Formato de arquivo não suportado",
3
+ "USER_EXIST": "Usuário já existe",
4
+ "USER_NOT_EXIST": "Usuário não existe",
5
+ "USER_REGISTERED": "Usuário já registrado",
6
+ "TOKEN_EXPIRED": "Token expirou",
7
+ "TWO_FACTOR_AUTH_ERROR_DETAIL": "Erro de autenticação em dois fatores",
8
+ "[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】Confirmação de registro",
9
+ "Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "Por favor clique em <a href=\"{{url}}\">{{url}}</a> para confirmar o registro. O link é válido por 1 hora. Se você não está registrando, ignore este e-mail.",
10
+ "[{{name | safe}}] Reset Password": "【{{name | safe}}】Redefinir senha",
11
+ "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "Por favor clique em <a href=\"{{url}}\">{{url}}</a> para entrar e alterar sua senha o quanto antes!",
12
+ "Duplicate Content": "Conteúdo duplicado enviado",
13
+ "Comment too fast": "Comentando rápido demais, por favor aguarde!",
14
+ "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "Falha ao enviar o e-mail de confirmação. Por favor {%- if isAdmin -%}verifique a configuração de e-mail{%- else -%}verifique seu endereço de e-mail e contate o administrador{%- endif -%}.",
15
+ "MAIL_SUBJECT": "{{parent.nick | safe}},Seu comentário em '{{site.name | safe}}' recebeu uma resposta",
16
+ "MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Seu comentário em <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> tem uma nova resposta </h2>{{parent.nick}}, você comentou: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> respondeu:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Você pode ver a resposta completa em <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>aqui</a>, e visite novamente <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>.</p><br/> </div></div>",
17
+ "MAIL_SUBJECT_ADMIN": "Novo comentário em {{site.name | safe}}",
18
+ "MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Seu artigo em <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> tem novos comentários </h2> <p><strong>{{self.nick}}</strong> disse:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Você pode ver a resposta completa em <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>aqui</a></p><br/> </div>"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "import data format not support!": "Формат файла не поддерживается",
3
+ "USER_EXIST": "Пользователь уже существует",
4
+ "USER_NOT_EXIST": "Пользователь не найден",
5
+ "USER_REGISTERED": "Пользователь уже зарегистрирован",
6
+ "TOKEN_EXPIRED": "Токен истёк",
7
+ "TWO_FACTOR_AUTH_ERROR_DETAIL": "Ошибка двухфакторной аутентификации",
8
+ "[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】Письмо подтверждения регистрации",
9
+ "Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "Пожалуйста, нажмите <a href=\"{{url}}\">{{url}}</a>, чтобы подтвердить регистрацию. Ссылка действительна 1 час. Если вы не регистрировались, проигнорируйте это письмо.",
10
+ "[{{name | safe}}] Reset Password": "【{{name | safe}}】Сброс пароля",
11
+ "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "Пожалуйста, нажмите <a href=\"{{url}}\">{{url}}</a>, чтобы войти и сменить пароль как можно скорее!",
12
+ "Duplicate Content": "Дублированное содержание",
13
+ "Comment too fast": "Слишком быстрое комментирование, пожалуйста, замедлитесь!",
14
+ "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "Сбой отправки письма подтверждения регистрации. Пожалуйста, {%- if isAdmin -%}проверьте конфигурацию почты{%- else -%}проверьте адрес эл. почты и свяжитесь с администратором{%- endif -%}.",
15
+ "MAIL_SUBJECT": "{{parent.nick | safe}},У вас есть ответ на комментарий в '{{site.name | safe}}'",
16
+ "MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Ваш комментарий на <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> получил новый ответ </h2>{{parent.nick}}, вы писали: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> ответил:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Просмотреть полный ответ можно по ссылке <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>здесь</a>, и посетите снова <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>.</p><br/> </div></div>",
17
+ "MAIL_SUBJECT_ADMIN": "Новый комментарий на {{site.name | safe}}",
18
+ "MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> В вашей статье на <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> новый комментарий </h2> <p><strong>{{self.nick}}</strong> написал:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Просмотреть полностью: <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>здесь</a></p><br/> </div>"
19
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "import data format not support!": "Định dạng tệp không được hỗ trợ",
3
+ "USER_EXIST": "Người dùng đã tồn tại",
4
+ "USER_NOT_EXIST": "Người dùng không tồn tại",
5
+ "USER_REGISTERED": "Người dùng đã đăng ký",
6
+ "TOKEN_EXPIRED": "Mã thông báo đã hết hạn",
7
+ "TWO_FACTOR_AUTH_ERROR_DETAIL": "Xác thực hai bước thất bại",
8
+ "[{{name | safe}}] Registration Confirm Mail": "【{{name | safe}}】 Thư xác nhận đăng ký",
9
+ "Please click <a href=\"{{url}}\">{{url}}<a/> to confirm registration, the link is valid for 1 hour. If you are not registering, please ignore this email.": "Vui lòng nhấp vào <a href=\"{{url}}\">{{url}}</a> để xác nhận đăng ký. Liên kết có hiệu lực trong 1 giờ. Nếu bạn không đăng ký, hãy bỏ qua email này.",
10
+ "[{{name | safe}}] Reset Password": "【{{name | safe}}】 Đặt lại mật khẩu",
11
+ "Please click <a href=\"{{url}}\">{{url}}</a> to login and change your password as soon as possible!": "Vui lòng nhấp vào <a href=\"{{url}}\">{{url}}</a> để đăng nhập và thay đổi mật khẩu càng sớm càng tốt!",
12
+ "Duplicate Content": "Nội dung trùng lặp đã được gửi",
13
+ "Comment too fast": "Bình luận quá nhanh, vui lòng chậm lại!",
14
+ "Registration confirm mail send failed, please {%- if isAdmin -%}check your mail configuration{%- else -%}check your email address and contact administrator{%- endif -%}.": "Gửi mail xác nhận đăng ký thất bại. Vui lòng {%- if isAdmin -%}kiểm tra cấu hình mail{%- else -%}kiểm tra địa chỉ email và liên hệ quản trị viên{%- endif -%}.",
15
+ "MAIL_SUBJECT": "{{parent.nick | safe}},Bình luận của bạn trên '{{site.name | safe}}' đã có phản hồi",
16
+ "MAIL_TEMPLATE": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Bình luận của bạn trên <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> đã có phản hồi </h2>{{parent.nick}}, bạn đã bình luận: <div style='padding:0 12px 0 12px;margin-top:18px'> <div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{parent.comment | safe}}</div><p><strong>{{self.nick}}</strong> trả lời:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Bạn có thể xem phản hồi đầy đủ tại <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>đây</a>, và ghé thăm lại <a style='text-decoration:none; color:#12addb' href='{{site.url}}' target='_blank'>{{site.name}}</a>.</p><br/> </div></div>",
17
+ "MAIL_SUBJECT_ADMIN": "Bình luận mới trên {{site.name | safe}}",
18
+ "MAIL_TEMPLATE_ADMIN": "<div style='border-top:2px solid #12ADDB;box-shadow:0 1px 3px #AAAAAA;line-height:180%;padding:0 15px 12px;margin:50px auto;font-size:12px;'> <h2 style='border-bottom:1px solid #DDD;font-size:14px;font-weight:normal;padding:13px 0 10px 8px;'> Bài viết của bạn trên <a style='text-decoration:none;color: #12ADDB;' href='{{site.url}}' target='_blank'>{{site.name}}</a> có bình luận mới </h2> <p><strong>{{self.nick}}</strong> trả lời:</p><div style='background-color: #f5f5f5;padding: 10px 15px;margin:18px 0;word-wrap:break-word;'>{{self.comment | safe}}</div><p>Bạn có thể xem phản hồi đầy đủ tại <a style='text-decoration:none; color:#12addb' href='{{site.postUrl}}' target='_blank'>đây</a></p><br/> </div>"
19
+ }
package/src/logic/base.js CHANGED
@@ -3,7 +3,7 @@ const qs = require('node:querystring');
3
3
 
4
4
  const jwt = require('jsonwebtoken');
5
5
 
6
- module.exports = class extends think.Logic {
6
+ module.exports = class BaseLogic extends think.Logic {
7
7
  constructor(...args) {
8
8
  super(...args);
9
9
  this.modelInstance = this.getModel('Users');
@@ -11,17 +11,18 @@ module.exports = class extends think.Logic {
11
11
  this.id = this.getId();
12
12
  }
13
13
 
14
+ // oxlint-disable-next-line max-statements
14
15
  async __before() {
15
16
  const referrer = this.ctx.referrer(true);
16
- let origin = this.ctx.origin;
17
+ let { origin } = this.ctx;
17
18
 
18
19
  if (origin) {
19
20
  try {
20
21
  const parsedOrigin = new URL(origin);
21
22
 
22
23
  origin = parsedOrigin.hostname;
23
- } catch (e) {
24
- console.error('Invalid origin format:', origin, e);
24
+ } catch (err) {
25
+ console.error('Invalid origin format:', origin, err);
25
26
  }
26
27
  }
27
28
 
@@ -39,9 +40,10 @@ module.exports = class extends think.Logic {
39
40
  // 'api.weibo.com',
40
41
  // 'graph.qq.com',
41
42
  );
42
- secureDomains = secureDomains.concat(
43
- this.ctx.state.oauthServices.map(({ origin }) => origin),
44
- );
43
+ secureDomains = [
44
+ ...secureDomains,
45
+ ...this.ctx.state.oauthServices.map(({ origin }) => origin),
46
+ ];
45
47
 
46
48
  // 转换可能的正则表达式字符串为正则表达式对象
47
49
  secureDomains = secureDomains
@@ -50,8 +52,8 @@ module.exports = class extends think.Logic {
50
52
  if (typeof domain === 'string' && domain.startsWith('/') && domain.endsWith('/')) {
51
53
  try {
52
54
  return new RegExp(domain.slice(1, -1)); // 去掉斜杠并创建 RegExp 对象
53
- } catch (e) {
54
- console.error('Invalid regex pattern in secureDomains:', domain, e);
55
+ } catch (err) {
56
+ console.error('Invalid regex pattern in secureDomains:', domain, err);
55
57
 
56
58
  return null;
57
59
  }
@@ -62,7 +64,7 @@ module.exports = class extends think.Logic {
62
64
  .filter(Boolean); // 过滤掉无效的正则表达式
63
65
 
64
66
  // 有 referrer 检查 referrer,没有则检查 origin
65
- const checking = referrer ? referrer : origin;
67
+ const checking = referrer || origin;
66
68
  const isSafe = secureDomains.some((domain) =>
67
69
  think.isFunction(domain.test) ? domain.test(checking) : domain === checking,
68
70
  );
@@ -84,8 +86,8 @@ module.exports = class extends think.Logic {
84
86
 
85
87
  try {
86
88
  userId = jwt.verify(token, think.config('jwtKey'));
87
- } catch (e) {
88
- think.logger.debug(e);
89
+ } catch (err) {
90
+ think.logger.debug(err);
89
91
  }
90
92
 
91
93
  if (think.isEmpty(userId) || !think.isString(userId)) {
@@ -93,7 +95,7 @@ module.exports = class extends think.Logic {
93
95
  }
94
96
 
95
97
  const user = await this.modelInstance.select(
96
- { objectId: userId },
98
+ { objectId: userId, type: ['!=', 'banned'] },
97
99
  {
98
100
  field: [
99
101
  'id',
@@ -113,19 +115,19 @@ module.exports = class extends think.Logic {
113
115
  return;
114
116
  }
115
117
 
116
- const userInfo = user[0];
118
+ const [userInfo] = user;
117
119
 
118
- let avatarUrl = userInfo.avatar
119
- ? userInfo.avatar
120
- : await think.service('avatar').stringify({
121
- mail: userInfo.email,
122
- nick: userInfo.display_name,
123
- link: userInfo.url,
124
- });
120
+ let avatarUrl =
121
+ userInfo.avatar ||
122
+ (await think.service('avatar').stringify({
123
+ mail: userInfo.email,
124
+ nick: userInfo.display_name,
125
+ link: userInfo.url,
126
+ }));
125
127
  const { avatarProxy } = think.config();
126
128
 
127
129
  if (avatarProxy) {
128
- avatarUrl = avatarProxy + '?url=' + encodeURIComponent(avatarUrl);
130
+ avatarUrl = `${avatarProxy}?url=${encodeURIComponent(avatarUrl)}`;
129
131
  }
130
132
  userInfo.avatar = avatarUrl;
131
133
  this.ctx.state.userInfo = userInfo;
@@ -136,7 +138,7 @@ module.exports = class extends think.Logic {
136
138
  const filename = this.__filename || __filename;
137
139
  const last = filename.lastIndexOf(path.sep);
138
140
 
139
- return filename.substr(last + 1, filename.length - last - 4);
141
+ return filename.slice(last + 1, filename.length - last - 4);
140
142
  }
141
143
 
142
144
  getId() {
@@ -193,7 +195,7 @@ module.exports = class extends think.Logic {
193
195
  remoteip: this.ctx.ip,
194
196
  });
195
197
 
196
- const requestUrl = method === 'GET' ? api + '?' + query : api;
198
+ const requestUrl = method === 'GET' ? `${api}?${query}` : api;
197
199
  const options =
198
200
  method === 'GET'
199
201
  ? {}
@@ -1,6 +1,6 @@
1
1
  const Base = require('./base.js');
2
2
 
3
- module.exports = class extends Base {
3
+ module.exports = class CommentLogic extends Base {
4
4
  checkAdmin() {
5
5
  const { userInfo } = this.ctx.state;
6
6
 
@@ -117,7 +117,7 @@ module.exports = class extends Base {
117
117
  }
118
118
 
119
119
  switch (type) {
120
- case 'recent':
120
+ case 'recent': {
121
121
  this.rules = {
122
122
  count: {
123
123
  int: { max: 50 },
@@ -125,14 +125,16 @@ module.exports = class extends Base {
125
125
  },
126
126
  };
127
127
  break;
128
+ }
128
129
 
129
- case 'count':
130
+ case 'count': {
130
131
  this.rules = {
131
132
  url: {
132
133
  array: true,
133
134
  },
134
135
  };
135
136
  break;
137
+ }
136
138
 
137
139
  case 'list': {
138
140
  const { userInfo } = this.ctx.state;
@@ -153,7 +155,7 @@ module.exports = class extends Base {
153
155
  break;
154
156
  }
155
157
 
156
- default:
158
+ default: {
157
159
  this.rules = {
158
160
  path: {
159
161
  string: true,
@@ -173,6 +175,7 @@ module.exports = class extends Base {
173
175
  },
174
176
  };
175
177
  break;
178
+ }
176
179
  }
177
180
  }
178
181
 
package/src/logic/db.js CHANGED
@@ -1,6 +1,6 @@
1
1
  const Base = require('./base.js');
2
2
 
3
- module.exports = class extends Base {
3
+ module.exports = class DatabaseLogic extends Base {
4
4
  async __before(...args) {
5
5
  await super.__before(...args);
6
6
 
@@ -34,8 +34,8 @@ module.exports = class extends Base {
34
34
  * @apiSuccess (200) {Number} errno 0
35
35
  * @apiSuccess (200) {String} errmsg return error message if error
36
36
  */
37
- postAction() {
38
- return this.useCaptchaCheck();
37
+ async postAction() {
38
+ await this.useCaptchaCheck();
39
39
  }
40
40
 
41
41
  /**
package/src/logic/user.js CHANGED
@@ -121,4 +121,29 @@ module.exports = class extends Base {
121
121
  },
122
122
  };
123
123
  }
124
+
125
+ /**
126
+ * @api {DELETE} /api/user delete user account when user is in verify status, or forbid user to login when user is other status.
127
+ * @apiGroup User
128
+ * @apiVersion 0.0.1
129
+ *
130
+ * @apiParam {String} id user id
131
+ * @apiParam {String} lang language
132
+ *
133
+ * @apiSuccess (200) {Number} errno 0
134
+ * @apiSuccess (200) {String} errmsg return error message if error
135
+ */
136
+ deleteAction() {
137
+ // you need to be logged in to delete users
138
+ const { userInfo } = this.ctx.state;
139
+
140
+ if (think.isEmpty(userInfo)) {
141
+ return this.fail();
142
+ }
143
+
144
+ // you must be an administrator to delete other users and cannot delete yourself
145
+ if (userInfo.type !== 'administrator' || this.id === userInfo.objectId) {
146
+ return this.fail();
147
+ }
148
+ }
124
149
  };
@@ -1,3 +1,4 @@
1
+ // oxlint-disable-next-line func-names
1
2
  module.exports = function () {
2
3
  return (ctx) => {
3
4
  ctx.type = 'html';
@@ -3,7 +3,7 @@ const compose = require('koa-compose');
3
3
  module.exports = () => async (ctx, next) => {
4
4
  const middlewares = think.getPluginMiddlewares();
5
5
 
6
- if (!think.isArray(middlewares) || !middlewares.length) {
6
+ if (!think.isArray(middlewares) || middlewares.length === 0) {
7
7
  return next();
8
8
  }
9
9
 
@@ -2,6 +2,7 @@ const Akismet = require('akismet');
2
2
 
3
3
  const DEFAULT_KEY = '70542d86693e';
4
4
 
5
+ // oxlint-disable-next-line func-names
5
6
  module.exports = function (comment, blog) {
6
7
  let { AKISMET_KEY, SITE_URL } = process.env;
7
8
 
@@ -13,14 +14,18 @@ module.exports = function (comment, blog) {
13
14
  return Promise.resolve(false);
14
15
  }
15
16
 
17
+ // oxlint-disable-next-line func-names
16
18
  return new Promise(function (resolve, reject) {
17
19
  const akismet = Akismet.client({ blog, apiKey: AKISMET_KEY });
18
20
 
21
+ // oxlint-disable-next-line func-names
19
22
  akismet.verifyKey(function (err, verifyKey) {
20
23
  if (err) {
21
- return reject(err);
24
+ reject(err);
25
+ return;
22
26
  } else if (!verifyKey) {
23
- return reject(new Error('Akismet API_KEY verify failed!'));
27
+ reject(new Error('Akismet API_KEY verify failed!'));
28
+ return;
24
29
  }
25
30
 
26
31
  akismet.checkComment(
@@ -30,9 +35,11 @@ module.exports = function (comment, blog) {
30
35
  comment_author: comment.nick,
31
36
  comment_content: comment.comment,
32
37
  },
38
+ // oxlint-disable-next-line func-names
33
39
  function (err, spam) {
34
40
  if (err) {
35
- return reject(err);
41
+ reject(err);
42
+ return;
36
43
  }
37
44
  resolve(spam);
38
45
  },
@@ -1,4 +1,4 @@
1
- const crypto = require('crypto');
1
+ const crypto = require('node:crypto');
2
2
 
3
3
  const nunjucks = require('nunjucks');
4
4
  const helper = require('think-helper');
@@ -7,7 +7,7 @@ rawLoadLanguages.silent = true;
7
7
  const loadLanguages = (languages = []) => {
8
8
  const langsToLoad = languages.filter((item) => !prism.languages[item]);
9
9
 
10
- if (langsToLoad.length) {
10
+ if (langsToLoad.length > 0) {
11
11
  rawLoadLanguages(langsToLoad);
12
12
  }
13
13
  };
@@ -1,10 +1,10 @@
1
1
  const escapeHtml = (unsafeHTML) =>
2
2
  unsafeHTML
3
- .replace(/&/gu, '&amp;')
4
- .replace(/</gu, '&lt;')
5
- .replace(/>/gu, '&gt;')
6
- .replace(/"/gu, '&quot;')
7
- .replace(/'/gu, '&#039;');
3
+ .replaceAll('&', '&amp;')
4
+ .replaceAll('<', '&lt;')
5
+ .replaceAll('>', '&gt;')
6
+ .replaceAll('"', '&quot;')
7
+ .replaceAll("'", '&#039;');
8
8
 
9
9
  module.exports = {
10
10
  escapeHtml,
@@ -28,16 +28,11 @@ DOMPurify.addHook('afterSanitizeAttributes', (node) => {
28
28
  });
29
29
 
30
30
  const sanitize = (content) =>
31
- DOMPurify.sanitize(
32
- content,
33
- Object.assign(
34
- {
35
- FORBID_TAGS: ['form', 'input', 'style'],
36
- FORBID_ATTR: ['autoplay', 'style'],
37
- },
38
- think.config('domPurify') || {},
39
- ),
40
- );
31
+ DOMPurify.sanitize(content, {
32
+ FORBID_TAGS: ['form', 'input', 'style'],
33
+ FORBID_ATTR: ['autoplay', 'style'],
34
+ ...think.config('domPurify'),
35
+ });
41
36
 
42
37
  module.exports = {
43
38
  sanitize,