Skip to content
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

API server changes #1767

Merged
merged 3 commits into from
Feb 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions ansible/roles/api-server/handlers/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,10 @@
become: true
ansible.builtin.systemd:
daemon_reload: true

- name: Restart API server service
become: true
ansible.builtin.service:
name: spyglassmc-api-server
enabled: true
state: restarted
2 changes: 2 additions & 0 deletions ansible/roles/api-server/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
name: '@spyglassmc/web-api-server'
global: true
state: latest
notify: Restart API server service

- name: Create user
become: true
Expand All @@ -29,6 +30,7 @@
owner: api-server
group: api-server
mode: '700'
notify: Restart API server service

- name: Create service
become: true
Expand Down
8 changes: 4 additions & 4 deletions docs/developer/api.adoc → docs/developer/web-api.adoc
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
:page-layout: default
:page-title: API
:page-title: Web API
:page-parent: Developer Guides

:toc:

== Introduction

The Spyglass API provides access to various information that is helpful for data pack/resource pack
toolings. It uses https://github.com/misode/mcmeta[misode/mcmeta] and
The Spyglass Web API provides access to various information that is helpful for data pack/resource
pack toolings. It uses https://github.com/misode/mcmeta[misode/mcmeta] and
https://github.com/SpyglassMC/vanilla-mcdoc[SpyglassMC/vanilla-mcdoc] under the hood and provides a
few advantages over using the GitHub API directly:

Expand Down Expand Up @@ -42,7 +42,7 @@ the last response.

Each API endpoint has a point cost depending on how expensive it is to serve the request. Each IP
address can consume up to 100 points per one hour window before receiving `429 Too Many Requests`
responses. Additionally, the response time will be degraded starting from 50 requests per 15 minute
responses. Additionally, the response time will be degraded starting from 150 requests per 15 minute
window.

.Point Costs
Expand Down
21 changes: 8 additions & 13 deletions packages/web-api-server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Mutex } from 'async-mutex'
import chalk from 'chalk'
import cors from 'cors'
import express from 'express'
import { slowDown } from 'express-slow-down'
import assert from 'node:assert'
import { fileURLToPath } from 'node:url'
import {
assertRootDir,
Expand All @@ -16,6 +16,7 @@ import {
MemCache,
sendGitFile,
sendGitTarball,
updateGitRepo,
userAgentEnforcer,
verifySignature,
} from './utils.js'
Expand All @@ -24,7 +25,6 @@ const { hookSecret, port, rootDir } = loadConfig()
await assertRootDir(rootDir)
const gits = await initGitRepos(rootDir)
const cache = new MemCache(gits.mcmeta)
const gitMutex = new Mutex()

const versionRoute = express.Router({ mergeParams: true })
.use(getVersionValidator(cache))
Expand All @@ -49,7 +49,7 @@ const versionRoute = express.Router({ mergeParams: true })
await sendGitTarball(req, res, gits.mcmeta, `${version}-data`)
})

const DELAY_AFTER = 50
const DELAY_AFTER = 150

const app = express()
.set('trust proxy', 1)
Expand Down Expand Up @@ -86,10 +86,10 @@ const app = express()
})
.use('/mcje/versions/:version', versionRoute)
.get('/vanilla-mcdoc/symbols', cheapRateLimiter, async (req, res) => {
await sendGitFile(req, res, gits.mcdoc, `generated`, 'symbols.json')
await sendGitFile(req, res, gits['vanilla-mcdoc'], `generated`, 'symbols.json')
})
.get('/vanilla-mcdoc/tarball', expensiveRateLimiter, async (req, res) => {
await sendGitTarball(req, res, gits.mcdoc, 'main', 'vanilla-mcdoc')
await sendGitTarball(req, res, gits['vanilla-mcdoc'], 'main', 'vanilla-mcdoc')
})
.post(
'/hooks/github',
Expand All @@ -112,14 +112,9 @@ const app = express()
const { repository: { name } } = JSON.parse(req.body.toString()) as {
repository: { name: string }
}
const git = gits[name === 'vanilla-mcdoc' ? 'mcdoc' : 'mcmeta']

await gitMutex.runExclusive(async () => {
console.info(chalk.yellow(`Updating ${name}...`))
await git.remote(['update', '--prune'])
cache.invalidate()
console.info(chalk.green(`Updated ${name}`))
})
assert(name === 'vanilla-mcdoc' || name === 'mcmeta')
await updateGitRepo(name, gits[name])
cache.invalidate()
},
)
.get('/favicon.ico', cheapRateLimiter, (_req, res) => {
Expand Down
18 changes: 15 additions & 3 deletions packages/web-api-server/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Mutex } from 'async-mutex'
import chalk from 'chalk'
import { createHmac, timingSafeEqual } from 'crypto'
import type { NextFunction, Request, Response } from 'express'
Expand All @@ -7,6 +8,8 @@ import { RateLimiterMemory, RateLimiterRes } from 'rate-limiter-flexible'
import simpleGit from 'simple-git'
import type { SimpleGit } from 'simple-git'

const gitMutex = new Mutex()

export function loadConfig() {
if (
!(process.env.SPYGLASSMC_API_SERVER_DIR && process.env.SPYGLASSMC_API_SERVER_WEBHOOK_SECRET)
Expand Down Expand Up @@ -63,8 +66,10 @@ export async function initGitRepos(rootDir: string) {

async function initGitRepo(owner: string, repo: string) {
const repoDir = path.join(rootDir, repo)
const repoGit = simpleGit({ baseDir: repoDir }).outputHandler(defaultOutputHandler)
if (await doesPathExist(repoDir)) {
console.info(chalk.green(`Repo ${owner}/${repo} already cloned.`))
await updateGitRepo(repo, repoGit)
} else {
console.info(chalk.yellow(`Cloning ${owner}/${repo}...`))
await gitCloner.clone(
Expand All @@ -74,16 +79,23 @@ export async function initGitRepos(rootDir: string) {
)
console.info(chalk.green(`Repo ${owner}/${repo} cloned.`))
}

return simpleGit({ baseDir: repoDir }).outputHandler(defaultOutputHandler)
return repoGit
}

return {
mcmeta: await initGitRepo('misode', 'mcmeta'),
mcdoc: await initGitRepo('SpyglassMC', 'vanilla-mcdoc'),
'vanilla-mcdoc': await initGitRepo('SpyglassMC', 'vanilla-mcdoc'),
}
}

export async function updateGitRepo(name: string, git: SimpleGit) {
await gitMutex.runExclusive(async () => {
console.info(chalk.yellow(`Updating ${name}...`))
await git.remote(['update', '--prune'])
console.info(chalk.green(`Updated ${name}`))
})
}

export async function sendGitFile(
req: Request,
res: Response,
Expand Down