-
Notifications
You must be signed in to change notification settings - Fork 370
fix: /api/v1/instance
#363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
DataDrivenMD
wants to merge
39
commits into
cloudflare:main
Choose a base branch
from
Distal-Labs:api/v1/instance
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 31 commits
Commits
Show all changes
39 commits
Select commit
Hold shift + click to select a range
414e003
Update main.tf
DataDrivenMD f3a5574
yolo
DataDrivenMD c443959
Applying user handle patch
DataDrivenMD b6b93b7
Allow TF to overwrite wildebeest CNAME
DataDrivenMD 6d0a2da
Merge remote-tracking branch 'upstream/main'
DataDrivenMD 9b90066
Merge branch 'cloudflare:main' into main
DataDrivenMD e07b1ed
Merge pull request #1 from Distal-Labs/fix-missing-apps-verify_creden…
DataDrivenMD 3409a3e
Define MastodonInstance types
DataDrivenMD 629ce0c
Return Mastodon-compliant instance info
DataDrivenMD 14c3289
Merge remote-tracking branch 'upstream/main' into fix-incompatible-in…
DataDrivenMD ec9aba6
Merge pull request #2 from Distal-Labs/fix-incompatible-instance-endp…
DataDrivenMD b83e79a
Merge branch 'fix-missing-apps-verify_credentials-endpoint'
DataDrivenMD 749429c
Merge branch 'cloudflare:main' into main
DataDrivenMD d71599c
Change Default Images to Official Mastodon Avatar
DataDrivenMD 60802a4
Merge pull request #3 from Distal-Labs/fix-use-official-default-avatar
DataDrivenMD bfbbd05
Merge remote-tracking branch 'upstream/main'
DataDrivenMD ddf04ef
Merge remote-tracking branch 'upstream/main'
DataDrivenMD ef45841
Merge branch 'cloudflare:main' into main
DataDrivenMD 2887e3a
Merge branch 'cloudflare:main' into main
DataDrivenMD 8fb9b9b
Tiny refactor of existing Mastodon instance tests
DataDrivenMD 2e30352
/api/v1/instance endpoint + getAdmins() test
DataDrivenMD 0f2d29b
Adding test for instance v1 statistics
DataDrivenMD 199b267
Merge remote-tracking branch 'upstream/main'
DataDrivenMD 9a94e00
Linting
DataDrivenMD 4ef9d98
More linting
DataDrivenMD 0776114
Merge branch 'main' into api/v1/instance
DataDrivenMD 1a38165
Revert "Merge branch 'main' into api/v1/instance"
DataDrivenMD 52a4eab
Merge branch 'cloudflare:main' into api/v1/instance
DataDrivenMD af4f8a8
Merge branch 'upstream/main' into api/v1/instance
DataDrivenMD baa28d5
Merge remote-tracking branch 'upstream/main' into api/v1/instance
DataDrivenMD ea5f3fa
Pass e2e tests
DataDrivenMD 3283c55
Making requested changes
DataDrivenMD 4a698bd
Merge remote-tracking branch 'upstream/main' into api/v1/instance
DataDrivenMD 79c3b61
Reverting breaking change to `getVersion()`
DataDrivenMD 35ed2d1
Prettier
DataDrivenMD a8eb077
Requested changes + bind DB for e2e tests
DataDrivenMD 1685c6a
Merge remote-tracking branch 'upstream/main' into api/v1/instance
DataDrivenMD 9b9863f
Merge remote-tracking branch 'upstream/main' into api/v1/instance
DataDrivenMD 4095db5
Merge remote-tracking branch 'upstream/main' into api/v1/instance
DataDrivenMD File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import type { InstanceStatistics } from 'wildebeest/backend/src/types/instance' | ||
| import { instanceStatisticsQuery } from 'wildebeest/backend/src/mastodon/sql/instance' | ||
| import { Database } from 'wildebeest/backend/src/database' | ||
|
|
||
| export async function calculateInstanceStatistics(origin: string, db: Database): Promise<InstanceStatistics> { | ||
| const row: any = await db.prepare(instanceStatisticsQuery(origin)).first() | ||
|
|
||
| return { | ||
| user_count: row?.user_count ?? 0, | ||
| status_count: row?.status_count ?? 0, | ||
| domain_count: row?.domain_count ?? 1, | ||
| } as InstanceStatistics | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // Prepared statements for Mastodon Instance API endpoints | ||
| export const instanceStatisticsQuery = (origin: string): string => { | ||
| return ` | ||
| SELECT | ||
| (SELECT count(1) FROM actors WHERE type IN ('Person', 'Service') AND id LIKE '%${origin}/ap/users/%') AS user_count, | ||
| (SELECT count(1) FROM objects WHERE local = 1 AND type = 'Note') AS status_count, | ||
| (SELECT count(1) FROM peers) + 1 AS domain_count | ||
| ; | ||
| ` | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| import type { MastodonAccount } from './account' | ||
|
|
||
| // https://docs.joinmastodon.org/entities/Instance/ | ||
| // https://github.com/mastodon/mastodon-ios/blob/develop/MastodonSDK/Sources/MastodonSDK/Entity/Mastodon%2BEntity%2BInstance.swift | ||
| // https://github.com/mastodon/mastodon-android/blob/master/mastodon/src/main/java/org/joinmastodon/android/model/Instance.java | ||
| export interface MastodonInstance { | ||
| uri: string | ||
| title: string | ||
| description: string | ||
| short_description: string | ||
| email: string | ||
| version?: string | ||
| languages?: Array<string> | ||
| registrations?: boolean | ||
| approval_required?: boolean | ||
| invites_enabled?: boolean | ||
| urls?: InstanceURL | ||
| statistics?: InstanceStatistics | ||
| stats?: InstanceStatistics | ||
| thumbnail?: string | ||
| contact_account?: MastodonAccount | ||
| rules?: Array<InstanceRule> | ||
| configuration?: InstanceConfiguration | ||
| } | ||
|
|
||
| export interface InstanceURL { | ||
| streaming_api: string | ||
| } | ||
|
|
||
| export type InstanceStatistics = { | ||
| user_count: number | ||
| status_count: number | ||
| domain_count: number | ||
| } | ||
|
|
||
| export type InstanceRule = { | ||
| id: string | ||
| text: string | ||
| } | ||
|
|
||
| export type InstanceConfiguration = { | ||
| statuses?: StatusesConfiguration | ||
| media_attachments?: MediaAttachmentsConfiguration | ||
| polls?: PollsConfiguration | ||
| } | ||
|
|
||
| export type StatusesConfiguration = { | ||
| max_characters: number | ||
| max_media_attachments: number | ||
| characters_reserved_per_url: number | ||
| } | ||
|
|
||
| export type MediaAttachmentsConfiguration = { | ||
| supported_mime_types: Array<string> | ||
| image_size_limit: number | ||
| image_matrix_limit: number | ||
| video_size_limit: number | ||
| video_frame_rate_limit: number | ||
| video_matrix_limit: number | ||
| } | ||
|
|
||
| export type PollsConfiguration = { | ||
| max_options: number | ||
| max_characters_per_option: number | ||
| min_expiration: number | ||
| max_expiration: number | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,152 @@ | ||
| import { addPeer } from 'wildebeest/backend/src/activitypub/peers' | ||
| import { strict as assert } from 'node:assert/strict' | ||
| import type { Env } from 'wildebeest/backend/src/types/env' | ||
| import * as v1_instance from 'wildebeest/functions/api/v1/instance' | ||
| import * as v2_instance from 'wildebeest/functions/api/v2/instance' | ||
| import * as peers from 'wildebeest/functions/api/v1/instance/peers' | ||
| import { makeDB } from '../utils' | ||
| import { makeDB, assertCORS, assertJSON } from 'wildebeest/backend/test/utils' | ||
| import { createPerson } from 'wildebeest/backend/src/activitypub/actors' | ||
| import { createPublicNote } from 'wildebeest/backend/src/activitypub/objects/note' | ||
| import { MastodonInstance } from 'wildebeest/backend/src/types/instance' | ||
|
|
||
| const adminKEK = 'admin' | ||
| const userKEK = 'test_kek2' | ||
| const admin_email = 'admin@cloudflare.com' | ||
| const domain = 'cloudflare.com' | ||
|
|
||
| describe('Mastodon APIs', () => { | ||
| describe('instance', () => { | ||
| test('returns peers', async () => { | ||
| const db = await makeDB() | ||
| await addPeer(db, 'a') | ||
| await addPeer(db, 'b') | ||
|
|
||
| const res = await peers.handleRequest(db) | ||
| assert.equal(res.status, 200) | ||
|
|
||
| const data = await res.json<Array<string>>() | ||
| assert.equal(data.length, 2) | ||
| assert.equal(data[0], 'a') | ||
| assert.equal(data[1], 'b') | ||
| describe('/v1', () => { | ||
| describe('/instance', () => { | ||
| const env = { | ||
| INSTANCE_TITLE: 'a', | ||
| ADMIN_EMAIL: admin_email, | ||
| INSTANCE_DESCR: 'c', | ||
| } as Env | ||
|
|
||
| test('return the correct instance admin', async () => { | ||
| const db = await makeDB() | ||
| await createPerson(domain, db, adminKEK, admin_email, {}, true) | ||
|
|
||
| const res = await v1_instance.handleRequest(domain, env, db) | ||
| assert.equal(res.status, 200) | ||
| assertCORS(res) | ||
| assertJSON(res) | ||
|
|
||
| { | ||
| const data = await res.json<MastodonInstance>() | ||
| assert.equal(data.email, admin_email) | ||
| assert.equal(data?.contact_account?.acct, adminKEK) | ||
| } | ||
| }) | ||
|
|
||
| test('return the correct instance statistics', async () => { | ||
| const db = await makeDB() | ||
| const person = await createPerson(domain, db, adminKEK, admin_email, {}, true) | ||
| await createPerson(domain, db, userKEK, 'sven@cloudflare.com') | ||
| await addPeer(db, 'a') | ||
| await addPeer(db, 'b') | ||
| await createPublicNote(domain, db, 'my first status', person) | ||
|
|
||
| const res = await v1_instance.handleRequest(domain, env, db) | ||
| assert.equal(res.status, 200) | ||
| assertCORS(res) | ||
| assertJSON(res) | ||
|
|
||
| { | ||
| const data = await res.json<MastodonInstance>() | ||
| assert.equal(data.stats?.user_count, 2) | ||
| assert.equal(data.stats?.status_count, 1) | ||
| assert.equal(data.stats?.domain_count, 3) | ||
| } | ||
| }) | ||
|
|
||
| test('return the instance info', async () => { | ||
| const db = await makeDB() | ||
| await createPerson(domain, db, adminKEK, admin_email, {}, true) | ||
|
|
||
| const res = await v1_instance.handleRequest(domain, env, db) | ||
| assert.equal(res.status, 200) | ||
| assertCORS(res) | ||
| assertJSON(res) | ||
|
|
||
| { | ||
| const data = await res.json<MastodonInstance>() | ||
| assert.equal(data.rules?.length, 0) | ||
| assert.equal(data.uri, domain) | ||
| assert.equal(data.title, 'a') | ||
| assert.equal(data.email, admin_email) | ||
| assert.equal(data.description, 'c') | ||
| assert(data.version?.includes('Wildebeest')) | ||
| } | ||
| }) | ||
|
|
||
| test('adds a short_description if missing v1', async () => { | ||
| const db = await makeDB() | ||
| await createPerson(domain, db, adminKEK, admin_email, {}, true) | ||
|
|
||
| const res = await v1_instance.handleRequest(domain, env, db) | ||
| assert.equal(res.status, 200) | ||
|
|
||
| { | ||
| const data = await res.json<any>() | ||
| assert.equal(data.short_description, 'c') | ||
| } | ||
| }) | ||
|
|
||
| describe('/peers', () => { | ||
| test('returns peers', async () => { | ||
| const db = await makeDB() | ||
| await addPeer(db, 'a') | ||
| await addPeer(db, 'b') | ||
|
|
||
| const res = await peers.handleRequest(db) | ||
| assert.equal(res.status, 200) | ||
|
|
||
| const data = await res.json<Array<string>>() | ||
| assert.equal(data.length, 2) | ||
| assert.equal(data[0], 'a') | ||
| assert.equal(data[1], 'b') | ||
| }) | ||
| }) | ||
| }) | ||
| }) | ||
| describe('/v2', () => { | ||
| describe('/instance', () => { | ||
| type Data = { | ||
| rules: unknown[] | ||
| uri: string | ||
| title: string | ||
| email: string | ||
| description: string | ||
| version: string | ||
| domain: string | ||
| contact: { email: string } | ||
| } | ||
|
|
||
| test('return the instance infos v2', async () => { | ||
| const db = await makeDB() | ||
| await createPerson(domain, db, adminKEK, admin_email, {}, true) | ||
|
|
||
| const env = { | ||
| INSTANCE_TITLE: 'a', | ||
| ADMIN_EMAIL: 'b', | ||
| INSTANCE_DESCR: 'c', | ||
| } as Env | ||
| const res = await v2_instance.handleRequest(domain, db, env) | ||
| assert.equal(res.status, 200) | ||
| assertCORS(res) | ||
| assertJSON(res) | ||
|
|
||
| { | ||
| const data = await res.json<Data>() | ||
| assert.equal(data.rules.length, 0) | ||
| assert.equal(data.domain, domain) | ||
| assert.equal(data.title, 'a') | ||
| assert.equal(data.contact.email, 'b') | ||
| assert.equal(data.description, 'c') | ||
| assert(data.version.includes('Wildebeest')) | ||
| } | ||
| }) | ||
| }) | ||
| }) | ||
| }) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.