diff --git a/.github/workflows/ok-to-test.yml b/.github/workflows/ok-to-test.yml new file mode 100644 index 000000000..87af3a6ba --- /dev/null +++ b/.github/workflows/ok-to-test.yml @@ -0,0 +1,36 @@ +# If someone with write access comments "/ok-to-test" on a pull request, emit a repository_dispatch event +name: Ok To Test + +on: + issue_comment: + types: [created] + +jobs: + ok-to-test: + runs-on: ubuntu-latest + # Only run for PRs, not issue comments + if: ${{ github.event.issue.pull_request }} + steps: + # Generate a GitHub App installation access token from an App ID and private key + # To create a new GitHub App: + # https://developer.github.com/apps/building-github-apps/creating-a-github-app/ + # See app.yml for an example app manifest + - name: Generate token + id: generate_token + uses: tibdex/github-app-token@v1 + with: + app_id: ${{ secrets.DEPLOYBOT_APP_ID }} + private_key: ${{ secrets.DEPLOYBOT_PRIVATE_KEY }} + + - name: Slash Command Dispatch + uses: peter-evans/slash-command-dispatch@v1 + env: + TOKEN: ${{ steps.generate_token.outputs.token }} + with: + token: ${{ env.TOKEN }} # GitHub App installation access token + # token: ${{ secrets.PERSONAL_ACCESS_TOKEN }} # PAT or OAuth token will also work + reaction-token: ${{ secrets.GITHUB_TOKEN }} + issue-type: pull-request + commands: deploy + named-args: true + permission: write diff --git a/.github/workflows/pr-preview-deploy.yml b/.github/workflows/pr-preview-deploy.yml new file mode 100644 index 000000000..fd43bce9e --- /dev/null +++ b/.github/workflows/pr-preview-deploy.yml @@ -0,0 +1,95 @@ +# Run secret-dependent integration tests only after /deploy approval +on: + pull_request: + types: [opened, reopened, synchronize] + repository_dispatch: + types: [deploy-command] + +name: Deploy preview environment + +jobs: + # Repo owner has commented /deploy on a (fork-based) pull request + deploy-preview-environment: + runs-on: ubuntu-latest + if: + github.event_name == 'repository_dispatch' && + github.event.client_payload.slash_command.sha != '' && + contains(github.event.client_payload.pull_request.head.sha, github.event.client_payload.slash_command.sha) + steps: + - uses: actions/github-script@v5 + id: check-id + env: + number: ${{ github.event.client_payload.pull_request.number }} + job: ${{ github.job }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + result-encoding: string + script: | + const { data: pull } = await github.rest.pulls.get({ + ...context.repo, + pull_number: process.env.number + }); + const ref = pull.head.sha; + + const { data: checks } = await github.rest.checks.listForRef({ + ...context.repo, + ref + }); + + const check = checks.check_runs.filter(c => c.name === process.env.job); + + return check[0].id; + + - uses: actions/github-script@v5 + env: + check_id: ${{ steps.check-id.outputs.result }} + details_url: ${{ github.server_url }}/${{ github.repository }}/runs/${{ github.run_id }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.checks.update({ + ...context.repo, + check_run_id: process.env.check_id, + status: 'in_progress', + details_url: process.env.details_url + }); + + # Check out merge commit + - name: Fork based /deploy checkout + uses: actions/checkout@v2 + with: + ref: 'refs/pull/${{ github.event.client_payload.pull_request.number }}/merge' + + # + - name: Context + uses: okteto/context@latest + with: + token: ${{ secrets.OKTETO_TOKEN }} + + - name: Deploy preview environment + uses: ikuradon/deploy-preview@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + name: pr-${{ github.event.client_payload.pull_request.number }}-syuilo + timeout: 15m + + # Update check run called "integration-fork" + - uses: actions/github-script@v5 + id: update-check-run + if: ${{ always() }} + env: + # Conveniently, job.status maps to https://developer.github.com/v3/checks/runs/#update-a-check-run + conclusion: ${{ job.status }} + check_id: ${{ steps.check-id.outputs.result }} + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { data: result } = await github.rest.checks.update({ + ...context.repo, + check_run_id: process.env.check_id, + status: 'completed', + conclusion: process.env.conclusion + }); + + return result; diff --git a/.github/workflows/pr-preview-destroy.yml b/.github/workflows/pr-preview-destroy.yml new file mode 100644 index 000000000..c14c3db5c --- /dev/null +++ b/.github/workflows/pr-preview-destroy.yml @@ -0,0 +1,21 @@ +# file: .github/workflows/preview-closed.yaml +on: + pull_request: + types: + - closed + +name: Destroy preview environment + +jobs: + destroy-preview-environment: + runs-on: ubuntu-latest + steps: + - name: Context + uses: okteto/context@latest + with: + token: ${{ secrets.OKTETO_TOKEN }} + + - name: Destroy preview environment + uses: okteto/destroy-preview@latest + with: + name: pr-${{ github.event.number }}-syuilo diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d44717fc0..297f93b61 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -98,7 +98,7 @@ jobs: - name: ALSA Env run: echo -e 'pcm.!default {\n type hw\n card 0\n}\n\nctl.!default {\n type hw\n card 0\n}' > ~/.asoundrc - name: Cypress run - uses: cypress-io/github-action@v2 + uses: cypress-io/github-action@v4 with: install: false start: yarn start:test diff --git a/.node-version b/.node-version index c9b6b29e0..7fd023741 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v16.0.0 +v16.15.0 diff --git a/okteto.yml b/.okteto/okteto-pipeline.yml similarity index 100% rename from okteto.yml rename to .okteto/okteto-pipeline.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index b2e3d4d1f..7869f68b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,37 @@ You should also include the user name that made the change. --> ## 12.x.x (unreleased) + +### Improvements +- Server: Add rate limit to i/notifications @tamaina +- Client: Improve files page of control panel @syuilo + +### Bugfixes +- Server: Fix GenerateVideoThumbnail failed @mei23 +- Server: Ensure temp directory cleanup @Johann150 + +## 12.111.1 (2022/06/13) + +### Bugfixes +- some fixes of multiple notification read @tamaina +- some GenerateVideoThumbnail failed @Johann150 +- Client: デッキでウィジェットの情報が保存されない問題を修正 @syuilo +- Client: ギャラリーの投稿を開こうとすると編集画面が表示される @futchitwo + +## 12.111.0 (2022/06/11) +### Note +- Node.js 16.15.0 or later is required + ### Improvements - Supports Unicode Emoji 14.0 @mei23 - プッシュ通知を複数アカウント対応に #7667 @tamaina - プッシュ通知にクリックやactionを設定 #7667 @tamaina - ドライブに画像ファイルをアップロードするときオリジナル画像を破棄してwebpublicのみ保持するオプション @tamaina - Server: always remove completed tasks of job queue @Johann150 +- Client: アバターの設定で画像をクロップできるように @syuilo - Client: make emoji stand out more on reaction button @Johann150 - Client: display URL of QR code for TOTP registration @tamaina +- Client: render quote renote CWs as MFM @pixeldesu - API: notifications/readは配列でも受け付けるように #7667 @tamaina - API: ユーザー検索で、クエリがusernameの条件を満たす場合はusernameもLIKE検索するように @tamaina - MFM: Allow speed changes in all animated MFMs @Johann150 @@ -31,16 +54,14 @@ You should also include the user name that made the change. ### Bugfixes - Server: keep file order of note attachement @Johann150 -- Server: fix caching @Johann150 -- Server: await promises when following or unfollowing users @Johann150 - Server: fix missing foreign key for reports leading to reports page being unusable @Johann150 - Server: fix internal in-memory caching @Johann150 -- Server: use correct order of attachments on notes @Johann150 - Server: prevent crash when processing certain PNGs @syuilo - Server: Fix unable to generate video thumbnails @mei23 - Server: Fix `Cannot find module` issue @mei23 - Federation: Add rel attribute to host-meta @mei23 - Federation: add id for activitypub follows @Johann150 +- Federation: use `source` instead of `_misskey_content` @Johann150 - Federation: ensure resolver does not fetch local resources via HTTP(S) @Johann150 - Federation: correctly render empty note text @Johann150 - Federation: Fix quote renotes containing no text being federated correctly @Johann150 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a2e8319f4..c108bc8df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -66,6 +66,13 @@ Be willing to comment on the good points and not just the things you want fixed - Are there any omissions or gaps? - Does it check for anomalies? +## Deploy +The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment. +``` +/deploy sha= +``` +An actual domain will be assigned so you can test the federation. + ## Merge For now, basically only @syuilo has the authority to merge PRs into develop because he is most familiar with the codebase. However, minor fixes, refactoring, and urgent changes may be merged at the discretion of a contributor. @@ -77,9 +84,9 @@ However, minor fixes, refactoring, and urgent changes may be merged at the discr - Into `master` from `develop` branch. - The title must be in the format `Release: x.y.z`. - `x.y.z` is the new version you are trying to release. - - Assign about 2~3 reviewers. -3. The release PR is approved, merge it. -4. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases) +3. Deploy and perform a simple QA check. Also verify that the tests passed. +4. Merge it. +5. Create a [release of GitHub](https://github.com/misskey-dev/misskey/releases) - The target branch must be `master` - The tag name must be the version diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 000000000..e390c41a5 --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + e2e: { + // We've imported your old cypress plugins here. + // You may want to clean this up later by importing these. + setupNodeEvents(on, config) { + return require('./cypress/plugins/index.js')(on, config) + }, + baseUrl: 'http://localhost:61812', + }, +}) diff --git a/cypress.json b/cypress.json deleted file mode 100644 index e858e480b..000000000 --- a/cypress.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "baseUrl": "http://localhost:61812" -} diff --git a/cypress/integration/basic.js b/cypress/e2e/basic.cy.js similarity index 100% rename from cypress/integration/basic.js rename to cypress/e2e/basic.cy.js diff --git a/cypress/integration/widgets.js b/cypress/e2e/widgets.cy.js similarity index 100% rename from cypress/integration/widgets.js rename to cypress/e2e/widgets.cy.js diff --git a/cypress/support/index.js b/cypress/support/e2e.js similarity index 100% rename from cypress/support/index.js rename to cypress/support/e2e.js diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index efad7c954..3bd8f1e50 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1007,7 +1007,6 @@ _sfx: antenna: "الهوائيات" channel: "إشعارات القنات" _ago: - unknown: "مجهول" future: "المستقبَل" justNow: "اللحظة" secondsAgo: "منذ {n} ثوانٍ" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index bcecb0625..d7753b6dc 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -831,11 +831,18 @@ themeColor: "থিমের রং" size: "আকার" numberOfColumn: "কলামের সংখ্যা" searchByGoogle: "গুগল" +instanceDefaultLightTheme: "ইন্সট্যান্সের ডিফল্ট লাইট থিম" +instanceDefaultDarkTheme: "ইন্সট্যান্সের ডিফল্ট ডার্ক থিম" +instanceDefaultThemeDescription: "অবজেক্ট ফরম্যাটে থিম কোড লিখুন" +mutePeriod: "মিউটের সময়কাল" indefinitely: "অনির্দিষ্ট" tenMinutes: "১০ মিনিট" oneHour: "১ ঘণ্টা" oneDay: "একদিন" oneWeek: "এক সপ্তাহ" +reflectMayTakeTime: "এটির কাজ দেখা যেতে কিছুটা সময় লাগতে পারে।" +failedToFetchAccountInformation: "অ্যাকাউন্টের তথ্য উদ্ধার করা যায়নি" +rateLimitExceeded: "রেট লিমিট ছাড়িয়ে গেছে " _emailUnavailable: used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" @@ -1081,7 +1088,6 @@ _sfx: antenna: "অ্যান্টেনাগুলি" channel: "চ্যানেলের বিজ্ঞপ্তি" _ago: - unknown: "অজানা" future: "ভবিষ্যৎ" justNow: "এইমাত্র" secondsAgo: "{n} সেকেন্ড আগে" @@ -1125,6 +1131,7 @@ _2fa: registerKey: "সিকিউরিটি কী নিবন্ধন করুন" step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷" step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।" + step2Url: "ডেস্কটপ অ্যাপে, নিম্নলিখিত URL লিখুন:" step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।" step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।" securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷" @@ -1608,6 +1615,8 @@ _notification: youReceivedFollowRequest: "অনুসরণ করার জন্য অনুরোধ পাওয়া গেছে" yourFollowRequestAccepted: "আপনার অনুসরণ করার অনুরোধ গৃহীত হয়েছে" youWereInvitedToGroup: "আপনি একটি গ্রুপে আমন্ত্রিত হয়েছেন" + pollEnded: "পোলের ফলাফল দেখা যাবে" + emptyPushNotificationMessage: "আপডেট করা পুশ বিজ্ঞপ্তি" _types: all: "সকল" follow: "অনুসরণ করা হচ্ছে" @@ -1617,11 +1626,13 @@ _notification: quote: "উদ্ধৃতি" reaction: "প্রতিক্রিয়া" pollVote: "পোলে ভোট আছে" + pollEnded: "পোল শেষ" receiveFollowRequest: "প্রাপ্ত অনুসরণের অনুরোধসমূহ" followRequestAccepted: "গৃহীত অনুসরণের অনুরোধসমূহ" groupInvited: "গ্রুপের আমন্ত্রনসমূহ" app: "লিঙ্ক করা অ্যাপ থেকে বিজ্ঞপ্তি" _actions: + followBack: "ফলো ব্যাক করেছে" reply: "জবাব" renote: "রিনোট" _deck: diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index 577fbca2a..74eab3603 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -134,6 +134,8 @@ _theme: _sfx: note: "Notes" notification: "Notificacions" +_2fa: + step2Url: "També pots inserir aquest enllaç i utilitzes una aplicació d'escriptori:" _widgets: notifications: "Notificacions" timeline: "Línia de temps" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index a3ff6c0a6..5dfce2800 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -842,6 +842,9 @@ oneDay: "Einen Tag" oneWeek: "Eine Woche" reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt." failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden" +rateLimitExceeded: "Versuchsanzahl überschritten" +cropImage: "Bild zuschneiden" +cropImageAsk: "Möchtest du das Bild zuschneiden?" _emailUnavailable: used: "Diese Email-Adresse wird bereits verwendet" format: "Das Format dieser Email-Adresse ist ungültig" @@ -1087,7 +1090,6 @@ _sfx: antenna: "Antennen" channel: "Kanalbenachrichtigung" _ago: - unknown: "Unbekannt" future: "Zukunft" justNow: "Gerade eben" secondsAgo: "vor {n} Sekunde(n)" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "Neuen Sicherheitsschlüssel registrieren" step1: "Installiere zuerst eine Authentifizierungsapp (z.B. {a} oder {b}) auf deinem Gerät." step2: "Dann, scanne den angezeigten QR-Code mit deinem Gerät." + step2Url: "Nutzt du ein Desktopprogramm kannst du alternativ diese URL eingeben:" step3: "Gib zum Abschluss den Token ein, der von deiner App angezeigt wird." step4: "Alle folgenden Anmeldungsversuche werden ab sofort die Eingabe eines solchen Tokens benötigen." securityKeyInfo: "Du kannst neben Fingerabdruck- oder PIN-Authentifizierung auf deinem Gerät auch Anmeldung mit Hilfe eines FIDO2-kompatiblen Hardware-Sicherheitsschlüssels einrichten." diff --git a/locales/en-US.yml b/locales/en-US.yml index a7d69d6d4..8bfea26b0 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -842,6 +842,9 @@ oneDay: "One day" oneWeek: "One week" reflectMayTakeTime: "It may take some time for this to be reflected." failedToFetchAccountInformation: "Could not fetch account information" +rateLimitExceeded: "Rate limit exceeded" +cropImage: "Crop image" +cropImageAsk: "Do you want to crop this image?" _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" @@ -1087,7 +1090,6 @@ _sfx: antenna: "Antennas" channel: "Channel notifications" _ago: - unknown: "Unknown" future: "Future" justNow: "Just now" secondsAgo: "{n} second(s) ago" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "Register a security key" step1: "First, install an authentication app (such as {a} or {b}) on your device." step2: "Then, scan the QR code displayed on this screen." + step2Url: "You can also enter this URL if you're using a desktop program:" step3: "Enter the token provided by your app to finish setup." step4: "From now on, any future login attempts will ask for such a login token." securityKeyInfo: "Besides fingerprint or PIN authentication, you can also setup authentication via hardware security keys that support FIDO2 to further secure your account." diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 8fff5ca4d..6c10942b4 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -989,7 +989,6 @@ _sfx: antenna: "Antena receptora" channel: "Notificaciones del canal" _ago: - unknown: "Desconocido" future: "Futuro" justNow: "Recién ahora" secondsAgo: "Hace {n} segundos" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 3b5dc1b06..7e225c299 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -804,7 +804,7 @@ manageAccounts: "Gérer les comptes" makeReactionsPublic: "Rendre les réactions publiques" makeReactionsPublicDescription: "Ceci rendra la liste de toutes vos réactions données publique." classic: "Classique" -muteThread: "Mettre ce thread en sourdine" +muteThread: "Masquer cette discussion" unmuteThread: "Ne plus masquer le fil" ffVisibility: "Visibilité des abonnés/abonnements" ffVisibilityDescription: "Permet de configurer qui peut voir les personnes que tu suis et les personnes qui te suivent." @@ -1075,7 +1075,6 @@ _sfx: antenna: "Réception de l’antenne" channel: "Notifications de canal" _ago: - unknown: "Inconnu" future: "Futur" justNow: "à l’instant" secondsAgo: "Il y a {n}s" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index a97ac819a..39e2c1f66 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -842,6 +842,9 @@ oneDay: "1 Hari" oneWeek: "1 Bulan" reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan." failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun" +rateLimitExceeded: "Batas sudah terlampaui" +cropImage: "potong gambar" +cropImageAsk: "Ingin memotong gambar?" _emailUnavailable: used: "Alamat surel ini telah digunakan" format: "Format tidak valid." @@ -1087,7 +1090,6 @@ _sfx: antenna: "Penerimaan Antenna" channel: "Pemberitahuan saluran" _ago: - unknown: "Tidak diketahui" future: "Masa depan" justNow: "Baru saja" secondsAgo: "{n} detik lalu" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "Daftarkan kunci keamanan baru" step1: "Pertama, pasang aplikasi otentikasi (seperti {a} atau {b}) di perangkat kamu." step2: "Lalu, pindai kode QR yang ada di layar." + step2Url: "Di aplikasi desktop, masukkan URL berikut:" step3: "Masukkan token yang telah disediakan oleh aplikasimu untuk menyelesaikan pemasangan." step4: "Mulai sekarang, upaya login apapun akan meminta token login dari aplikasi otentikasi kamu." securityKeyInfo: "Kamu dapat memasang otentikasi WebAuthN untuk mengamankan proses login lebih lanjut dengan tidak hanya perangkat keras kunci keamanan yang mendukung FIDO2, namun juga sidik jari atau otentikasi PIN pada perangkatmu." diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 4d1035669..8584ed6a8 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -10,7 +10,7 @@ password: "Password" forgotPassword: "Hai dimenticato la tua password?" fetchingAsApObject: "Recuperando dal Fediverso..." ok: "OK" -gotIt: "Capito!" +gotIt: "Ho capito" cancel: "Annulla" enterUsername: "Inserisci un nome utente" renotedBy: "Rinotato da {user}" @@ -767,6 +767,7 @@ customCss: "CSS personalizzato" global: "Federata" squareAvatars: "Mostra l'immagine del profilo come quadrato" sent: "Inviare" +received: "Ricevuto" searchResult: "Risultati della Ricerca" hashtags: "Hashtag" troubleshooting: "Risoluzione problemi" @@ -804,6 +805,10 @@ welcomeBackWithName: "Bentornato/a, {name}" clickToFinishEmailVerification: "Fai click su [{ok}] per completare la verifica dell'indirizzo email." searchByGoogle: "Cerca" indefinitely: "Non scade" +tenMinutes: "10 minuti" +oneHour: "1 ora" +oneDay: "1 giorno" +oneWeek: "1 settimana" _emailUnavailable: used: "Email già in uso" format: "Formato email non valido" @@ -999,7 +1004,6 @@ _sfx: antenna: "Ricezione dell'antenna" channel: "Notifiche di canale" _ago: - unknown: "Sconosciuto" future: "Futuro" justNow: "Ora" secondsAgo: "{n}s fa" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9cd1d1eed..43ab7f2d6 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -843,6 +843,8 @@ oneWeek: "1週間" reflectMayTakeTime: "反映されるまで時間がかかる場合があります。" failedToFetchAccountInformation: "アカウント情報の取得に失敗しました" rateLimitExceeded: "レート制限を超えました" +cropImage: "画像のクロップ" +cropImageAsk: "画像をクロップしますか?" _emailUnavailable: used: "既に使用されています" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index fd6945160..5458152dd 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -799,7 +799,6 @@ _sfx: notification: "通知" chat: "チャット" _ago: - unknown: "わからん" future: "未来" justNow: "たった今" secondsAgo: "{n}秒前" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 74d06185d..e0a839a2c 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -842,6 +842,9 @@ oneDay: "1일" oneWeek: "일주일" reflectMayTakeTime: "반영되기까지 시간이 걸릴 수 있습니다." failedToFetchAccountInformation: "계정 정보를 가져오지 못했습니다" +rateLimitExceeded: "요청 제한 횟수를 초과하였습니다" +cropImage: "이미지 자르기" +cropImageAsk: "이미지를 자르시겠습니까?" _emailUnavailable: used: "이 메일 주소는 사용중입니다" format: "형식이 올바르지 않습니다" @@ -1087,7 +1090,6 @@ _sfx: antenna: "안테나 수신" channel: "채널 알림" _ago: - unknown: "알 수 없음" future: "미래" justNow: "방금 전" secondsAgo: "{n}초 전" @@ -1131,6 +1133,7 @@ _2fa: registerKey: "키를 등록" step1: "먼저, {a}나 {b}등의 인증 앱을 사용 중인 디바이스에 설치합니다." step2: "그 후, 표시되어 있는 QR코드를 앱으로 스캔합니다." + step2Url: "데스크톱 앱에서는 다음 URL을 입력하세요:" step3: "앱에 표시된 토큰을 입력하시면 완료됩니다." step4: "다음 로그인부터는 토큰을 입력해야 합니다." securityKeyInfo: "FIDO2를 지원하는 하드웨어 보안 키 혹은 디바이스의 지문인식이나 화면잠금 PIN을 이용해서 로그인하도록 설정할 수 있습니다." diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index d0a83eb6a..0ded57394 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -303,6 +303,8 @@ muteThread: "Discussies dempen " unmuteThread: "Dempen van discussie ongedaan maken" hide: "Verbergen" searchByGoogle: "Zoeken" +cropImage: "Afbeelding bijsnijden" +cropImageAsk: "Bijsnijdengevraagd" _email: _follow: title: "volgde jou" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 7fabab3b4..fa1dad217 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -946,7 +946,6 @@ _sfx: chatBg: "Rozmowy (tło)" channel: "Powiadomienia kanału" _ago: - unknown: "Nieznane" future: "W przyszłości" justNow: "Przed chwilą" secondsAgo: "{n} sek. temu" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index c1afa5b56..0dc15a27b 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -81,18 +81,67 @@ somethingHappened: "Ocorreu um erro" retry: "Tentar novamente" pageLoadError: "Ocorreu um erro ao carregar a página." pageLoadErrorDescription: "Isto é normalmente causado por erros de rede ou pela cache do browser. Experimenta limpar a cache e tenta novamente após algum tempo." +serverIsDead: "O servidor não está respondendo. Por favor espere um pouco e tente novamente." +youShouldUpgradeClient: "Para visualizar essa página, por favor recarregue-a para atualizar seu cliente." +enterListName: "Insira um nome para a lista" +privacy: "Privacidade" +makeFollowManuallyApprove: "Pedidos de seguimento precisam ser aprovados" +defaultNoteVisibility: "Visibilidade padrão" follow: "Seguindo" +followRequest: "Mandar pedido de seguimento" +followRequests: "Pedidos de seguimento" +unfollow: "Deixar de seguir" +followRequestPending: "Pedido de seguimento pendente" enterEmoji: "Inserir emoji" renote: "Repostar" renoted: "Repostado" cantRenote: "Não pode repostar" cantReRenote: "Não pode repostar este repost" +quote: "Citar" pinnedNote: "Post fixado" pinned: "Afixar no perfil" +you: "Você" +clickToShow: "Clique para ver" sensitive: "Conteúdo sensível" +add: "Adicionar" +reaction: "Reações" +reactionSetting: "Quais reações a mostrar no selecionador de reações" +rememberNoteVisibility: "Lembrar das configurações de visibilidade de notas" +attachCancel: "Remover anexo" +markAsSensitive: "Marcar como sensível" +unmarkAsSensitive: "Desmarcar como sensível" +enterFileName: "Digite o nome do ficheiro" mute: "Silenciar" unmute: "Dessilenciar" +block: "Bloquear" +unblock: "Desbloquear" +suspend: "Suspender" +unsuspend: "Cancelar suspensão" +blockConfirm: "Tem certeza que gostaria de bloquear essa conta?" +unblockConfirm: "Tem certeza que gostaria de desbloquear essa conta?" +suspendConfirm: "Tem certeza que gostaria de suspender essa conta?" +unsuspendConfirm: "Tem certeza que gostaria de cancelar a suspensão dessa conta?" +selectList: "Escolhe uma lista" +selectAntenna: "Escolhe uma antena" +selectWidget: "Escolhe um widget" +editWidgets: "Editar widgets" +editWidgetsExit: "Pronto" +customEmojis: "Emoji personalizado" +emoji: "Emoji" +emojis: "Emojis" +emojiName: "Nome do Emoji" +emojiUrl: "URL do Emoji" +addEmoji: "Adicionar um Emoji" settingGuide: "Guia de configuração" +flagAsBot: "Marcar conta como robô" +flagAsCat: "Marcar conta como gato" +flagAsCatDescription: "Ative essa opção para marcar essa conta como gato." +flagShowTimelineReplies: "Mostrar respostas na linha de tempo" +general: "Geral" +wallpaper: "Papel de parede" +searchWith: "Buscar: {q}" +youHaveNoLists: "Não tem nenhuma lista" +followConfirm: "Tem certeza que quer deixar de seguir {name}?" instances: "Instância" registeredAt: "Registrado em" perHour: "por hora" @@ -107,6 +156,8 @@ nsfw: "Conteúdo sensível" monthX: "mês de {month}" pinnedNotes: "Post fixado" userList: "Listas" +none: "Nenhum" +output: "Resultado" smtpUser: "Nome de usuário" smtpPass: "Senha" user: "Usuários" @@ -116,6 +167,8 @@ _email: title: "Você tem um novo seguidor" _mfm: mention: "Menção" + quote: "Citar" + emoji: "Emoji personalizado" search: "Pesquisar" _theme: keys: @@ -136,38 +189,229 @@ _profile: _exportOrImport: followingList: "Seguindo" muteList: "Silenciar" + blockingList: "Bloquear" userLists: "Listas" _pages: + blocks: + _button: + _action: + _pushEvent: + event: "Nome do evento" + message: "Mostrar mensagem quando ativado" + variable: "Variável a mandar" + no-variable: "Nenhum" + callAiScript: "Invocar AiScript" + _callAiScript: + functionName: "Nome da função" + radioButton: "Escolha" + _radioButton: + values: "Lista de escolhas separadas por quebras de texto" script: categories: + logical: "Operação lógica" + operation: "Cálculos" + comparison: "Comparação" list: "Listas" blocks: + _strReplace: + arg2: "Texto que irá ser substituído" + arg3: "Substituir com" + strReverse: "Virar texto" + join: "Sequência de texto" _join: arg1: "Listas" + arg2: "Separador" + add: "Somar" + _add: + arg1: "A" + arg2: "B" + subtract: "Subtrair" + _subtract: + arg1: "A" + arg2: "B" + multiply: "Multiplicar" + _multiply: + arg1: "A" + arg2: "B" + divide: "Dividir" + _divide: + arg1: "A" + arg2: "B" + mod: "O resto de" + _mod: + arg1: "A" + arg2: "B" + round: "Arredondar decimal" + _round: + arg1: "Numérico" + eq: "A e B são iguais" + _eq: + arg1: "A" + arg2: "B" + notEq: "A e B são diferentes" + _notEq: + arg1: "A" + arg2: "B" + and: "A e B" + _and: + arg1: "A" + arg2: "B" + or: "A OU B" + _or: + arg1: "A" + arg2: "B" + lt: "< A é menor do que B" + _lt: + arg1: "A" + arg2: "B" + gt: "> A é maior do que B" + _gt: + arg1: "A" + arg2: "B" + ltEq: "<= A é maior ou igual a B" + _ltEq: + arg1: "A" + arg2: "B" + gtEq: ">= A é maior ou igual a B" + _gtEq: + arg1: "A" + arg2: "B" + if: "Galho" + _if: + arg1: "Se" + arg2: "Então" + arg3: "Se não" + not: "NÃO" + _not: + arg1: "NÃO" + random: "Aleatório" + _random: + arg1: "Probabilidade" + rannum: "Numeral aleatório" + _rannum: + arg1: "Valor mínimo" + arg2: "Valor máximo" + randomPick: "Escolher aleatoriamente de uma lista" _randomPick: arg1: "Listas" + dailyRandom: "Aleatório (Muda uma vez por dia para cada usuário)" + _dailyRandom: + arg1: "Probabilidade" + dailyRannum: "Numeral aleatório (Muda uma vez por dia para cada usuário)" + _dailyRannum: + arg1: "Valor mínimo" + arg2: "Valor máximo" + dailyRandomPick: "Escolher aleatoriamente de uma lista (Muda uma vez por dia para cada usuário)" _dailyRandomPick: arg1: "Listas" + seedRandom: "Aleatório (com semente)" + _seedRandom: + arg1: "Semente" + arg2: "Probabilidade" + seedRannum: "Número aleatório (com semente)" + _seedRannum: + arg1: "Semente" + arg2: "Valor mínimo" + arg3: "Valor máximo" + seedRandomPick: "Escolher aleatoriamente de uma lista (com uma semente)" _seedRandomPick: + arg1: "Semente" arg2: "Listas" + DRPWPM: "Escolher aleatoriamente de uma lista ponderada (Muda uma vez por dia para cada usuário)" + _DRPWPM: + arg1: "Lista de texto" + pick: "Escolhe a partir da lista" _pick: arg1: "Listas" + arg2: "Posição" + listLen: "Pegar comprimento da lista" _listLen: arg1: "Listas" + number: "Numérico" + stringToNumber: "Texto para numérico" + _stringToNumber: + arg1: "Texto" + numberToString: "Numérico para texto" + _numberToString: + arg1: "Numérico" + splitStrByLine: "Dividir texto por quebras" + _splitStrByLine: + arg1: "Texto" + ref: "Variável" + aiScriptVar: "Variável AiScript" + fn: "Função" + _fn: + slots: "Espaços" + slots-info: "Separar cada espaço com uma quebra de texto" + arg1: "Resultado" + for: "Repetição 'for'" + _for: + arg1: "Número de repetições" + arg2: "Ação" + typeError: "Espaço {slot} aceita valores de tipo \"{expect}\", mas o valor dado é do tipo \"{actual}\"!" + thereIsEmptySlot: "O espaço {slot} está vazio!" types: + string: "Texto" + number: "Numérico" array: "Listas" + stringArray: "Lista de texto" + emptySlot: "Espaço vazio" + enviromentVariables: "Variáveis de ambiente" + pageVariables: "Variáveis de página" +_relayStatus: + requesting: "Pendente" + accepted: "Aprovado" + rejected: "Recusado" _notification: + fileUploaded: "Carregamento de arquivo efetuado com sucesso" + youGotMention: "{name} te mencionou" + youGotReply: "{name} te respondeu" + youGotQuote: "{name} te citou" + youGotPoll: "{name} votou em sua enquete" + youGotMessagingMessageFromUser: "{name} te mandou uma mensagem de bate-papo" + youGotMessagingMessageFromGroup: "Uma mensagem foi mandada para o grupo {name}" youWereFollowed: "Você tem um novo seguidor" + youReceivedFollowRequest: "Você recebeu um pedido de seguimento" + yourFollowRequestAccepted: "Seu pedido de seguimento foi aceito" + youWereInvitedToGroup: "{userName} te convidou para um grupo" + pollEnded: "Os resultados da enquete agora estão disponíveis" + emptyPushNotificationMessage: "As notificações de alerta foram atualizadas" _types: + all: "Todos" follow: "Seguindo" mention: "Menção" + reply: "Respostas" renote: "Repostar" + quote: "Citar" + reaction: "Reações" + pollVote: "Votações em enquetes" + pollEnded: "Enquetes terminando" + receiveFollowRequest: "Recebeu pedidos de seguimento" + followRequestAccepted: "Aceitou pedidos de seguimento" + groupInvited: "Convites de grupo" + app: "Notificações de aplicativos conectados" _actions: + followBack: "te seguiu de volta" reply: "Responder" renote: "Repostar" _deck: + alwaysShowMainColumn: "Sempre mostrar a coluna principal" + columnAlign: "Alinhar colunas" + columnMargin: "Margem entre colunas" + columnHeaderHeight: "Altura do cabeçalho de coluna" + addColumn: "Adicionar coluna" + swapLeft: "Trocar de posição com a coluna à esquerda" + swapRight: "Trocar de posição com a coluna à direita" + swapUp: "Trocar de posição com a coluna acima" + swapDown: "Trocar de posição com a coluna abaixo" + popRight: "Acoplar coluna à direita" + profile: "Perfil" _columns: + main: "Principal" + widgets: "Widgets" notifications: "Notificações" tl: "Timeline" + antenna: "Antenas" list: "Listas" mentions: "Menções" + direct: "Notas diretas" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 7405c07e6..c44589a7e 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -141,6 +141,8 @@ flagAsBot: "Аккаунт бота" flagAsBotDescription: "Включите, если этот аккаунт управляется программой. Это позволит системе Misskey учитывать это, а также поможет разработчикам других ботов предотвратить бесконечные циклы взаимодействия." flagAsCat: "Аккаунт кота" flagAsCatDescription: "Включите, и этот аккаунт будет помечен как кошачий." +flagShowTimelineReplies: "Показывать ответы на заметки в ленте" +flagShowTimelineRepliesDescription: "Если этот параметр включен, то в ленте, в дополнение к заметкам пользователя, отображаются ответы на другие заметки пользователя." autoAcceptFollowed: "Принимать подписчиков автоматически" addAccount: "Добавить учётную запись" loginFailed: "Неудачная попытка входа" @@ -236,6 +238,7 @@ saved: "Сохранено" messaging: "Сообщения" upload: "Загрузить" keepOriginalUploading: "Сохранить исходное изображение" +keepOriginalUploadingDescription: "Сохраняет исходную версию при загрузке изображений. Если выключить, то при загрузке браузер генерирует изображение для публикации." fromDrive: "С «диска»" fromUrl: "По ссылке" uploadFromUrl: "Загрузить по ссылке" @@ -589,6 +592,7 @@ smtpSecure: "Использовать SSL/TLS для SMTP-соединений" smtpSecureInfo: "Выключите при использовании STARTTLS." testEmail: "Проверка доставки электронной почты" wordMute: "Скрытие слов" +regexpError: "Ошибка в регулярном выражении" instanceMute: "Глушение инстансов" userSaysSomething: "{name} что-то сообщает" makeActive: "Активировать" @@ -619,6 +623,8 @@ fillAbuseReportDescription: "Опишите, пожалуйста, причин abuseReported: "Жалоба отправлена. Большое спасибо за информацию." reporteeOrigin: "О ком сообщено" reporterOrigin: "Кто сообщил" +forwardReport: "Перенаправление отчета на инстант." +forwardReportIsAnonymous: "Удаленный инстант не сможет увидеть вашу информацию и будет отображаться как анонимная системная учетная запись." send: "Отправить" abuseMarkAsResolved: "Отметить жалобу как решённую" openInNewTab: "Открыть в новой вкладке" @@ -815,7 +821,16 @@ leaveGroupConfirm: "Покинуть группу «{name}»?" useDrawerReactionPickerForMobile: "Выдвижная палитра на мобильном устройстве" welcomeBackWithName: "С возвращением, {name}!" clickToFinishEmailVerification: "Пожалуйста, нажмите [{ok}], чтобы завершить подтверждение адреса электронной почты." +overridedDeviceKind: "Тип устройства" +smartphone: "Смартфон" +tablet: "Планшет" +auto: "Автоматически" +themeColor: "Цвет темы" +size: "Размер" +numberOfColumn: "Количество столбцов" searchByGoogle: "Поиск" +instanceDefaultLightTheme: "Светлая тема по умолчанию" +instanceDefaultDarkTheme: "Темная тема по умолчанию" indefinitely: "вечно" _emailUnavailable: used: "Уже используется" @@ -1059,7 +1074,6 @@ _sfx: antenna: "Антенна" channel: "Канал" _ago: - unknown: "Когда-то" future: "Из будущего" justNow: "Только что" secondsAgo: "{n} с назад" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 6818a64d3..dc1151522 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -841,6 +841,7 @@ oneDay: "1 deň" oneWeek: "1 týždeň" reflectMayTakeTime: "Zmeny môžu chvíľu trvať kým sa prejavia." failedToFetchAccountInformation: "Nepodarilo sa načítať informácie o účte." +rateLimitExceeded: "Prekročený limit rýchlosti" _emailUnavailable: used: "Táto emailová adresa sa už používa" format: "Formát emailovej adresy je nesprávny" @@ -1086,7 +1087,6 @@ _sfx: antenna: "Antény" channel: "Upozornenia kanála" _ago: - unknown: "Neznáme" future: "Budúcnosť" justNow: "Teraz" secondsAgo: "pred {n} sekundami" @@ -1130,6 +1130,7 @@ _2fa: registerKey: "Registrovať bezpečnostný kľúč" step1: "Najprv si nainštalujte autentifikačnú aplikáciu (napríklad {a} alebo {b}) na svoje zariadenie." step2: "Potom, naskenujte QR kód zobrazený na obrazovke." + step2Url: "Do aplikácie zadajte nasledujúcu URL adresu:" step3: "Nastavenie dokončíte zadaním tokenu z vašej aplikácie." step4: "Od teraz, všetky ďalšie prihlásenia budú vyžadovať prihlasovací token." securityKeyInfo: "Okrem odtlačku prsta alebo PIN autentifikácie si môžete nastaviť autentifikáciu cez hardvérový bezpečnostný kľúč podporujúci FIDO2 a tak ešte viac zabezpečiť svoj účet." diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml new file mode 100644 index 000000000..42bfa45f2 --- /dev/null +++ b/locales/sv-SE.yml @@ -0,0 +1,319 @@ +--- +_lang_: "Svenska" +headlineMisskey: "Ett nätverk kopplat av noter" +introMisskey: "Välkommen! Misskey är en öppen och decentraliserad mikrobloggningstjänst.\nSkapa en \"not\" och dela dina tankar med alla runtomkring dig. 📡\nMed \"reaktioner\" kan du snabbt uttrycka dina känslor kring andras noter.👍\nLåt oss utforska en nya värld!🚀" +monthAndDay: "{day}/{month}" +search: "Sök" +notifications: "Notifikationer" +username: "Användarnamn" +password: "Lösenord" +forgotPassword: "Glömt lösenord" +fetchingAsApObject: "Hämtar från Fediversum..." +ok: "OK" +gotIt: "Uppfattat!" +cancel: "Avbryt" +enterUsername: "Ange användarnamn" +renotedBy: "Omnoterad av {user}" +noNotes: "Inga noteringar" +noNotifications: "Inga aviseringar" +instance: "Instanser" +settings: "Inställningar" +basicSettings: "Basinställningar" +otherSettings: "Andra inställningar" +openInWindow: "Öppna i ett fönster" +profile: "Profil" +timeline: "Tidslinje" +noAccountDescription: "Användaren har inte skrivit en biografi än." +login: "Logga in" +loggingIn: "Loggar in" +logout: "Logga ut" +signup: "Registrera" +uploading: "Uppladdning sker..." +save: "Spara" +users: "Användare" +addUser: "Lägg till användare" +favorite: "Lägg till i favoriter" +favorites: "Favoriter" +unfavorite: "Avfavorisera" +favorited: "Tillagd i favoriter." +alreadyFavorited: "Redan tillagd i favoriter." +cantFavorite: "Gick inte att lägga till i favoriter." +pin: "Fäst till profil" +unpin: "Lossa från profil" +copyContent: "Kopiera innehåll" +copyLink: "Kopiera länk" +delete: "Radera" +deleteAndEdit: "Radera och ändra" +deleteAndEditConfirm: "Är du säker att du vill radera denna not och ändra den? Du kommer förlora alla reaktioner, omnoteringar och svar till den." +addToList: "Lägg till i lista" +sendMessage: "Skicka ett meddelande" +copyUsername: "Kopiera användarnamn" +searchUser: "Sök användare" +reply: "Svara" +loadMore: "Ladda mer" +showMore: "Visa mer" +youGotNewFollower: "följde dig" +receiveFollowRequest: "Följarförfrågan mottagen" +followRequestAccepted: "Följarförfrågan accepterad" +mention: "Nämn" +mentions: "Omnämningar" +directNotes: "Direktnoter" +importAndExport: "Importera / Exportera" +import: "Importera" +export: "Exportera" +files: "Filer" +download: "Nedladdning" +driveFileDeleteConfirm: "Är du säker att du vill radera filen \"{name}\"? Noter med denna fil bifogad kommer också raderas." +unfollowConfirm: "Är du säker att du vill avfölja {name}?" +exportRequested: "Du har begärt en export. Detta kan ta lite tid. Den kommer läggas till i din Drive när den blir klar." +importRequested: "Du har begärt en import. Detta kan ta lite tid." +lists: "Listor" +noLists: "Du har inga listor" +note: "Not" +notes: "Noter" +following: "Följer" +followers: "Följare" +followsYou: "Följer dig" +createList: "Skapa lista" +manageLists: "Hantera lista" +error: "Fel!" +somethingHappened: "Ett fel har uppstått" +retry: "Försök igen" +pageLoadError: "Det gick inte att ladda sidan." +pageLoadErrorDescription: "Detta händer oftast p.g.a. nätverksfel eller din webbläsarcache. Försök tömma din cache och testa sedan igen efter en liten stund." +serverIsDead: "Servern svarar inte. Vänta ett litet tag och försök igen." +youShouldUpgradeClient: "För att kunna se denna sida, vänligen ladda om sidan för att uppdatera din klient." +enterListName: "Skriv ett namn till listan" +privacy: "Integritet" +makeFollowManuallyApprove: "Följarförfrågningar kräver manuellt godkännande" +defaultNoteVisibility: "Standardsynlighet" +follow: "Följ" +followRequest: "Skicka följarförfrågan" +followRequests: "Följarförfrågningar" +unfollow: "Avfölj" +followRequestPending: "Följarförfrågning avvaktar för svar" +enterEmoji: "Skriv en emoji" +renote: "Omnotera" +unrenote: "Ta tillbaka omnotering" +renoted: "Omnoterad." +cantRenote: "Inlägget kunde inte bli omnoterat." +cantReRenote: "En omnotering kan inte bli omnoterad." +quote: "Citat" +pinnedNote: "Fästad not" +pinned: "Fäst till profil" +you: "Du" +clickToShow: "Klicka för att visa" +sensitive: "Känsligt innehåll" +add: "Lägg till" +reaction: "Reaktioner" +reactionSetting: "Reaktioner som ska visas i reaktionsväljaren" +reactionSettingDescription2: "Dra för att omordna, klicka för att radera, tryck \"+\" för att lägga till." +rememberNoteVisibility: "Komihåg notvisningsinställningar" +attachCancel: "Ta bort bilaga" +markAsSensitive: "Markera som känsligt innehåll" +unmarkAsSensitive: "Avmarkera som känsligt innehåll" +enterFileName: "Ange filnamn" +mute: "Tysta" +unmute: "Avtysta" +block: "Blockera" +unblock: "Avblockera" +suspend: "Suspendera" +unsuspend: "Ta bort suspenderingen" +blockConfirm: "Är du säker att du vill blockera kontot?" +unblockConfirm: "Är du säkert att du vill avblockera kontot?" +suspendConfirm: "Är du säker att du vill suspendera detta konto?" +unsuspendConfirm: "Är du säker att du vill avsuspendera detta konto?" +selectList: "Välj lista" +selectAntenna: "Välj en antenn" +selectWidget: "Välj en widget" +editWidgets: "Redigera widgets" +editWidgetsExit: "Avsluta redigering" +customEmojis: "Anpassa emoji" +emoji: "Emoji" +emojis: "Emoji" +emojiName: "Emoji namn" +emojiUrl: "Emoji länk" +addEmoji: "Lägg till emoji" +settingGuide: "Rekommenderade inställningar" +cacheRemoteFiles: "Spara externa filer till cachen" +cacheRemoteFilesDescription: "När denna inställning är avstängd kommer externa filer laddas direkt från den externa instansen. Genom att stänga av detta kommer lagringsutrymme minska i användning men kommer öka datatrafiken eftersom miniatyrer inte kommer genereras." +flagAsBot: "Markera konto som bot" +flagAsBotDescription: "Aktivera det här alternativet om kontot är kontrollerat av ett program. Om aktiverat kommer den fungera som en flagga för andra utvecklare för att hindra ändlösa kedjor med andra bottar. Det kommer också få Misskeys interna system att hantera kontot som en bot." +flagAsCat: "Markera konto som katt" +flagAsCatDescription: "Aktivera denna inställning för att markera kontot som en katt." +flagShowTimelineReplies: "Visa svar i tidslinje" +flagShowTimelineRepliesDescription: "Visar användarsvar till andra användares noter i tidslinjen om påslagen." +autoAcceptFollowed: "Godkänn följarförfrågningar från användare du följer automatiskt" +addAccount: "Lägg till konto" +loginFailed: "Inloggningen misslyckades" +showOnRemote: "Se på extern instans" +general: "Allmänt" +wallpaper: "Bakgrundsbild" +setWallpaper: "Välj bakgrund" +removeWallpaper: "Ta bort bakgrund" +searchWith: "Sök: {q}" +youHaveNoLists: "Du har inga listor" +followConfirm: "Är du säker att du vill följa {name}?" +proxyAccount: "Proxykonto" +proxyAccountDescription: "Ett proxykonto är ett konto som agerar som en extern följare för användare under vissa villkor. Till exempel, när en användare lägger till en extern användare till en lista så kommer den externa användarens aktivitet inte levireras till instansen om ingen lokal användare följer det kontot, så proxykontot används istället." +host: "Värd" +selectUser: "Välj användare" +recipient: "Mottagare" +annotation: "Kommentarer" +federation: "Federation" +instances: "Instanser" +registeredAt: "Registrerad på" +latestRequestSentAt: "Senaste förfrågan skickad" +latestRequestReceivedAt: "Senaste begäran mottagen" +latestStatus: "Senaste status" +storageUsage: "Använt lagringsutrymme" +charts: "Diagram" +perHour: "Per timme" +perDay: "Per dag" +stopActivityDelivery: "Sluta skicka aktiviteter" +blockThisInstance: "Blockera instans" +operations: "Operationer" +software: "Mjukvara" +version: "Version" +metadata: "Metadata" +withNFiles: "{n} fil(er)" +monitor: "Övervakning" +jobQueue: "Jobbkö" +cpuAndMemory: "CPU och minne" +network: "Nätverk" +disk: "Disk" +instanceInfo: "Instansinformation" +statistics: "Statistik" +clearQueue: "Rensa kö" +clearQueueConfirmTitle: "Är du säker att du vill rensa kön?" +clearQueueConfirmText: "Om någon not är olevererad i kön kommer den inte federeras. Vanligtvis behövs inte denna handling." +clearCachedFiles: "Rensa cache" +clearCachedFilesConfirm: "Är du säker att du vill radera alla cachade externa filer?" +blockedInstances: "Blockerade instanser" +blockedInstancesDescription: "Lista adressnamn av instanser som du vill blockera. Listade instanser kommer inte längre kommunicera med denna instans." +muteAndBlock: "Tystningar och blockeringar" +mutedUsers: "Tystade användare" +blockedUsers: "Blockerade användare" +noUsers: "Det finns inga användare" +editProfile: "Redigera profil" +noteDeleteConfirm: "Är du säker på att du vill ta bort denna not?" +pinLimitExceeded: "Du kan inte fästa fler noter" +intro: "Misskey har installerats! Vänligen skapa en adminanvändare." +done: "Klar" +processing: "Bearbetar..." +preview: "Förhandsvisning" +default: "Standard" +noCustomEmojis: "Det finns ingen emoji" +noJobs: "Det finns inga jobb" +federating: "Federerar" +blocked: "Blockerad" +suspended: "Suspenderad" +all: "Allt" +subscribing: "Prenumererar" +publishing: "Publiceras" +notResponding: "Svarar inte" +instanceFollowing: "Följer på instans" +instanceFollowers: "Följare av instans" +instanceUsers: "Användare av denna instans" +changePassword: "Ändra lösenord" +security: "Säkerhet" +retypedNotMatch: "Inmatningen matchar inte" +currentPassword: "Nuvarande lösenord" +newPassword: "Nytt lösenord" +newPasswordRetype: "Bekräfta lösenord" +attachFile: "Bifoga filer" +more: "Mer!" +featured: "Utvalda" +usernameOrUserId: "Användarnamn eller användar-id" +noSuchUser: "Kan inte hitta användaren" +lookup: "Sökning" +announcements: "Nyheter" +imageUrl: "Bild-URL" +remove: "Radera" +removed: "Borttaget" +removeAreYouSure: "Är du säker att du vill radera \"{x}\"?" +deleteAreYouSure: "Är du säker att du vill radera \"{x}\"?" +resetAreYouSure: "Vill du återställa?" +saved: "Sparad" +messaging: "Chatt" +upload: "Ladda upp" +keepOriginalUploading: "Behåll originalbild" +nsfw: "Känsligt innehåll" +pinnedNotes: "Fästad not" +userList: "Listor" +smtpHost: "Värd" +smtpUser: "Användarnamn" +smtpPass: "Lösenord" +clearCache: "Rensa cache" +user: "Användare" +searchByGoogle: "Sök" +_email: + _follow: + title: "följde dig" +_mfm: + mention: "Nämn" + quote: "Citat" + emoji: "Anpassa emoji" + search: "Sök" +_theme: + keys: + mention: "Nämn" + renote: "Omnotera" +_sfx: + note: "Noter" + notification: "Notifikationer" + chat: "Chatt" +_widgets: + notifications: "Notifikationer" + timeline: "Tidslinje" + federation: "Federation" + jobQueue: "Jobbkö" +_cw: + show: "Ladda mer" +_visibility: + followers: "Följare" +_profile: + username: "Användarnamn" +_exportOrImport: + followingList: "Följer" + muteList: "Tysta" + blockingList: "Blockera" + userLists: "Listor" +_charts: + federation: "Federation" +_pages: + script: + categories: + list: "Listor" + blocks: + _join: + arg1: "Listor" + _randomPick: + arg1: "Listor" + _dailyRandomPick: + arg1: "Listor" + _seedRandomPick: + arg2: "Listor" + _pick: + arg1: "Listor" + _listLen: + arg1: "Listor" + types: + array: "Listor" +_notification: + youWereFollowed: "följde dig" + _types: + follow: "Följer" + mention: "Nämn" + renote: "Omnotera" + quote: "Citat" + reaction: "Reaktioner" + _actions: + reply: "Svara" + renote: "Omnotera" +_deck: + _columns: + notifications: "Notifikationer" + tl: "Tidslinje" + list: "Listor" + mentions: "Omnämningar" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 480526c13..7e7ef8685 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -919,7 +919,6 @@ _sfx: antenna: "Прийом антени" channel: "Повідомлення каналу" _ago: - unknown: "Невідомо" future: "Майбутнє" justNow: "Щойно" secondsAgo: "{n}с тому" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index ffe5ba197..9919e0a0a 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -842,6 +842,7 @@ oneDay: "1 ngày" oneWeek: "1 tuần" reflectMayTakeTime: "Có thể mất một thời gian để điều này được áp dụng." failedToFetchAccountInformation: "Không thể lấy thông tin tài khoản" +rateLimitExceeded: "Giới hạn quá mức" _emailUnavailable: used: "Địa chỉ email đã được sử dụng" format: "Địa chỉ email không hợp lệ" @@ -1087,7 +1088,6 @@ _sfx: antenna: "Trạm phát sóng" channel: "Kênh" _ago: - unknown: "Không rõ" future: "Tương lai" justNow: "Vừa xong" secondsAgo: "{n}s trước" @@ -1131,6 +1131,7 @@ _2fa: registerKey: "Đăng ký một mã bảo vệ" step1: "Trước tiên, hãy cài đặt một ứng dụng xác minh (chẳng hạn như {a} hoặc {b}) trên thiết bị của bạn." step2: "Sau đó, quét mã QR hiển thị trên màn hình này." + step2Url: "Bạn cũng có thể nhập URL này nếu sử dụng một chương trình máy tính:" step3: "Nhập mã token do ứng dụng của bạn cung cấp để hoàn tất thiết lập." step4: "Kể từ bây giờ, những lần đăng nhập trong tương lai sẽ yêu cầu mã token đăng nhập đó." securityKeyInfo: "Bên cạnh xác minh bằng vân tay hoặc mã PIN, bạn cũng có thể thiết lập xác minh thông qua khóa bảo mật phần cứng hỗ trợ FIDO2 để bảo mật hơn nữa cho tài khoản của mình." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index c719dcb76..4953f5528 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -1087,7 +1087,6 @@ _sfx: antenna: "天线接收" channel: "频道通知" _ago: - unknown: "未知" future: "未来" justNow: "最近" secondsAgo: "{n}秒前" @@ -1131,6 +1130,7 @@ _2fa: registerKey: "注册密钥" step1: "首先,在您的设备上安装验证应用,例如{a}或{b}。" step2: "然后,扫描屏幕上显示的二维码。" + step2Url: "在桌面应用程序中输入以下URL:" step3: "输入您的应用提供的动态口令以完成设置。" step4: "从现在开始,任何登录操作都将要求您提供动态口令。" securityKeyInfo: "您可以设置使用支持FIDO2的硬件安全密钥、设备上的指纹或PIN来保护您的登录过程。" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index e9b7ab654..f088fdc0e 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -1087,7 +1087,6 @@ _sfx: antenna: "天線接收" channel: "頻道通知" _ago: - unknown: "未知" future: "未來" justNow: "剛剛" secondsAgo: "{n}秒前" @@ -1131,6 +1130,7 @@ _2fa: registerKey: "註冊鍵" step1: "首先,在您的設備上安裝二步驗證程式,例如{a}或{b}。" step2: "然後,掃描螢幕上的QR code。" + step2Url: "在桌面版應用中,請輸入以下的URL:" step3: "輸入您的App提供的權杖以完成設定。" step4: "從現在開始,任何登入操作都將要求您提供權杖。" securityKeyInfo: "您可以設定使用支援FIDO2的硬體安全鎖、終端設備的指纹認證或者PIN碼來登入。" diff --git a/package.json b/package.json index e21c2c163..f1c8c9f37 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.110.1", + "version": "12.111.1", "codename": "indigo", "repository": { "type": "git", @@ -24,7 +24,7 @@ "watch": "yarn dev", "dev": "node ./scripts/dev.js", "lint": "yarn workspaces foreach run lint", - "cy:open": "cypress open", + "cy:open": "cypress open --browser --e2e --config-file=cypress.config.ts", "cy:run": "cypress run", "e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "mocha": "yarn workspace backend run mocha", @@ -48,13 +48,13 @@ "@types/gulp": "4.0.9", "@types/gulp-rename": "2.0.1", "@typescript-eslint/eslint-plugin": "latest", - "@typescript-eslint/parser": "5.27.0", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "cypress": "9.7.0", + "cypress": "10.0.3", "eslint-plugin-import": "^2.26.0", "eslint-plugin-vue": "latest", "start-server-and-test": "1.14.0", - "typescript": "4.7.2", + "typescript": "4.7.3", "vue-eslint-parser": "^9.0.2" } } diff --git a/packages/backend/.mocharc.json b/packages/backend/.mocharc.json index 589522216..87c571cfd 100644 --- a/packages/backend/.mocharc.json +++ b/packages/backend/.mocharc.json @@ -5,6 +5,6 @@ "loader=./test/loader.js" ], "slow": 1000, - "timeout": 3000, + "timeout": 10000, "exit": true } diff --git a/packages/backend/package.json b/packages/backend/package.json index 8d4fbed06..0e3e3ada8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -35,10 +35,9 @@ "archiver": "5.3.1", "autobind-decorator": "2.4.0", "autwh": "0.1.0", - "aws-sdk": "2.1145.0", + "aws-sdk": "2.1152.0", "bcryptjs": "2.4.3", "blurhash": "1.1.5", - "broadcast-channel": "4.12.0", "bull": "4.8.3", "cacheable-lookup": "6.0.4", "cbor": "8.1.0", @@ -51,7 +50,7 @@ "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", "feed": "4.2.2", - "file-type": "17.1.1", + "file-type": "17.1.2", "fluent-ffmpeg": "2.1.2", "got": "12.1.0", "hpagent": "0.1.2", @@ -61,8 +60,8 @@ "jsdom": "19.0.0", "json5": "2.2.1", "json5-loader": "4.0.1", - "jsonld": "5.2.0", - "jsrsasign": "10.5.23", + "jsonld": "6.0.0", + "jsrsasign": "10.5.24", "koa": "2.13.4", "koa-bodyparser": "4.3.0", "koa-favicon": "2.1.0", @@ -79,7 +78,7 @@ "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "3.2.4", + "node-fetch": "3.2.6", "nodemailer": "6.7.5", "oauth": "^0.9.15", "os-utils": "0.0.14", @@ -115,8 +114,8 @@ "tinycolor2": "1.4.2", "tmp": "0.2.1", "ts-loader": "9.3.0", - "ts-node": "10.8.0", - "tsc-alias": "1.6.7", + "ts-node": "10.8.1", + "tsc-alias": "1.6.9", "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", "typeorm": "0.3.6", @@ -125,7 +124,7 @@ "uuid": "8.3.2", "web-push": "3.5.0", "websocket": "1.0.34", - "ws": "8.7.0", + "ws": "8.8.0", "xev": "3.0.2" }, "devDependencies": { @@ -152,7 +151,7 @@ "@types/koa__multer": "2.0.4", "@types/koa__router": "8.0.11", "@types/mocha": "9.1.1", - "@types/node": "17.0.36", + "@types/node": "17.0.41", "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.4", "@types/oauth": "0.9.1", @@ -175,10 +174,10 @@ "@types/web-push": "3.3.2", "@types/websocket": "1.0.5", "@types/ws": "8.5.3", - "@typescript-eslint/eslint-plugin": "5.27.0", - "@typescript-eslint/parser": "5.27.0", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "eslint": "8.16.0", + "eslint": "8.17.0", "eslint-plugin-import": "2.26.0", "execa": "6.1.0", "form-data": "^4.0.0", diff --git a/packages/backend/src/db/postgre.ts b/packages/backend/src/db/postgre.ts index 50a85d626..298f6713e 100644 --- a/packages/backend/src/db/postgre.ts +++ b/packages/backend/src/db/postgre.ts @@ -208,7 +208,15 @@ export const db = new DataSource({ migrations: ['../../migration/*.js'], }); -export async function initDb() { +export async function initDb(force = false) { + if (force) { + if (db.isInitialized) { + await db.destroy(); + } + await db.initialize(); + return; + } + if (db.isInitialized) { // nop } else { diff --git a/packages/backend/src/misc/create-temp.ts b/packages/backend/src/misc/create-temp.ts index f07be634f..fa88769de 100644 --- a/packages/backend/src/misc/create-temp.ts +++ b/packages/backend/src/misc/create-temp.ts @@ -11,9 +11,14 @@ export function createTemp(): Promise<[string, () => void]> { export function createTempDir(): Promise<[string, () => void]> { return new Promise<[string, () => void]>((res, rej) => { - tmp.dir((e, path, cleanup) => { - if (e) return rej(e); - res([path, cleanup]); - }); + tmp.dir( + { + unsafeCleanup: true, + }, + (e, path, cleanup) => { + if (e) return rej(e); + res([path, cleanup]); + } + ); }); } diff --git a/packages/backend/src/models/repositories/note.ts b/packages/backend/src/models/repositories/note.ts index c0abbb4f9..3fefab031 100644 --- a/packages/backend/src/models/repositories/note.ts +++ b/packages/backend/src/models/repositories/note.ts @@ -136,6 +136,7 @@ async function populateMyReaction(note: Note, meId: User['id'], _hint_?: { export const NoteRepository = db.getRepository(Note).extend({ async isVisibleForMe(note: Note, meId: User['id'] | null): Promise { + // This code must always be synchronized with the checks in generateVisibilityQuery. // visibility が specified かつ自分が指定されていなかったら非表示 if (note.visibility === 'specified') { if (meId == null) { diff --git a/packages/backend/src/remote/activitypub/models/note.ts b/packages/backend/src/remote/activitypub/models/note.ts index ad24bbcd6..5d63f2605 100644 --- a/packages/backend/src/remote/activitypub/models/note.ts +++ b/packages/backend/src/remote/activitypub/models/note.ts @@ -197,7 +197,14 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s const cw = note.summary === '' ? null : note.summary; // テキストのパース - const text = typeof note._misskey_content !== 'undefined' ? note._misskey_content : (note.content ? htmlToMfm(note.content, note.tag) : null); + let text: string | null = null; + if (note.source?.mediaType === 'text/x.misskeymarkdown' && typeof note.source?.content === 'string') { + text = note.source.content; + } else if (typeof note._misskey_content !== 'undefined') { + text = note._misskey_content; + } else if (typeof note.content === 'string') { + text = htmlToMfm(note.content, note.tag); + } // vote if (reply && reply.hasPoll) { diff --git a/packages/backend/src/remote/activitypub/renderer/note.ts b/packages/backend/src/remote/activitypub/renderer/note.ts index b7df0e9a3..b3bafaa3a 100644 --- a/packages/backend/src/remote/activitypub/renderer/note.ts +++ b/packages/backend/src/remote/activitypub/renderer/note.ts @@ -82,15 +82,14 @@ export default async function renderNote(note: Note, dive = true, isTalk = false const files = await getPromisedFiles(note.fileIds); - // text should never be undefined - const text = note.text ?? null; + const text = note.text ?? ''; let poll: Poll | null = null; if (note.hasPoll) { poll = await Polls.findOneBy({ noteId: note.id }); } - let apText = text ?? ''; + let apText = text; if (quote) { apText += `\n\nRE: ${quote}`; @@ -138,6 +137,10 @@ export default async function renderNote(note: Note, dive = true, isTalk = false summary, content, _misskey_content: text, + source: { + content: text, + mediaType: "text/x.misskeymarkdown", + }, _misskey_quote: quote, quoteUrl: quote, published: note.createdAt.toISOString(), diff --git a/packages/backend/src/remote/activitypub/type.ts b/packages/backend/src/remote/activitypub/type.ts index ef5b98b59..5d00481b7 100644 --- a/packages/backend/src/remote/activitypub/type.ts +++ b/packages/backend/src/remote/activitypub/type.ts @@ -106,7 +106,10 @@ export const isPost = (object: IObject): object is IPost => export interface IPost extends IObject { type: 'Note' | 'Question' | 'Article' | 'Audio' | 'Document' | 'Image' | 'Page' | 'Video' | 'Event'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; _misskey_talk: boolean; @@ -114,7 +117,10 @@ export interface IPost extends IObject { export interface IQuestion extends IObject { type: 'Note' | 'Question'; - _misskey_content?: string; + source?: { + content: string; + mediaType: string; + }; _misskey_quote?: string; quoteUrl?: string; oneOf?: IQuestionChoice[]; diff --git a/packages/backend/src/server/api/call.ts b/packages/backend/src/server/api/call.ts index cd3e0abc0..46afde4e4 100644 --- a/packages/backend/src/server/api/call.ts +++ b/packages/backend/src/server/api/call.ts @@ -1,12 +1,12 @@ -import Koa from 'koa'; import { performance } from 'perf_hooks'; -import { limiter } from './limiter.js'; +import Koa from 'koa'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; +import { AccessToken } from '@/models/entities/access-token.js'; +import { getIpHash } from '@/misc/get-ip-hash.js'; +import { limiter } from './limiter.js'; import endpoints, { IEndpointMeta } from './endpoints.js'; import { ApiError } from './error.js'; import { apiLogger } from './logger.js'; -import { AccessToken } from '@/models/entities/access-token.js'; -import { getIpHash } from '@/misc/get-ip-hash.js'; const accessDenied = { message: 'Access denied.', @@ -33,7 +33,7 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi throw new ApiError(accessDenied); } - if (ep.meta.limit && !isModerator) { + if (ep.meta.limit) { // koa will automatically load the `X-Forwarded-For` header if `proxy: true` is configured in the app. let limitActor: string; if (user) { @@ -120,20 +120,20 @@ export default async (endpoint: string, user: CacheableLocalUser | null | undefi if (e instanceof ApiError) { throw e; } else { - apiLogger.error(`Internal error occurred in ${ep.name}: ${e?.message}`, { + apiLogger.error(`Internal error occurred in ${ep.name}: ${e.message}`, { ep: ep.name, ps: data, e: { - message: e?.message, - code: e?.name, - stack: e?.stack, + message: e.message, + code: e.name, + stack: e.stack, }, }); throw new ApiError(null, { e: { - message: e?.message, - code: e?.name, - stack: e?.stack, + message: e.message, + code: e.name, + stack: e.stack, }, }); } diff --git a/packages/backend/src/server/api/common/generate-visibility-query.ts b/packages/backend/src/server/api/common/generate-visibility-query.ts index 715982934..b50b6812f 100644 --- a/packages/backend/src/server/api/common/generate-visibility-query.ts +++ b/packages/backend/src/server/api/common/generate-visibility-query.ts @@ -3,6 +3,7 @@ import { Followings } from '@/models/index.js'; import { Brackets, SelectQueryBuilder } from 'typeorm'; export function generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: User['id'] } | null) { + // This code must always be synchronized with the checks in Notes.isVisibleForMe. if (me == null) { q.andWhere(new Brackets(qb => { qb .where(`note.visibility = 'public'`) @@ -11,7 +12,7 @@ export function generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: U } else { const followingQuery = Followings.createQueryBuilder('following') .select('following.followeeId') - .where('following.followerId = :followerId', { followerId: me.id }); + .where('following.followerId = :meId'); q.andWhere(new Brackets(qb => { qb // 公開投稿である @@ -20,21 +21,22 @@ export function generateVisibilityQuery(q: SelectQueryBuilder, me?: { id: U .orWhere(`note.visibility = 'home'`); })) // または 自分自身 - .orWhere('note.userId = :userId1', { userId1: me.id }) + .orWhere('note.userId = :meId') // または 自分宛て - .orWhere(`'{"${me.id}"}' <@ note.visibleUserIds`) + .orWhere(':meId = ANY(note.visibleUserIds)') + .orWhere(':meId = ANY(note.mentions)') .orWhere(new Brackets(qb => { qb // または フォロワー宛ての投稿であり、 - .where('note.visibility = \'followers\'') + .where(`note.visibility = 'followers'`) .andWhere(new Brackets(qb => { qb // 自分がフォロワーである .where(`note.userId IN (${ followingQuery.getQuery() })`) // または 自分の投稿へのリプライ - .orWhere('note.replyUserId = :userId3', { userId3: me.id }); + .orWhere('note.replyUserId = :meId'); })); })); })); - q.setParameters(followingQuery.getParameters()); + q.setParameters({ meId: me.id }); } } diff --git a/packages/backend/src/server/api/common/read-notification.ts b/packages/backend/src/server/api/common/read-notification.ts index 0dad35bcc..8c4ba41a3 100644 --- a/packages/backend/src/server/api/common/read-notification.ts +++ b/packages/backend/src/server/api/common/read-notification.ts @@ -9,6 +9,8 @@ export async function readNotification( userId: User['id'], notificationIds: Notification['id'][] ) { + if (notificationIds.length === 0) return; + // Update documents await Notifications.update({ id: In(notificationIds), diff --git a/packages/backend/src/server/api/endpoints/announcements.ts b/packages/backend/src/server/api/endpoints/announcements.ts index 222efdcef..23cb93c9a 100644 --- a/packages/backend/src/server/api/endpoints/announcements.ts +++ b/packages/backend/src/server/api/endpoints/announcements.ts @@ -1,5 +1,5 @@ -import define from '../define.js'; import { Announcements, AnnouncementReads } from '@/models/index.js'; +import define from '../define.js'; import { makePaginationQuery } from '../common/make-pagination-query.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/drive.ts b/packages/backend/src/server/api/endpoints/drive.ts index c599d96ca..47e940cdd 100644 --- a/packages/backend/src/server/api/endpoints/drive.ts +++ b/packages/backend/src/server/api/endpoints/drive.ts @@ -1,6 +1,6 @@ -import define from '../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { DriveFiles } from '@/models/index.js'; +import define from '../define.js'; export const meta = { tags: ['drive', 'account'], diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts index 7ffe89a1e..415a8cc69 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/attached-notes.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Find the notes to which the given file is attached.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts index 80293df5d..bbae9bf4e 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/check-existence.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Check if a given file exists.', + res: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/create.ts b/packages/backend/src/server/api/endpoints/drive/files/create.ts index 0939ae336..7397fd9ce 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/create.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/create.ts @@ -20,6 +20,8 @@ export const meta = { kind: 'write:drive', + description: 'Upload a new drive file.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/delete.ts b/packages/backend/src/server/api/endpoints/drive/files/delete.ts index 61c56e631..6108ae7da 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/delete.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/delete.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Delete an existing drive file.', + errors: { noSuchFile: { message: 'No such file.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts index 0b74cb9f0..f2bc7348c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find-by-hash.ts @@ -8,6 +8,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by a hash of the contents.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/find.ts b/packages/backend/src/server/api/endpoints/drive/files/find.ts index 4938a69d1..245fb45a6 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/find.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/find.ts @@ -9,6 +9,8 @@ export const meta = { kind: 'read:drive', + description: 'Search for a drive file by the given parameters.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/show.ts b/packages/backend/src/server/api/endpoints/drive/files/show.ts index fb19345fe..2c604c54c 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/show.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/show.ts @@ -10,6 +10,8 @@ export const meta = { kind: 'read:drive', + description: 'Show the properties of a drive file.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/drive/files/update.ts b/packages/backend/src/server/api/endpoints/drive/files/update.ts index 4b3f5f2dc..e3debe0b4 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/update.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/update.ts @@ -11,6 +11,8 @@ export const meta = { kind: 'write:drive', + description: 'Update the properties of a drive file.', + errors: { invalidFileName: { message: 'Invalid file name.', diff --git a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts index 3bfecac80..53f2298f2 100644 --- a/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts +++ b/packages/backend/src/server/api/endpoints/drive/files/upload-from-url.ts @@ -13,6 +13,8 @@ export const meta = { max: 60, }, + description: 'Request the server to download a new drive file from the specified URL.', + requireCredential: true, kind: 'write:drive', diff --git a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts index bc8d2e2ac..5fe622932 100644 --- a/packages/backend/src/server/api/endpoints/export-custom-emojis.ts +++ b/packages/backend/src/server/api/endpoints/export-custom-emojis.ts @@ -1,6 +1,6 @@ -import define from '../define.js'; -import { createExportCustomEmojisJob } from '@/queue/index.js'; import ms from 'ms'; +import { createExportCustomEmojisJob } from '@/queue/index.js'; +import define from '../define.js'; export const meta = { secure: true, diff --git a/packages/backend/src/server/api/endpoints/get-online-users-count.ts b/packages/backend/src/server/api/endpoints/get-online-users-count.ts index b0c1225be..56c550297 100644 --- a/packages/backend/src/server/api/endpoints/get-online-users-count.ts +++ b/packages/backend/src/server/api/endpoints/get-online-users-count.ts @@ -1,6 +1,6 @@ +import { MoreThan } from 'typeorm'; import { USER_ONLINE_THRESHOLD } from '@/const.js'; import { Users } from '@/models/index.js'; -import { MoreThan } from 'typeorm'; import define from '../define.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts index 5b1ad2b09..22aedfeee 100644 --- a/packages/backend/src/server/api/endpoints/i.ts +++ b/packages/backend/src/server/api/endpoints/i.ts @@ -1,5 +1,5 @@ -import define from '../define.js'; import { Users } from '@/models/index.js'; +import define from '../define.js'; export const meta = { tags: ['account'], diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts index 6ea8cb357..1c31ce7a6 100644 --- a/packages/backend/src/server/api/endpoints/i/notifications.ts +++ b/packages/backend/src/server/api/endpoints/i/notifications.ts @@ -1,17 +1,22 @@ +import { Brackets } from 'typeorm'; +import { Notifications, Followings, Mutings, Users } from '@/models/index.js'; +import { notificationTypes } from '@/types.js'; +import read from '@/services/note/read.js'; import { readNotification } from '../../common/read-notification.js'; import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateMutedInstanceNotificationQuery } from '../../common/generate-muted-instance-query.js'; -import { Notifications, Followings, Mutings, Users } from '@/models/index.js'; -import { notificationTypes } from '@/types.js'; -import read from '@/services/note/read.js'; -import { Brackets } from 'typeorm'; export const meta = { tags: ['account', 'notifications'], requireCredential: true, + limit: { + duration: 60000, + max: 10, + }, + kind: 'read:notifications', res: { @@ -67,7 +72,7 @@ export default define(meta, paramDef, async (ps, user) => { .where('users.isSuspended = TRUE'); const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) - .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) + .andWhere('notification.notifieeId = :meId', { meId: user.id }) .leftJoinAndSelect('notification.notifier', 'notifier') .leftJoinAndSelect('notification.note', 'note') .leftJoinAndSelect('notifier.avatar', 'notifierAvatar') @@ -103,13 +108,13 @@ export default define(meta, paramDef, async (ps, user) => { } if (ps.includeTypes && ps.includeTypes.length > 0) { - query.andWhere(`notification.type IN (:...includeTypes)`, { includeTypes: ps.includeTypes }); + query.andWhere('notification.type IN (:...includeTypes)', { includeTypes: ps.includeTypes }); } else if (ps.excludeTypes && ps.excludeTypes.length > 0) { - query.andWhere(`notification.type NOT IN (:...excludeTypes)`, { excludeTypes: ps.excludeTypes }); + query.andWhere('notification.type NOT IN (:...excludeTypes)', { excludeTypes: ps.excludeTypes }); } if (ps.unreadOnly) { - query.andWhere(`notification.isRead = false`); + query.andWhere('notification.isRead = false'); } const notifications = await query.take(ps.limit).getMany(); diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts index e1ae282a9..5b624842c 100644 --- a/packages/backend/src/server/api/endpoints/meta.ts +++ b/packages/backend/src/server/api/endpoints/meta.ts @@ -1,10 +1,10 @@ +import { IsNull, MoreThan } from 'typeorm'; import config from '@/config/index.js'; -import define from '../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { Ads, Emojis, Users } from '@/models/index.js'; import { DB_MAX_NOTE_TEXT_LENGTH } from '@/misc/hard-limits.js'; -import { IsNull, MoreThan } from 'typeorm'; import { MAX_NOTE_TEXT_LENGTH } from '@/const.js'; +import define from '../define.js'; export const meta = { tags: ['meta'], diff --git a/packages/backend/src/server/api/endpoints/notes.ts b/packages/backend/src/server/api/endpoints/notes.ts index 2733c826e..015b0338e 100644 --- a/packages/backend/src/server/api/endpoints/notes.ts +++ b/packages/backend/src/server/api/endpoints/notes.ts @@ -1,6 +1,6 @@ +import { Notes } from '@/models/index.js'; import define from '../define.js'; import { makePaginationQuery } from '../common/make-pagination-query.js'; -import { Notes } from '@/models/index.js'; export const meta = { tags: ['notes'], @@ -34,8 +34,8 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(`note.visibility = 'public'`) - .andWhere(`note.localOnly = FALSE`) + .andWhere('note.visibility = \'public\'') + .andWhere('note.localOnly = FALSE') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') @@ -61,7 +61,7 @@ export default define(meta, paramDef, async (ps) => { } if (ps.withFiles !== undefined) { - query.andWhere(ps.withFiles ? `note.fileIds != '{}'` : `note.fileIds = '{}'`); + query.andWhere(ps.withFiles ? 'note.fileIds != \'{}\'' : 'note.fileIds = \'{}\''); } if (ps.poll !== undefined) { diff --git a/packages/backend/src/server/api/endpoints/notes/children.ts b/packages/backend/src/server/api/endpoints/notes/children.ts index 86dde30d6..50ba293a5 100644 --- a/packages/backend/src/server/api/endpoints/notes/children.ts +++ b/packages/backend/src/server/api/endpoints/notes/children.ts @@ -1,9 +1,9 @@ +import { Brackets } from 'typeorm'; +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { Brackets } from 'typeorm'; -import { Notes } from '@/models/index.js'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; @@ -38,13 +38,13 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(new Brackets(qb => { qb - .where(`note.replyId = :noteId`, { noteId: ps.noteId }) + .where('note.replyId = :noteId', { noteId: ps.noteId }) .orWhere(new Brackets(qb => { qb - .where(`note.renoteId = :noteId`, { noteId: ps.noteId }) + .where('note.renoteId = :noteId', { noteId: ps.noteId }) .andWhere(new Brackets(qb => { qb - .where(`note.text IS NOT NULL`) - .orWhere(`note.fileIds != '{}'`) - .orWhere(`note.hasPoll = TRUE`); + .where('note.text IS NOT NULL') + .orWhere('note.fileIds != \'{}\'') + .orWhere('note.hasPoll = TRUE'); })); })); })) diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts index 8683a7f75..e79f8563e 100644 --- a/packages/backend/src/server/api/endpoints/notes/clips.ts +++ b/packages/backend/src/server/api/endpoints/notes/clips.ts @@ -1,8 +1,8 @@ -import define from '../../define.js'; +import { In } from 'typeorm'; import { ClipNotes, Clips } from '@/models/index.js'; +import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; -import { In } from 'typeorm'; export const meta = { tags: ['clips', 'notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/conversation.ts b/packages/backend/src/server/api/endpoints/notes/conversation.ts index b991a495f..b731d1824 100644 --- a/packages/backend/src/server/api/endpoints/notes/conversation.ts +++ b/packages/backend/src/server/api/endpoints/notes/conversation.ts @@ -1,8 +1,8 @@ +import { Note } from '@/models/entities/note.js'; +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getNote } from '../../common/getters.js'; -import { Note } from '@/models/entities/note.js'; -import { Notes } from '@/models/index.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/delete.ts b/packages/backend/src/server/api/endpoints/notes/delete.ts index 804e146fa..c23ceeb5b 100644 --- a/packages/backend/src/server/api/endpoints/notes/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/delete.ts @@ -1,9 +1,9 @@ -import deleteNote from '@/services/note/delete.js'; -import define from '../../define.js'; import ms from 'ms'; +import deleteNote from '@/services/note/delete.js'; +import { Users } from '@/models/index.js'; +import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts index 41dc5ac8e..097371a42 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/create.ts @@ -1,8 +1,8 @@ +import { NoteFavorites } from '@/models/index.js'; +import { genId } from '@/misc/gen-id.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getNote } from '../../../common/getters.js'; -import { NoteFavorites } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['notes', 'favorites'], diff --git a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts index a48f7a0aa..82ef4fa19 100644 --- a/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/favorites/delete.ts @@ -1,7 +1,7 @@ +import { NoteFavorites } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getNote } from '../../../common/getters.js'; -import { NoteFavorites } from '@/models/index.js'; export const meta = { tags: ['notes', 'favorites'], diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts index 6308d2369..dd9cc581a 100644 --- a/packages/backend/src/server/api/endpoints/notes/featured.ts +++ b/packages/backend/src/server/api/endpoints/notes/featured.ts @@ -1,6 +1,6 @@ +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { Notes } from '@/models/index.js'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { @@ -36,9 +36,9 @@ export default define(meta, paramDef, async (ps, user) => { const query = Notes.createQueryBuilder('note') .addSelect('note.score') .where('note.userHost IS NULL') - .andWhere(`note.score > 0`) - .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) - .andWhere(`note.visibility = 'public'`) + .andWhere('note.score > 0') + .andWhere('note.createdAt > :date', { date: new Date(Date.now() - day) }) + .andWhere('note.visibility = \'public\'') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts index cb402ecaa..418fc62c3 100644 --- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts @@ -1,11 +1,11 @@ -import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Notes, Users } from '@/models/index.js'; +import { activeUsersChart } from '@/services/chart/index.js'; +import define from '../../define.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes, Users } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; import { generateRepliesQuery } from '../../common/generate-replies-query.js'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; @@ -60,7 +60,7 @@ export default define(meta, paramDef, async (ps, user) => { //#region Construct query const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.visibility = \'public\'') .andWhere('note.channelId IS NULL') .innerJoinAndSelect('note.user', 'user') diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts index f9893527e..52ee81799 100644 --- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -1,13 +1,13 @@ -import define from '../../define.js'; +import { Brackets } from 'typeorm'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { Followings, Notes, Users } from '@/models/index.js'; +import { activeUsersChart } from '@/services/chart/index.js'; +import define from '../../define.js'; import { ApiError } from '../../error.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Followings, Notes, Users } from '@/models/index.js'; -import { Brackets } from 'typeorm'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; import { generateRepliesQuery } from '../../common/generate-replies-query.js'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; import { generateChannelQuery } from '../../common/generate-channel-query.js'; @@ -70,7 +70,7 @@ export default define(meta, paramDef, async (ps, user) => { .where('following.followerId = :followerId', { followerId: user.id }); const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere(new Brackets(qb => { qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }) .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts index 03edf30b3..aac2a3749 100644 --- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts @@ -1,12 +1,12 @@ -import define from '../../define.js'; +import { Brackets } from 'typeorm'; import { fetchMeta } from '@/misc/fetch-meta.js'; -import { ApiError } from '../../error.js'; import { Notes, Users } from '@/models/index.js'; +import { activeUsersChart } from '@/services/chart/index.js'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import { Brackets } from 'typeorm'; import { generateRepliesQuery } from '../../common/generate-replies-query.js'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; import { generateChannelQuery } from '../../common/generate-channel-query.js'; @@ -66,7 +66,7 @@ export default define(meta, paramDef, async (ps, user) => { //#region Construct query const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts index eafbba322..9b4154452 100644 --- a/packages/backend/src/server/api/endpoints/notes/mentions.ts +++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts @@ -1,10 +1,10 @@ -import define from '../../define.js'; +import { Brackets } from 'typeorm'; import read from '@/services/note/read.js'; import { Notes, Followings } from '@/models/index.js'; +import define from '../../define.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Brackets } from 'typeorm'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts index 28bfade2f..2150efaaf 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/recommendation.ts @@ -1,6 +1,6 @@ -import define from '../../../define.js'; -import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js'; import { Brackets, In } from 'typeorm'; +import { Polls, Mutings, Notes, PollVotes } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['notes'], @@ -31,8 +31,8 @@ export const paramDef = { export default define(meta, paramDef, async (ps, user) => { const query = Polls.createQueryBuilder('poll') .where('poll.userHost IS NULL') - .andWhere(`poll.userId != :meId`, { meId: user.id }) - .andWhere(`poll.noteVisibility = 'public'`) + .andWhere('poll.userId != :meId', { meId: user.id }) + .andWhere('poll.noteVisibility = \'public\'') .andWhere(new Brackets(qb => { qb .where('poll.expiresAt IS NULL') .orWhere('poll.expiresAt > :now', { now: new Date() }); diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts index 6244b55cf..45a832cbd 100644 --- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts +++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts @@ -1,16 +1,16 @@ +import { Not } from 'typeorm'; import { publishNoteStream } from '@/services/stream.js'; import { createNotification } from '@/services/create-notification.js'; -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getNote } from '../../../common/getters.js'; import { deliver } from '@/queue/index.js'; import { renderActivity } from '@/remote/activitypub/renderer/index.js'; import renderVote from '@/remote/activitypub/renderer/vote.js'; import { deliverQuestionUpdate } from '@/services/note/polls/update.js'; import { PollVotes, NoteWatchings, Users, Polls, Blockings } from '@/models/index.js'; -import { Not } from 'typeorm'; import { IRemoteUser } from '@/models/entities/user.js'; import { genId } from '@/misc/gen-id.js'; +import { getNote } from '../../../common/getters.js'; +import { ApiError } from '../../../error.js'; +import define from '../../../define.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts index 639ecae26..c13cafa21 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/delete.ts @@ -1,6 +1,6 @@ -import define from '../../../define.js'; import ms from 'ms'; import deleteReaction from '@/services/note/reaction/delete.js'; +import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/renotes.ts b/packages/backend/src/server/api/endpoints/notes/renotes.ts index 87c855a5e..28be36076 100644 --- a/packages/backend/src/server/api/endpoints/notes/renotes.ts +++ b/packages/backend/src/server/api/endpoints/notes/renotes.ts @@ -1,10 +1,10 @@ +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes } from '@/models/index.js'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { @@ -50,7 +50,7 @@ export default define(meta, paramDef, async (ps, user) => { }); const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .andWhere(`note.renoteId = :renoteId`, { renoteId: note.id }) + .andWhere('note.renoteId = :renoteId', { renoteId: note.id }) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') .leftJoinAndSelect('user.banner', 'banner') diff --git a/packages/backend/src/server/api/endpoints/notes/replies.ts b/packages/backend/src/server/api/endpoints/notes/replies.ts index 3053eabe3..ab0018f58 100644 --- a/packages/backend/src/server/api/endpoints/notes/replies.ts +++ b/packages/backend/src/server/api/endpoints/notes/replies.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { Notes } from '@/models/index.js'; +import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts index bb85c9200..777de7221 100644 --- a/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts +++ b/packages/backend/src/server/api/endpoints/notes/search-by-tag.ts @@ -1,11 +1,11 @@ -import define from '../../define.js'; -import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes } from '@/models/index.js'; -import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { Brackets } from 'typeorm'; +import { Notes } from '@/models/index.js'; import { safeForSql } from '@/misc/safe-for-sql.js'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; +import define from '../../define.js'; +import { makePaginationQuery } from '../../common/make-pagination-query.js'; +import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; +import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts index af9b5f0a1..4e2cdae80 100644 --- a/packages/backend/src/server/api/endpoints/notes/search.ts +++ b/packages/backend/src/server/api/endpoints/notes/search.ts @@ -1,8 +1,8 @@ +import { In } from 'typeorm'; +import { Notes } from '@/models/index.js'; +import config from '@/config/index.js'; import es from '../../../../db/elasticsearch.js'; import define from '../../define.js'; -import { Notes } from '@/models/index.js'; -import { In } from 'typeorm'; -import config from '@/config/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; @@ -99,7 +99,7 @@ export default define(meta, paramDef, async (ps, me) => { userHost: ps.host, }, }] : [] - : []; + : []; const result = await es.search({ index: config.elasticsearch.index || 'misskey_note', diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index d6692923c..5cd74bd2c 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -1,7 +1,7 @@ +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; -import { Notes } from '@/models/index.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/state.ts b/packages/backend/src/server/api/endpoints/notes/state.ts index 069f11fa4..01afa5add 100644 --- a/packages/backend/src/server/api/endpoints/notes/state.ts +++ b/packages/backend/src/server/api/endpoints/notes/state.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts index e48a2cf57..cf360526d 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/create.ts @@ -1,9 +1,9 @@ -import define from '../../../define.js'; -import { getNote } from '../../../common/getters.js'; -import { ApiError } from '../../../error.js'; import { Notes, NoteThreadMutings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import readNote from '@/services/note/read.js'; +import define from '../../../define.js'; +import { getNote } from '../../../common/getters.js'; +import { ApiError } from '../../../error.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts index 4fb3137a5..ac310d0fe 100644 --- a/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -1,7 +1,7 @@ +import { NoteThreadMutings } from '@/models/index.js'; import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; -import { NoteThreadMutings } from '@/models/index.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts index 0f976d18b..d80940e95 100644 --- a/packages/backend/src/server/api/endpoints/notes/timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts @@ -1,11 +1,11 @@ +import { Brackets } from 'typeorm'; +import { Notes, Followings } from '@/models/index.js'; +import { activeUsersChart } from '@/services/chart/index.js'; import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { Notes, Followings } from '@/models/index.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import { Brackets } from 'typeorm'; import { generateRepliesQuery } from '../../common/generate-replies-query.js'; import { generateMutedNoteQuery } from '../../common/generate-muted-note-query.js'; import { generateChannelQuery } from '../../common/generate-channel-query.js'; @@ -62,10 +62,10 @@ export default define(meta, paramDef, async (ps, user) => { .where('following.followerId = :followerId', { followerId: user.id }); const query = makePaginationQuery(Notes.createQueryBuilder('note'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere(new Brackets(qb => { qb .where('note.userId = :meId', { meId: user.id }); - if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); + if (hasFollowing) qb.orWhere(`note.userId IN (${ followingQuery.getQuery() })`); })) .innerJoinAndSelect('note.user', 'user') .leftJoinAndSelect('user.avatar', 'avatar') diff --git a/packages/backend/src/server/api/endpoints/notes/unrenote.ts b/packages/backend/src/server/api/endpoints/notes/unrenote.ts index 5e8c31eaf..3fba0efe0 100644 --- a/packages/backend/src/server/api/endpoints/notes/unrenote.ts +++ b/packages/backend/src/server/api/endpoints/notes/unrenote.ts @@ -1,9 +1,9 @@ -import deleteNote from '@/services/note/delete.js'; -import define from '../../define.js'; import ms from 'ms'; +import deleteNote from '@/services/note/delete.js'; +import { Notes, Users } from '@/models/index.js'; +import define from '../../define.js'; import { getNote } from '../../common/getters.js'; import { ApiError } from '../../error.js'; -import { Notes, Users } from '@/models/index.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts index fd4a87903..e603a8f62 100644 --- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts @@ -1,10 +1,10 @@ +import { Brackets } from 'typeorm'; +import { UserLists, UserListJoinings, Notes } from '@/models/index.js'; +import { activeUsersChart } from '@/services/chart/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { UserLists, UserListJoinings, Notes } from '@/models/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { activeUsersChart } from '@/services/chart/index.js'; -import { Brackets } from 'typeorm'; export const meta = { tags: ['notes', 'lists'], diff --git a/packages/backend/src/server/api/endpoints/notes/watching/create.ts b/packages/backend/src/server/api/endpoints/notes/watching/create.ts index 8fdf84624..7d482b073 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/create.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import watch from '@/services/note/watch.js'; +import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts index d58f09797..2c1a2e5fb 100644 --- a/packages/backend/src/server/api/endpoints/notes/watching/delete.ts +++ b/packages/backend/src/server/api/endpoints/notes/watching/delete.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import unwatch from '@/services/note/unwatch.js'; +import define from '../../../define.js'; import { getNote } from '../../../common/getters.js'; import { ApiError } from '../../../error.js'; diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts index b339c8723..80d513d8d 100644 --- a/packages/backend/src/server/api/endpoints/notifications/create.ts +++ b/packages/backend/src/server/api/endpoints/notifications/create.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { createNotification } from '@/services/create-notification.js'; +import define from '../../define.js'; export const meta = { tags: ['notifications'], diff --git a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts index 4575cba43..d169afbb3 100644 --- a/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/mark-all-as-read.ts @@ -1,7 +1,7 @@ import { publishMainStream } from '@/services/stream.js'; import { pushNotification } from '@/services/push-notification.js'; -import define from '../../define.js'; import { Notifications } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['notifications', 'account'], diff --git a/packages/backend/src/server/api/endpoints/notifications/read.ts b/packages/backend/src/server/api/endpoints/notifications/read.ts index 65e96d486..7bce525a5 100644 --- a/packages/backend/src/server/api/endpoints/notifications/read.ts +++ b/packages/backend/src/server/api/endpoints/notifications/read.ts @@ -2,17 +2,14 @@ import define from '../../define.js'; import { readNotification } from '../../common/read-notification.js'; export const meta = { - desc: { - 'ja-JP': '通知を既読にします。', - 'en-US': 'Mark a notification as read.' - }, - tags: ['notifications', 'account'], requireCredential: true, kind: 'write:notifications', + description: 'Mark a notification as read.', + errors: { noSuchNotification: { message: 'No such notification.', @@ -34,7 +31,11 @@ export const paramDef = { { type: 'object', properties: { - notificationIds: { type: 'array', items: { type: 'string', format: 'misskey:id' } }, + notificationIds: { + type: 'array', + items: { type: 'string', format: 'misskey:id' }, + maxItems: 100, + }, }, required: ['notificationIds'], }, diff --git a/packages/backend/src/server/api/endpoints/page-push.ts b/packages/backend/src/server/api/endpoints/page-push.ts index 7096aaa3d..6dd3ede85 100644 --- a/packages/backend/src/server/api/endpoints/page-push.ts +++ b/packages/backend/src/server/api/endpoints/page-push.ts @@ -1,6 +1,6 @@ -import define from '../define.js'; import { publishMainStream } from '@/services/stream.js'; import { Users, Pages } from '@/models/index.js'; +import define from '../define.js'; import { ApiError } from '../error.js'; export const meta = { diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts index c171cd39f..b008cde84 100644 --- a/packages/backend/src/server/api/endpoints/pages/create.ts +++ b/packages/backend/src/server/api/endpoints/pages/create.ts @@ -1,8 +1,8 @@ import ms from 'ms'; -import define from '../../define.js'; import { Pages, DriveFiles } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { Page } from '@/models/entities/page.js'; +import define from '../../define.js'; import { ApiError } from '../../error.js'; export const meta = { @@ -51,7 +51,7 @@ export const paramDef = { } }, script: { type: 'string' }, eyeCatchingImageId: { type: 'string', format: 'misskey:id', nullable: true }, - font: { type: 'string', enum: ['serif', 'sans-serif'], default: "sans-serif" }, + font: { type: 'string', enum: ['serif', 'sans-serif'], default: 'sans-serif' }, alignCenter: { type: 'boolean', default: false }, hideTitleWhenPinned: { type: 'boolean', default: false }, }, diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts index e35ad9ebf..a7708e658 100644 --- a/packages/backend/src/server/api/endpoints/pages/delete.ts +++ b/packages/backend/src/server/api/endpoints/pages/delete.ts @@ -1,6 +1,6 @@ +import { Pages } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { Pages } from '@/models/index.js'; export const meta = { tags: ['pages'], diff --git a/packages/backend/src/server/api/endpoints/pages/featured.ts b/packages/backend/src/server/api/endpoints/pages/featured.ts index eeb6d509c..5a149a626 100644 --- a/packages/backend/src/server/api/endpoints/pages/featured.ts +++ b/packages/backend/src/server/api/endpoints/pages/featured.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { Pages } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['pages'], diff --git a/packages/backend/src/server/api/endpoints/pages/like.ts b/packages/backend/src/server/api/endpoints/pages/like.ts index 20793db98..269b539f7 100644 --- a/packages/backend/src/server/api/endpoints/pages/like.ts +++ b/packages/backend/src/server/api/endpoints/pages/like.ts @@ -1,7 +1,7 @@ -import define from '../../define.js'; -import { ApiError } from '../../error.js'; import { Pages, PageLikes } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; +import define from '../../define.js'; +import { ApiError } from '../../error.js'; export const meta = { tags: ['pages'], diff --git a/packages/backend/src/server/api/endpoints/pages/unlike.ts b/packages/backend/src/server/api/endpoints/pages/unlike.ts index 636f3c714..6b3a2bec1 100644 --- a/packages/backend/src/server/api/endpoints/pages/unlike.ts +++ b/packages/backend/src/server/api/endpoints/pages/unlike.ts @@ -1,6 +1,6 @@ +import { Pages, PageLikes } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { Pages, PageLikes } from '@/models/index.js'; export const meta = { tags: ['pages'], diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts index bf95ab36f..d241f585a 100644 --- a/packages/backend/src/server/api/endpoints/pages/update.ts +++ b/packages/backend/src/server/api/endpoints/pages/update.ts @@ -1,8 +1,8 @@ import ms from 'ms'; +import { Not } from 'typeorm'; +import { Pages, DriveFiles } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { Pages, DriveFiles } from '@/models/index.js'; -import { Not } from 'typeorm'; export const meta = { tags: ['pages'], diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts index 8d253c1f3..41595b47d 100644 --- a/packages/backend/src/server/api/endpoints/pinned-users.ts +++ b/packages/backend/src/server/api/endpoints/pinned-users.ts @@ -1,9 +1,9 @@ -import define from '../define.js'; +import { IsNull } from 'typeorm'; import { Users } from '@/models/index.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import * as Acct from '@/misc/acct.js'; import { User } from '@/models/entities/user.js'; -import { IsNull } from 'typeorm'; +import define from '../define.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/promo/read.ts b/packages/backend/src/server/api/endpoints/promo/read.ts index cc602857d..c6a940c65 100644 --- a/packages/backend/src/server/api/endpoints/promo/read.ts +++ b/packages/backend/src/server/api/endpoints/promo/read.ts @@ -1,8 +1,8 @@ +import { PromoReads } from '@/models/index.js'; +import { genId } from '@/misc/gen-id.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getNote } from '../../common/getters.js'; -import { PromoReads } from '@/models/index.js'; -import { genId } from '@/misc/gen-id.js'; export const meta = { tags: ['notes'], diff --git a/packages/backend/src/server/api/endpoints/request-reset-password.ts b/packages/backend/src/server/api/endpoints/request-reset-password.ts index 046337f04..511a6bbb5 100644 --- a/packages/backend/src/server/api/endpoints/request-reset-password.ts +++ b/packages/backend/src/server/api/endpoints/request-reset-password.ts @@ -1,17 +1,21 @@ -import { publishMainStream } from '@/services/stream.js'; -import define from '../define.js'; import rndstr from 'rndstr'; -import config from '@/config/index.js'; import ms from 'ms'; +import { IsNull } from 'typeorm'; +import { publishMainStream } from '@/services/stream.js'; +import config from '@/config/index.js'; import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; import { sendEmail } from '@/services/send-email.js'; -import { ApiError } from '../error.js'; import { genId } from '@/misc/gen-id.js'; -import { IsNull } from 'typeorm'; +import { ApiError } from '../error.js'; +import define from '../define.js'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Request a users password to be reset.', + limit: { duration: ms('1hour'), max: 3, diff --git a/packages/backend/src/server/api/endpoints/reset-db.ts b/packages/backend/src/server/api/endpoints/reset-db.ts index dbe64e9a1..140f96d57 100644 --- a/packages/backend/src/server/api/endpoints/reset-db.ts +++ b/packages/backend/src/server/api/endpoints/reset-db.ts @@ -1,10 +1,14 @@ +import { resetDb } from '@/db/postgre.js'; import define from '../define.js'; import { ApiError } from '../error.js'; -import { resetDb } from '@/db/postgre.js'; export const meta = { + tags: ['non-productive'], + requireCredential: false, + description: 'Only available when running with NODE_ENV=testing. Reset the database and flush Redis.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/reset-password.ts b/packages/backend/src/server/api/endpoints/reset-password.ts index 7acc545c4..797169c2c 100644 --- a/packages/backend/src/server/api/endpoints/reset-password.ts +++ b/packages/backend/src/server/api/endpoints/reset-password.ts @@ -1,12 +1,16 @@ import bcrypt from 'bcryptjs'; import { publishMainStream } from '@/services/stream.js'; -import define from '../define.js'; import { Users, UserProfiles, PasswordResetRequests } from '@/models/index.js'; +import define from '../define.js'; import { ApiError } from '../error.js'; export const meta = { + tags: ['reset password'], + requireCredential: false, + description: 'Complete the password reset that was previously requested.', + errors: { }, diff --git a/packages/backend/src/server/api/endpoints/stats.ts b/packages/backend/src/server/api/endpoints/stats.ts index f8a1ee29d..cc94f8bf2 100644 --- a/packages/backend/src/server/api/endpoints/stats.ts +++ b/packages/backend/src/server/api/endpoints/stats.ts @@ -1,5 +1,5 @@ -import define from '../define.js'; import { Instances, NoteReactions, Notes, Users } from '@/models/index.js'; +import define from '../define.js'; import { } from '@/services/chart/index.js'; import { IsNull } from 'typeorm'; diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts index a48973a0d..437f8874f 100644 --- a/packages/backend/src/server/api/endpoints/sw/register.ts +++ b/packages/backend/src/server/api/endpoints/sw/register.ts @@ -1,13 +1,15 @@ -import define from '../../define.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; import { genId } from '@/misc/gen-id.js'; import { SwSubscriptions } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['account'], requireCredential: true, + description: 'Register to receive push notifications.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts index 9748f2a22..c19e06b87 100644 --- a/packages/backend/src/server/api/endpoints/sw/unregister.ts +++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts @@ -1,10 +1,12 @@ -import define from '../../define.js'; import { SwSubscriptions } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['account'], requireCredential: true, + + description: 'Unregister from receiving push notifications.', } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/test.ts b/packages/backend/src/server/api/endpoints/test.ts index 256da1a66..9949237a7 100644 --- a/packages/backend/src/server/api/endpoints/test.ts +++ b/packages/backend/src/server/api/endpoints/test.ts @@ -1,6 +1,10 @@ import define from '../define.js'; export const meta = { + tags: ['non-productive'], + + description: 'Endpoint for testing input validation.', + requireCredential: false, } as const; diff --git a/packages/backend/src/server/api/endpoints/username/available.ts b/packages/backend/src/server/api/endpoints/username/available.ts index 04b754f4a..3e41aeaed 100644 --- a/packages/backend/src/server/api/endpoints/username/available.ts +++ b/packages/backend/src/server/api/endpoints/username/available.ts @@ -1,6 +1,6 @@ -import define from '../../define.js'; -import { Users, UsedUsernames } from '@/models/index.js'; import { IsNull } from 'typeorm'; +import { Users, UsedUsernames } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['users'], diff --git a/packages/backend/src/server/api/endpoints/users.ts b/packages/backend/src/server/api/endpoints/users.ts index 10527d15c..2377faebd 100644 --- a/packages/backend/src/server/api/endpoints/users.ts +++ b/packages/backend/src/server/api/endpoints/users.ts @@ -1,5 +1,5 @@ -import define from '../define.js'; import { Users } from '@/models/index.js'; +import define from '../define.js'; import { generateMutedUserQueryForUsers } from '../common/generate-muted-user-query.js'; import { generateBlockQueryForUsers } from '../common/generate-block-query.js'; @@ -25,8 +25,8 @@ export const paramDef = { limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, offset: { type: 'integer', default: 0 }, sort: { type: 'string', enum: ['+follower', '-follower', '+createdAt', '-createdAt', '+updatedAt', '-updatedAt'] }, - state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: "all" }, - origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: "local" }, + state: { type: 'string', enum: ['all', 'admin', 'moderator', 'adminOrModerator', 'alive'], default: 'all' }, + origin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'local' }, }, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts index 424c59474..09fdf27c2 100644 --- a/packages/backend/src/server/api/endpoints/users/clips.ts +++ b/packages/backend/src/server/api/endpoints/users/clips.ts @@ -1,9 +1,21 @@ -import define from '../../define.js'; import { Clips } from '@/models/index.js'; +import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'clips'], + + description: 'Show all clips this user owns.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Clip', + }, + }, } as const; export const paramDef = { @@ -20,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const query = makePaginationQuery(Clips.createQueryBuilder('clip'), ps.sinceId, ps.untilId) - .andWhere(`clip.userId = :userId`, { userId: ps.userId }) + .andWhere('clip.userId = :userId', { userId: ps.userId }) .andWhere('clip.isPublic = true'); const clips = await query diff --git a/packages/backend/src/server/api/endpoints/users/followers.ts b/packages/backend/src/server/api/endpoints/users/followers.ts index 26b1f20df..7f9f98076 100644 --- a/packages/backend/src/server/api/endpoints/users/followers.ts +++ b/packages/backend/src/server/api/endpoints/users/followers.ts @@ -1,15 +1,17 @@ +import { IsNull } from 'typeorm'; +import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { toPunyNullable } from '@/misc/convert-host.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show everyone that follows this user.', + res: { type: 'array', optional: false, nullable: false, @@ -94,7 +96,7 @@ export default define(meta, paramDef, async (ps, me) => { } const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followeeId = :userId`, { userId: user.id }) + .andWhere('following.followeeId = :userId', { userId: user.id }) .innerJoinAndSelect('following.follower', 'follower'); const followings = await query diff --git a/packages/backend/src/server/api/endpoints/users/following.ts b/packages/backend/src/server/api/endpoints/users/following.ts index 42cf5216e..0aaa810f7 100644 --- a/packages/backend/src/server/api/endpoints/users/following.ts +++ b/packages/backend/src/server/api/endpoints/users/following.ts @@ -1,15 +1,17 @@ +import { IsNull } from 'typeorm'; +import { Users, Followings, UserProfiles } from '@/models/index.js'; +import { toPunyNullable } from '@/misc/convert-host.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { Users, Followings, UserProfiles } from '@/models/index.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; -import { toPunyNullable } from '@/misc/convert-host.js'; -import { IsNull } from 'typeorm'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show everyone that this user is following.', + res: { type: 'array', optional: false, nullable: false, @@ -94,7 +96,7 @@ export default define(meta, paramDef, async (ps, me) => { } const query = makePaginationQuery(Followings.createQueryBuilder('following'), ps.sinceId, ps.untilId) - .andWhere(`following.followerId = :userId`, { userId: user.id }) + .andWhere('following.followerId = :userId', { userId: user.id }) .innerJoinAndSelect('following.followee', 'followee'); const followings = await query diff --git a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts index d7c435256..35bf2df59 100644 --- a/packages/backend/src/server/api/endpoints/users/gallery/posts.ts +++ b/packages/backend/src/server/api/endpoints/users/gallery/posts.ts @@ -4,6 +4,18 @@ import { makePaginationQuery } from '../../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'gallery'], + + description: 'Show all gallery posts by the given user.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'GalleryPost', + }, + }, } as const; export const paramDef = { diff --git a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts index 73cadc0df..56965d306 100644 --- a/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts +++ b/packages/backend/src/server/api/endpoints/users/get-frequently-replied-users.ts @@ -1,15 +1,17 @@ -import define from '../../define.js'; +import { Not, In, IsNull } from 'typeorm'; import { maximum } from '@/prelude/array.js'; +import { Notes, Users } from '@/models/index.js'; +import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; -import { Not, In, IsNull } from 'typeorm'; -import { Notes, Users } from '@/models/index.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Get a list of other users that the specified user frequently replies to.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/create.ts b/packages/backend/src/server/api/endpoints/users/groups/create.ts index fc775d7cc..4a6362a3c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/create.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/create.ts @@ -1,8 +1,8 @@ -import define from '../../../define.js'; import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { UserGroup } from '@/models/entities/user-group.js'; import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; +import define from '../../../define.js'; export const meta = { tags: ['groups'], @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Create a new group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/delete.ts b/packages/backend/src/server/api/endpoints/users/groups/delete.ts index f68006994..2ff1f9aec 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/delete.ts @@ -1,6 +1,6 @@ +import { UserGroups } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserGroups } from '@/models/index.js'; export const meta = { tags: ['groups'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts index 75c1acc30..220fff5f3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/accept.ts @@ -1,8 +1,8 @@ -import define from '../../../../define.js'; -import { ApiError } from '../../../../error.js'; import { UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { UserGroupJoining } from '@/models/entities/user-group-joining.js'; +import { ApiError } from '../../../../error.js'; +import define from '../../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -11,6 +11,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Join a group the authenticated user has been invited to.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts index 46bc780ab..8d1d3db73 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invitations/reject.ts @@ -1,6 +1,6 @@ +import { UserGroupInvitations } from '@/models/index.js'; import define from '../../../../define.js'; import { ApiError } from '../../../../error.js'; -import { UserGroupInvitations } from '@/models/index.js'; export const meta = { tags: ['groups', 'users'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Delete an existing group invitation for the authenticated user without joining the group.', + errors: { noSuchInvitation: { message: 'No such invitation.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/invite.ts b/packages/backend/src/server/api/endpoints/users/groups/invite.ts index 30a5beb1d..1a8d320f3 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/invite.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/invite.ts @@ -1,10 +1,10 @@ -import define from '../../../define.js'; -import { ApiError } from '../../../error.js'; -import { getUser } from '../../../common/getters.js'; import { UserGroups, UserGroupJoinings, UserGroupInvitations } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { UserGroupInvitation } from '@/models/entities/user-group-invitation.js'; import { createNotification } from '@/services/create-notification.js'; +import { getUser } from '../../../common/getters.js'; +import { ApiError } from '../../../error.js'; +import define from '../../../define.js'; export const meta = { tags: ['groups', 'users'], @@ -13,6 +13,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Invite a user to an existing group.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/joined.ts b/packages/backend/src/server/api/endpoints/users/groups/joined.ts index 77dc59d3e..16c6e544e 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/joined.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/joined.ts @@ -1,6 +1,6 @@ -import define from '../../../define.js'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import { Not, In } from 'typeorm'; +import { UserGroups, UserGroupJoinings } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['groups', 'account'], @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is a member of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/leave.ts b/packages/backend/src/server/api/endpoints/users/groups/leave.ts index 33abd5439..83dc757db 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/leave.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/leave.ts @@ -1,6 +1,6 @@ +import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; export const meta = { tags: ['groups', 'users'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Leave a group. The owner of a group can not leave. They must transfer ownership or delete the group instead.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/owned.ts b/packages/backend/src/server/api/endpoints/users/groups/owned.ts index b1289e601..d77cf1a52 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/owned.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/owned.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import { UserGroups } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['groups', 'account'], @@ -8,6 +8,8 @@ export const meta = { kind: 'read:user-groups', + description: 'List the groups that the authenticated user is the owner of.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/pull.ts b/packages/backend/src/server/api/endpoints/users/groups/pull.ts index b31990b2e..ba67a1e5c 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/pull.ts @@ -1,7 +1,7 @@ +import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getUser } from '../../../common/getters.js'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; export const meta = { tags: ['groups', 'users'], @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Removes a specified user from a group. The owner can not be removed.', + errors: { noSuchGroup: { message: 'No such group.', diff --git a/packages/backend/src/server/api/endpoints/users/groups/show.ts b/packages/backend/src/server/api/endpoints/users/groups/show.ts index 3ffb0f5ba..21e3d9da2 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/show.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/show.ts @@ -1,6 +1,6 @@ +import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; export const meta = { tags: ['groups', 'account'], @@ -9,6 +9,8 @@ export const meta = { kind: 'read:user-groups', + description: 'Show the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts index 41ceee3b2..6456e70dd 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/transfer.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/transfer.ts @@ -1,7 +1,7 @@ +import { UserGroups, UserGroupJoinings } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getUser } from '../../../common/getters.js'; -import { UserGroups, UserGroupJoinings } from '@/models/index.js'; export const meta = { tags: ['groups', 'users'], @@ -10,6 +10,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Transfer ownership of a group from the authenticated user to another user.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/groups/update.ts b/packages/backend/src/server/api/endpoints/users/groups/update.ts index 1016aa892..0a96165fc 100644 --- a/packages/backend/src/server/api/endpoints/users/groups/update.ts +++ b/packages/backend/src/server/api/endpoints/users/groups/update.ts @@ -1,6 +1,6 @@ +import { UserGroups } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserGroups } from '@/models/index.js'; export const meta = { tags: ['groups'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:user-groups', + description: 'Update the properties of a group.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/create.ts b/packages/backend/src/server/api/endpoints/users/lists/create.ts index d5260256d..783e63f5d 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/create.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/create.ts @@ -1,7 +1,7 @@ -import define from '../../../define.js'; import { UserLists } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { UserList } from '@/models/entities/user-list.js'; +import define from '../../../define.js'; export const meta = { tags: ['lists'], @@ -10,6 +10,8 @@ export const meta = { kind: 'write:account', + description: 'Create a new list of users.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/delete.ts b/packages/backend/src/server/api/endpoints/users/lists/delete.ts index b7ad96eef..5a7613c98 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/delete.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/delete.ts @@ -1,6 +1,6 @@ +import { UserLists } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserLists } from '@/models/index.js'; export const meta = { tags: ['lists'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Delete an existing list of users.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/list.ts b/packages/backend/src/server/api/endpoints/users/lists/list.ts index 78311292c..889052fa3 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/list.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/list.ts @@ -1,5 +1,5 @@ -import define from '../../../define.js'; import { UserLists } from '@/models/index.js'; +import define from '../../../define.js'; export const meta = { tags: ['lists', 'account'], @@ -8,6 +8,8 @@ export const meta = { kind: 'read:account', + description: 'Show all lists that the authenticated user has created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/pull.ts b/packages/backend/src/server/api/endpoints/users/lists/pull.ts index 76863f07d..d3d1d6555 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/pull.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/pull.ts @@ -1,8 +1,8 @@ import { publishUserListStream } from '@/services/stream.js'; +import { UserLists, UserListJoinings, Users } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getUser } from '../../../common/getters.js'; -import { UserLists, UserListJoinings, Users } from '@/models/index.js'; export const meta = { tags: ['lists', 'users'], @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Remove a user from a list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/push.ts b/packages/backend/src/server/api/endpoints/users/lists/push.ts index 260665c63..12b7b8634 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/push.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/push.ts @@ -1,8 +1,8 @@ +import { pushUserToUserList } from '@/services/user-list/push.js'; +import { UserLists, UserListJoinings, Blockings } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; import { getUser } from '../../../common/getters.js'; -import { pushUserToUserList } from '@/services/user-list/push.js'; -import { UserLists, UserListJoinings, Blockings } from '@/models/index.js'; export const meta = { tags: ['lists', 'users'], @@ -11,6 +11,8 @@ export const meta = { kind: 'write:account', + description: 'Add a user to an existing list.', + errors: { noSuchList: { message: 'No such list.', diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts index 5f51980e9..fd0612f73 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/show.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts @@ -1,6 +1,6 @@ +import { UserLists } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserLists } from '@/models/index.js'; export const meta = { tags: ['lists', 'account'], @@ -9,6 +9,8 @@ export const meta = { kind: 'read:account', + description: 'Show the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/lists/update.ts b/packages/backend/src/server/api/endpoints/users/lists/update.ts index 52353a14c..65e708b95 100644 --- a/packages/backend/src/server/api/endpoints/users/lists/update.ts +++ b/packages/backend/src/server/api/endpoints/users/lists/update.ts @@ -1,6 +1,6 @@ +import { UserLists } from '@/models/index.js'; import define from '../../../define.js'; import { ApiError } from '../../../error.js'; -import { UserLists } from '@/models/index.js'; export const meta = { tags: ['lists'], @@ -9,6 +9,8 @@ export const meta = { kind: 'write:account', + description: 'Update the properties of a list.', + res: { type: 'object', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 16318d222..aec5c0ea9 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -1,17 +1,19 @@ +import { Brackets } from 'typeorm'; +import { Notes } from '@/models/index.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; import { getUser } from '../../common/getters.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; -import { Notes } from '@/models/index.js'; import { generateMutedUserQuery } from '../../common/generate-muted-user-query.js'; -import { Brackets } from 'typeorm'; import { generateBlockedUserQuery } from '../../common/generate-block-query.js'; import { generateMutedInstanceQuery } from '../../common/generate-muted-instance-query.js'; export const meta = { tags: ['users', 'notes'], + description: 'Show all notes that this user created.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/pages.ts b/packages/backend/src/server/api/endpoints/users/pages.ts index b8b3e8192..b1d28af84 100644 --- a/packages/backend/src/server/api/endpoints/users/pages.ts +++ b/packages/backend/src/server/api/endpoints/users/pages.ts @@ -1,9 +1,21 @@ -import define from '../../define.js'; import { Pages } from '@/models/index.js'; +import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; export const meta = { tags: ['users', 'pages'], + + description: 'Show all pages this user created.', + + res: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'object', + optional: false, nullable: false, + ref: 'Page', + }, + }, } as const; export const paramDef = { @@ -20,7 +32,7 @@ export const paramDef = { // eslint-disable-next-line import/no-default-export export default define(meta, paramDef, async (ps, user) => { const query = makePaginationQuery(Pages.createQueryBuilder('page'), ps.sinceId, ps.untilId) - .andWhere(`page.userId = :userId`, { userId: ps.userId }) + .andWhere('page.userId = :userId', { userId: ps.userId }) .andWhere('page.visibility = \'public\''); const pages = await query diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts index c2d199434..9668bd21b 100644 --- a/packages/backend/src/server/api/endpoints/users/reactions.ts +++ b/packages/backend/src/server/api/endpoints/users/reactions.ts @@ -1,5 +1,5 @@ -import define from '../../define.js'; import { NoteReactions, UserProfiles } from '@/models/index.js'; +import define from '../../define.js'; import { makePaginationQuery } from '../../common/make-pagination-query.js'; import { generateVisibilityQuery } from '../../common/generate-visibility-query.js'; import { ApiError } from '../../error.js'; @@ -9,6 +9,8 @@ export const meta = { requireCredential: false, + description: 'Show all reactions this user made.', + res: { type: 'array', optional: false, nullable: false, @@ -50,8 +52,8 @@ export default define(meta, paramDef, async (ps, me) => { } const query = makePaginationQuery(NoteReactions.createQueryBuilder('reaction'), - ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) - .andWhere(`reaction.userId = :userId`, { userId: ps.userId }) + ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) + .andWhere('reaction.userId = :userId', { userId: ps.userId }) .leftJoinAndSelect('reaction.note', 'note'); generateVisibilityQuery(query, me); diff --git a/packages/backend/src/server/api/endpoints/users/recommendation.ts b/packages/backend/src/server/api/endpoints/users/recommendation.ts index a8f18de52..e7654e171 100644 --- a/packages/backend/src/server/api/endpoints/users/recommendation.ts +++ b/packages/backend/src/server/api/endpoints/users/recommendation.ts @@ -1,6 +1,6 @@ import ms from 'ms'; -import define from '../../define.js'; import { Users, Followings } from '@/models/index.js'; +import define from '../../define.js'; import { generateMutedUserQueryForUsers } from '../../common/generate-muted-user-query.js'; import { generateBlockedUserQuery, generateBlockQueryForUsers } from '../../common/generate-block-query.js'; @@ -11,6 +11,8 @@ export const meta = { kind: 'read:account', + description: 'Show users that the authenticated user might be interested to follow.', + res: { type: 'array', optional: false, nullable: false, diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts index c6262122d..233a6a90b 100644 --- a/packages/backend/src/server/api/endpoints/users/relation.ts +++ b/packages/backend/src/server/api/endpoints/users/relation.ts @@ -1,11 +1,13 @@ -import define from '../../define.js'; import { Users } from '@/models/index.js'; +import define from '../../define.js'; export const meta = { tags: ['users'], requireCredential: true, + description: 'Show the different kinds of relations between the authenticated user and the specified user(s).', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/report-abuse.ts b/packages/backend/src/server/api/endpoints/users/report-abuse.ts index 0be385dbb..a9987eafa 100644 --- a/packages/backend/src/server/api/endpoints/users/report-abuse.ts +++ b/packages/backend/src/server/api/endpoints/users/report-abuse.ts @@ -1,18 +1,20 @@ import * as sanitizeHtml from 'sanitize-html'; -import define from '../../define.js'; import { publishAdminStream } from '@/services/stream.js'; -import { ApiError } from '../../error.js'; -import { getUser } from '../../common/getters.js'; import { AbuseUserReports, Users } from '@/models/index.js'; import { genId } from '@/misc/gen-id.js'; import { sendEmail } from '@/services/send-email.js'; import { fetchMeta } from '@/misc/fetch-meta.js'; +import { getUser } from '../../common/getters.js'; +import { ApiError } from '../../error.js'; +import define from '../../define.js'; export const meta = { tags: ['users'], requireCredential: true, + description: 'File a report.', + errors: { noSuchUser: { message: 'No such user.', diff --git a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts index f74d80e2a..6e5bc46bb 100644 --- a/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts +++ b/packages/backend/src/server/api/endpoints/users/search-by-username-and-host.ts @@ -1,14 +1,16 @@ -import define from '../../define.js'; -import { Followings, Users } from '@/models/index.js'; import { Brackets } from 'typeorm'; +import { Followings, Users } from '@/models/index.js'; import { USER_ACTIVE_THRESHOLD } from '@/const.js'; import { User } from '@/models/entities/user.js'; +import define from '../../define.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Search for a user by username and/or host.', + res: { type: 'array', optional: false, nullable: false, @@ -65,7 +67,7 @@ export default define(meta, paramDef, async (ps, me) => { const query = Users.createQueryBuilder('user') .where(`user.id IN (${ followingQuery.getQuery() })`) - .andWhere(`user.id != :meId`, { meId: me.id }) + .andWhere('user.id != :meId', { meId: me.id }) .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) .andWhere(new Brackets(qb => { qb @@ -83,7 +85,7 @@ export default define(meta, paramDef, async (ps, me) => { if (users.length < ps.limit) { const otherQuery = await Users.createQueryBuilder('user') .where(`user.id NOT IN (${ followingQuery.getQuery() })`) - .andWhere(`user.id != :meId`, { meId: me.id }) + .andWhere('user.id != :meId', { meId: me.id }) .andWhere('user.isSuspended = FALSE') .andWhere('user.usernameLower LIKE :username', { username: ps.username.toLowerCase() + '%' }) .andWhere('user.updatedAt IS NOT NULL'); diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts index f93d4f718..01729de66 100644 --- a/packages/backend/src/server/api/endpoints/users/search.ts +++ b/packages/backend/src/server/api/endpoints/users/search.ts @@ -1,13 +1,15 @@ -import define from '../../define.js'; +import { Brackets } from 'typeorm'; import { UserProfiles, Users } from '@/models/index.js'; import { User } from '@/models/entities/user.js'; -import { Brackets } from 'typeorm'; +import define from '../../define.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Search for users.', + res: { type: 'array', optional: false, nullable: false, @@ -25,7 +27,7 @@ export const paramDef = { query: { type: 'string' }, offset: { type: 'integer', default: 0 }, limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, - origin: { type: 'string', enum: ['local', 'remote', 'combined'], default: "combined" }, + origin: { type: 'string', enum: ['local', 'remote', 'combined'], default: 'combined' }, detail: { type: 'boolean', default: true }, }, required: ['query'], @@ -111,7 +113,7 @@ export default define(meta, paramDef, async (ps, me) => { .orderBy('user.updatedAt', 'DESC', 'NULLS LAST') .take(ps.limit) .skip(ps.offset) - .getMany() + .getMany(), ); } } diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts index 183ff1b8b..846d83b49 100644 --- a/packages/backend/src/server/api/endpoints/users/show.ts +++ b/packages/backend/src/server/api/endpoints/users/show.ts @@ -1,16 +1,18 @@ +import { FindOptionsWhere, In, IsNull } from 'typeorm'; import { resolveUser } from '@/remote/resolve-user.js'; +import { Users } from '@/models/index.js'; +import { User } from '@/models/entities/user.js'; import define from '../../define.js'; import { apiLogger } from '../../logger.js'; import { ApiError } from '../../error.js'; -import { Users } from '@/models/index.js'; -import { FindOptionsWhere, In, IsNull } from 'typeorm'; -import { User } from '@/models/entities/user.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show the properties of a user.', + res: { optional: false, nullable: false, oneOf: [ diff --git a/packages/backend/src/server/api/endpoints/users/stats.ts b/packages/backend/src/server/api/endpoints/users/stats.ts index d138019a7..47f322ee9 100644 --- a/packages/backend/src/server/api/endpoints/users/stats.ts +++ b/packages/backend/src/server/api/endpoints/users/stats.ts @@ -1,12 +1,15 @@ +import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; +import { awaitAll } from '@/prelude/await-all.js'; import define from '../../define.js'; import { ApiError } from '../../error.js'; -import { DriveFiles, Followings, NoteFavorites, NoteReactions, Notes, PageLikes, PollVotes, Users } from '@/models/index.js'; export const meta = { tags: ['users'], requireCredential: false, + description: 'Show statistics about a user.', + errors: { noSuchUser: { message: 'No such user.', @@ -14,6 +17,94 @@ export const meta = { id: '9e638e45-3b25-4ef7-8f95-07e8498f1819', }, }, + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + notesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliesCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + repliedCount: { + type: 'integer', + optional: false, nullable: false, + }, + renotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pollVotedCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowingCount: { + type: 'integer', + optional: false, nullable: false, + }, + localFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + remoteFollowersCount: { + type: 'integer', + optional: false, nullable: false, + }, + followingCount: { + type: 'integer', + optional: false, nullable: false, + }, + followersCount: { + type: 'integer', + optional: false, nullable: false, + }, + sentReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + receivedReactionsCount: { + type: 'integer', + optional: false, nullable: false, + }, + noteFavoritesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikesCount: { + type: 'integer', + optional: false, nullable: false, + }, + pageLikedCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveFilesCount: { + type: 'integer', + optional: false, nullable: false, + }, + driveUsage: { + type: 'integer', + optional: false, nullable: false, + description: 'Drive usage in bytes', + }, + }, + }, } as const; export const paramDef = { @@ -31,109 +122,72 @@ export default define(meta, paramDef, async (ps, me) => { throw new ApiError(meta.errors.noSuchUser); } - const [ - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - ] = await Promise.all([ - Notes.createQueryBuilder('note') + const result = await awaitAll({ + notesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + repliesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.replyId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + renotesCount: Notes.createQueryBuilder('note') .where('note.userId = :userId', { userId: user.id }) .andWhere('note.renoteId IS NOT NULL') .getCount(), - Notes.createQueryBuilder('note') + repliedCount: Notes.createQueryBuilder('note') .where('note.replyUserId = :userId', { userId: user.id }) .getCount(), - Notes.createQueryBuilder('note') + renotedCount: Notes.createQueryBuilder('note') .where('note.renoteUserId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotesCount: PollVotes.createQueryBuilder('vote') .where('vote.userId = :userId', { userId: user.id }) .getCount(), - PollVotes.createQueryBuilder('vote') + pollVotedCount: PollVotes.createQueryBuilder('vote') .innerJoin('vote.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - Followings.createQueryBuilder('following') + localFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowingCount: Followings.createQueryBuilder('following') .where('following.followerId = :userId', { userId: user.id }) .andWhere('following.followeeHost IS NOT NULL') .getCount(), - Followings.createQueryBuilder('following') + localFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NULL') .getCount(), - Followings.createQueryBuilder('following') + remoteFollowersCount: Followings.createQueryBuilder('following') .where('following.followeeId = :userId', { userId: user.id }) .andWhere('following.followerHost IS NOT NULL') .getCount(), - NoteReactions.createQueryBuilder('reaction') + sentReactionsCount: NoteReactions.createQueryBuilder('reaction') .where('reaction.userId = :userId', { userId: user.id }) .getCount(), - NoteReactions.createQueryBuilder('reaction') + receivedReactionsCount: NoteReactions.createQueryBuilder('reaction') .innerJoin('reaction.note', 'note') .where('note.userId = :userId', { userId: user.id }) .getCount(), - NoteFavorites.createQueryBuilder('favorite') + noteFavoritesCount: NoteFavorites.createQueryBuilder('favorite') .where('favorite.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikesCount: PageLikes.createQueryBuilder('like') .where('like.userId = :userId', { userId: user.id }) .getCount(), - PageLikes.createQueryBuilder('like') + pageLikedCount: PageLikes.createQueryBuilder('like') .innerJoin('like.page', 'page') .where('page.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.createQueryBuilder('file') + driveFilesCount: DriveFiles.createQueryBuilder('file') .where('file.userId = :userId', { userId: user.id }) .getCount(), - DriveFiles.calcDriveUsageOf(user), - ]); + driveUsage: DriveFiles.calcDriveUsageOf(user), + }); - return { - notesCount, - repliesCount, - renotesCount, - repliedCount, - renotedCount, - pollVotesCount, - pollVotedCount, - localFollowingCount, - remoteFollowingCount, - localFollowersCount, - remoteFollowersCount, - followingCount: localFollowingCount + remoteFollowingCount, - followersCount: localFollowersCount + remoteFollowersCount, - sentReactionsCount, - receivedReactionsCount, - noteFavoritesCount, - pageLikesCount, - pageLikedCount, - driveFilesCount, - driveUsage, - }; + result.followingCount = result.localFollowingCount + result.remoteFollowingCount; + result.followersCount = result.localFollowersCount + result.remoteFollowersCount; + + return result; }); diff --git a/packages/backend/src/server/api/limiter.ts b/packages/backend/src/server/api/limiter.ts index 23430cf8b..6ca3ebf18 100644 --- a/packages/backend/src/server/api/limiter.ts +++ b/packages/backend/src/server/api/limiter.ts @@ -1,8 +1,8 @@ import Limiter from 'ratelimiter'; -import { redisClient } from '../../db/redis.js'; -import { IEndpointMeta } from './endpoints.js'; import { CacheableLocalUser, User } from '@/models/entities/user.js'; import Logger from '@/services/logger.js'; +import { redisClient } from '../../db/redis.js'; +import { IEndpointMeta } from './endpoints.js'; const logger = new Logger('limiter'); diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts index 3929fff3f..68fa81404 100644 --- a/packages/backend/src/server/api/openapi/gen-spec.ts +++ b/packages/backend/src/server/api/openapi/gen-spec.ts @@ -3,7 +3,7 @@ import config from '@/config/index.js'; import { errors as basicErrors } from './errors.js'; import { schemas, convertSchemaToOpenApiSchema } from './schemas.js'; -export function genOpenapiSpec(lang = 'ja-JP') { +export function genOpenapiSpec() { const spec = { openapi: '3.0.0', diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 230ed1578..5bb156f0f 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -7,17 +7,15 @@ doctype html // - - _____ _ _ | |_|___ ___| |_ ___ _ _ - | | | | |_ -|_ -| \'_| -_| | | + | | | | |_ -|_ -| '_| -_| | | |_|_|_|_|___|___|_,_|___|_ | |___| Thank you for using Misskey! If you are reading this message... how about joining the development? https://github.com/misskey-dev/misskey - html diff --git a/packages/backend/src/services/chart/entities.ts b/packages/backend/src/services/chart/entities.ts index 13e994cb6..a9eeabd63 100644 --- a/packages/backend/src/services/chart/entities.ts +++ b/packages/backend/src/services/chart/entities.ts @@ -11,6 +11,11 @@ import { entity as PerUserFollowingChart } from './charts/entities/per-user-foll import { entity as PerUserDriveChart } from './charts/entities/per-user-drive.js'; import { entity as ApRequestChart } from './charts/entities/ap-request.js'; +import { entity as TestChart } from './charts/entities/test.js'; +import { entity as TestGroupedChart } from './charts/entities/test-grouped.js'; +import { entity as TestUniqueChart } from './charts/entities/test-unique.js'; +import { entity as TestIntersectionChart } from './charts/entities/test-intersection.js'; + export const entities = [ FederationChart.hour, FederationChart.day, NotesChart.hour, NotesChart.day, @@ -24,4 +29,11 @@ export const entities = [ PerUserFollowingChart.hour, PerUserFollowingChart.day, PerUserDriveChart.hour, PerUserDriveChart.day, ApRequestChart.hour, ApRequestChart.day, + + ...(process.env.NODE_ENV === 'test' ? [ + TestChart.hour, TestChart.day, + TestGroupedChart.hour, TestGroupedChart.day, + TestUniqueChart.hour, TestUniqueChart.day, + TestIntersectionChart.hour, TestIntersectionChart.day, + ] : []), ]; diff --git a/packages/backend/src/services/drive/generate-video-thumbnail.ts b/packages/backend/src/services/drive/generate-video-thumbnail.ts index ca12ab8d3..6e6666481 100644 --- a/packages/backend/src/services/drive/generate-video-thumbnail.ts +++ b/packages/backend/src/services/drive/generate-video-thumbnail.ts @@ -1,12 +1,10 @@ import * as fs from 'node:fs'; -import * as path from 'node:path'; -import { createTemp } from '@/misc/create-temp.js'; +import { createTempDir } from '@/misc/create-temp.js'; import { IImage, convertToJpeg } from './image-processor.js'; import FFmpeg from 'fluent-ffmpeg'; export async function GenerateVideoThumbnail(source: string): Promise { - const [file, cleanup] = await createTemp(); - const parsed = path.parse(file); + const [dir, cleanup] = await createTempDir(); try { await new Promise((res, rej) => { @@ -16,15 +14,15 @@ export async function GenerateVideoThumbnail(source: string): Promise { .on('end', res) .on('error', rej) .screenshot({ - folder: parsed.dir, - filename: parsed.base, + folder: dir, + filename: 'out.png', // must have .png extension count: 1, timestamps: ['5%'], }); }); // JPEGに変換 (Webpでもいいが、MastodonはWebpをサポートせず表示できなくなる) - return await convertToJpeg(498, 280); + return await convertToJpeg(`${dir}/out.png`, 498, 280); } finally { cleanup(); } diff --git a/packages/backend/test/chart.ts b/packages/backend/test/chart.ts index 823e388a8..ac0844679 100644 --- a/packages/backend/test/chart.ts +++ b/packages/backend/test/chart.ts @@ -6,26 +6,17 @@ import TestChart from '../src/services/chart/charts/test.js'; import TestGroupedChart from '../src/services/chart/charts/test-grouped.js'; import TestUniqueChart from '../src/services/chart/charts/test-unique.js'; import TestIntersectionChart from '../src/services/chart/charts/test-intersection.js'; -import * as _TestChart from '../src/services/chart/charts/entities/test.js'; -import * as _TestGroupedChart from '../src/services/chart/charts/entities/test-grouped.js'; -import * as _TestUniqueChart from '../src/services/chart/charts/entities/test-unique.js'; -import * as _TestIntersectionChart from '../src/services/chart/charts/entities/test-intersection.js'; -import { async, initTestDb } from './utils.js'; +import { initDb } from '../src/db/postgre.js'; describe('Chart', () => { let testChart: TestChart; let testGroupedChart: TestGroupedChart; let testUniqueChart: TestUniqueChart; let testIntersectionChart: TestIntersectionChart; - let clock: lolex.Clock; + let clock: lolex.InstalledClock; - beforeEach(async(async () => { - await initTestDb(false, [ - _TestChart.entity.hour, _TestChart.entity.day, - _TestGroupedChart.entity.hour, _TestGroupedChart.entity.day, - _TestUniqueChart.entity.hour, _TestUniqueChart.entity.day, - _TestIntersectionChart.entity.hour, _TestIntersectionChart.entity.day, - ]); + beforeEach(async () => { + await initDb(true); testChart = new TestChart(); testGroupedChart = new TestGroupedChart(); @@ -34,14 +25,15 @@ describe('Chart', () => { clock = lolex.install({ now: new Date(Date.UTC(2000, 0, 1, 0, 0, 0)), + shouldClearNativeTimers: true, }); - })); + }); - afterEach(async(async () => { + afterEach(() => { clock.uninstall(); - })); + }); - it('Can updates', async(async () => { + it('Can updates', async () => { await testChart.increment(); await testChart.save(); @@ -63,9 +55,9 @@ describe('Chart', () => { total: [1, 0, 0], }, }); - })); + }); - it('Can updates (dec)', async(async () => { + it('Can updates (dec)', async () => { await testChart.decrement(); await testChart.save(); @@ -87,9 +79,9 @@ describe('Chart', () => { total: [-1, 0, 0], }, }); - })); + }); - it('Empty chart', async(async () => { + it('Empty chart', async () => { const chartHours = await testChart.getChart('hour', 3, null); const chartDays = await testChart.getChart('day', 3, null); @@ -108,9 +100,9 @@ describe('Chart', () => { total: [0, 0, 0], }, }); - })); + }); - it('Can updates at multiple times at same time', async(async () => { + it('Can updates at multiple times at same time', async () => { await testChart.increment(); await testChart.increment(); await testChart.increment(); @@ -134,9 +126,9 @@ describe('Chart', () => { total: [3, 0, 0], }, }); - })); + }); - it('複数回saveされてもデータの更新は一度だけ', async(async () => { + it('複数回saveされてもデータの更新は一度だけ', async () => { await testChart.increment(); await testChart.save(); await testChart.save(); @@ -160,9 +152,9 @@ describe('Chart', () => { total: [1, 0, 0], }, }); - })); + }); - it('Can updates at different times', async(async () => { + it('Can updates at different times', async () => { await testChart.increment(); await testChart.save(); @@ -189,11 +181,11 @@ describe('Chart', () => { total: [2, 0, 0], }, }); - })); + }); // 仕様上はこうなってほしいけど、実装は難しそうなのでskip /* - it('Can updates at different times without save', async(async () => { + it('Can updates at different times without save', async () => { await testChart.increment(); clock.tick('01:00:00'); @@ -219,10 +211,10 @@ describe('Chart', () => { total: [2, 0, 0] }, }); - })); + }); */ - it('Can padding', async(async () => { + it('Can padding', async () => { await testChart.increment(); await testChart.save(); @@ -249,10 +241,10 @@ describe('Chart', () => { total: [2, 0, 0], }, }); - })); + }); // 要求された範囲にログがひとつもない場合でもパディングできる - it('Can padding from past range', async(async () => { + it('Can padding from past range', async () => { await testChart.increment(); await testChart.save(); @@ -276,11 +268,11 @@ describe('Chart', () => { total: [1, 0, 0], }, }); - })); + }); // 要求された範囲の最も古い箇所に位置するログが存在しない場合でもパディングできる // Issue #3190 - it('Can padding from past range 2', async(async () => { + it('Can padding from past range 2', async () => { await testChart.increment(); await testChart.save(); @@ -307,9 +299,9 @@ describe('Chart', () => { total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset', async(async () => { + it('Can specify offset', async () => { await testChart.increment(); await testChart.save(); @@ -336,9 +328,9 @@ describe('Chart', () => { total: [2, 0, 0], }, }); - })); + }); - it('Can specify offset (floor time)', async(async () => { + it('Can specify offset (floor time)', async () => { clock.tick('00:30:00'); await testChart.increment(); @@ -367,10 +359,10 @@ describe('Chart', () => { total: [2, 0, 0], }, }); - })); + }); describe('Grouped', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testGroupedChart.increment('alice'); await testGroupedChart.save(); @@ -410,11 +402,11 @@ describe('Chart', () => { total: [0, 0, 0], }, }); - })); + }); }); describe('Unique increment', () => { - it('Can updates', async(async () => { + it('Can updates', async () => { await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('alice'); await testUniqueChart.uniqueIncrement('bob'); @@ -430,10 +422,10 @@ describe('Chart', () => { assert.deepStrictEqual(chartDays, { foo: [2, 0, 0], }); - })); + }); describe('Intersection', () => { - it('条件が満たされていない場合はカウントされない', async(async () => { + it('条件が満たされていない場合はカウントされない', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -453,9 +445,9 @@ describe('Chart', () => { b: [1, 0, 0], aAndB: [0, 0, 0], }); - })); + }); - it('条件が満たされている場合にカウントされる', async(async () => { + it('条件が満たされている場合にカウントされる', async () => { await testIntersectionChart.addA('alice'); await testIntersectionChart.addA('bob'); await testIntersectionChart.addB('carol'); @@ -476,12 +468,12 @@ describe('Chart', () => { b: [2, 0, 0], aAndB: [1, 0, 0], }); - })); + }); }); }); describe('Resync', () => { - it('Can resync', async(async () => { + it('Can resync', async () => { testChart.total = 1; await testChart.resync(); @@ -504,9 +496,9 @@ describe('Chart', () => { total: [1, 0, 0], }, }); - })); + }); - it('Can resync (2)', async(async () => { + it('Can resync (2)', async () => { await testChart.increment(); await testChart.save(); @@ -534,6 +526,6 @@ describe('Chart', () => { total: [100, 0, 0], }, }); - })); + }); }); }); diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js index 1c2ab0a42..10f0e5a9c 100644 --- a/packages/client/.eslintrc.js +++ b/packages/client/.eslintrc.js @@ -15,6 +15,12 @@ module.exports = { 'plugin:vue/vue3-recommended', ], rules: { + '@typescript-eslint/no-empty-interface': [ + 'error', + { + 'allowSingleExtends': true, + }, + ], // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため // data の禁止理由: 抽象的すぎるため // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため diff --git a/packages/client/package.json b/packages/client/package.json index a53dc0cf5..a891fbd0e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -19,13 +19,13 @@ "@rollup/pluginutils": "^4.2.1", "@syuilo/aiscript": "0.11.1", "@vitejs/plugin-vue": "2.3.3", - "@vue/compiler-sfc": "3.2.36", + "@vue/compiler-sfc": "3.2.37", "abort-controller": "3.0.0", "autobind-decorator": "2.4.0", "autosize": "5.0.1", "autwh": "0.1.0", "blurhash": "1.1.5", - "broadcast-channel": "4.12.0", + "broadcast-channel": "4.13.0", "browser-image-resizer": "misskey-dev/browser-image-resizer#tag=v2.2.1-misskey.2", "chart.js": "3.8.0", "chartjs-adapter-date-fns": "2.0.0", @@ -33,7 +33,8 @@ "chartjs-plugin-zoom": "1.2.1", "compare-versions": "4.1.3", "content-disposition": "0.5.4", - "date-fns": "^2.28.0", + "cropperjs": "2.0.0-beta", + "date-fns": "2.28.0", "escape-regexp": "0.0.1", "eventemitter3": "4.0.7", "feed": "4.2.2", @@ -58,9 +59,9 @@ "random-seed": "0.3.0", "reflect-metadata": "0.1.13", "rndstr": "1.0.0", - "rollup": "2.75.3", + "rollup": "2.75.6", "s-age": "1.1.2", - "sass": "1.52.1", + "sass": "1.52.3", "seedrandom": "3.0.5", "strict-event-emitter-types": "2.0.0", "stringz": "2.1.0", @@ -69,20 +70,20 @@ "three": "0.141.0", "throttle-debounce": "5.0.0", "tinycolor2": "1.4.2", - "tsc-alias": "1.6.7", + "tsc-alias": "1.6.9", "tsconfig-paths": "4.0.0", "twemoji-parser": "14.0.0", - "typescript": "4.7.2", + "typescript": "4.7.3", "uuid": "8.3.2", "v-debounce": "0.1.2", "vanilla-tilt": "1.7.2", - "vite": "2.9.9", - "vue": "3.2.36", + "vite": "2.9.10", + "vue": "3.2.37", "vue-prism-editor": "2.0.0-alpha.2", - "vue-router": "4.0.15", - "vuedraggable": "4.1.0", + "vue-router": "4.0.16", + "vuedraggable": "4.0.1", "websocket": "1.0.34", - "ws": "8.7.0" + "ws": "8.8.0" }, "devDependencies": { "@types/escape-regexp": "0.0.1", @@ -103,13 +104,13 @@ "@types/uuid": "8.3.4", "@types/websocket": "1.0.5", "@types/ws": "8.5.3", - "@typescript-eslint/eslint-plugin": "5.27.0", - "@typescript-eslint/parser": "5.27.0", + "@typescript-eslint/eslint-plugin": "5.27.1", + "@typescript-eslint/parser": "5.27.1", "cross-env": "7.0.3", - "cypress": "9.7.0", - "eslint": "8.16.0", + "cypress": "10.0.3", + "eslint": "8.17.0", "eslint-plugin-import": "2.26.0", - "eslint-plugin-vue": "9.0.1", + "eslint-plugin-vue": "9.1.0", "start-server-and-test": "1.14.0" } } diff --git a/packages/client/src/components/captcha.vue b/packages/client/src/components/captcha.vue index ccd8880df..183658471 100644 --- a/packages/client/src/components/captcha.vue +++ b/packages/client/src/components/captcha.vue @@ -27,8 +27,7 @@ type CaptchaContainer = { }; declare global { - interface Window extends CaptchaContainer { - } + interface Window extends CaptchaContainer { } } const props = defineProps<{ diff --git a/packages/client/src/components/cropper-dialog.vue b/packages/client/src/components/cropper-dialog.vue new file mode 100644 index 000000000..a8bde6ea0 --- /dev/null +++ b/packages/client/src/components/cropper-dialog.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/packages/client/src/components/drive-file-thumbnail.vue b/packages/client/src/components/drive-file-thumbnail.vue index dd24440e8..07cd565c5 100644 --- a/packages/client/src/components/drive-file-thumbnail.vue +++ b/packages/client/src/components/drive-file-thumbnail.vue @@ -1,6 +1,6 @@