mirror of https://github.com/kern/filepizza
Begin scaffolding for next.js rewrite
parent
e70445530d
commit
2b1c473e45
@ -1,2 +1,4 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/types/global" />
|
||||
|
||||
declare module 'xkcd-password'
|
||||
|
||||
@ -1,34 +0,0 @@
|
||||
const express = require('express')
|
||||
const db = require('../db')
|
||||
|
||||
const routes = module.exports = new express.Router()
|
||||
|
||||
function bootstrap(uploader, req, res, next) {
|
||||
if (uploader) {
|
||||
res.locals.data = {
|
||||
DownloadStore: {
|
||||
status: 'ready',
|
||||
token: uploader.token,
|
||||
fileSize: uploader.fileSize,
|
||||
fileName: uploader.fileName,
|
||||
fileType: uploader.fileType,
|
||||
infoHash: uploader.infoHash,
|
||||
},
|
||||
}
|
||||
|
||||
next()
|
||||
} else {
|
||||
const err = new Error('Not Found')
|
||||
err.status = 404
|
||||
next(err)
|
||||
}
|
||||
}
|
||||
|
||||
routes.get(/^\/([a-z]+\/[a-z]+\/[a-z]+\/[a-z]+)$/, (req, res, next) => {
|
||||
const uploader = db.find(req.params[0])
|
||||
return bootstrap(uploader, req, res, next)
|
||||
})
|
||||
routes.get(/^\/download\/(\w+)$/, (req, res, next) => {
|
||||
const uploader = db.findShort(req.params[0])
|
||||
return bootstrap(uploader, req, res, next)
|
||||
})
|
||||
@ -1,6 +1,28 @@
|
||||
import toppings from './toppings'
|
||||
|
||||
export default {
|
||||
redisURL: 'redis://localhost:6379/0',
|
||||
channel: {
|
||||
ttl: 60 * 60, // 1 hour
|
||||
},
|
||||
bodyKeys: {
|
||||
uploaderPeerID: {
|
||||
min: 3,
|
||||
max: 256,
|
||||
},
|
||||
slug: {
|
||||
min: 3,
|
||||
max: 256,
|
||||
},
|
||||
},
|
||||
shortSlug: {
|
||||
numChars: 8,
|
||||
chars: '0123456789abcdefghijklmnopqrstuvwxyz',
|
||||
maxAttempts: 8,
|
||||
},
|
||||
longSlug: {
|
||||
numWords: 4,
|
||||
words: toppings,
|
||||
maxAttempts: 8,
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,19 +1,10 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { channelRepo } from '../../channel'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { Channel, channelRepo } from '../../channel'
|
||||
import { routeHandler, getBodyKey } from '../../routes'
|
||||
|
||||
export default (req: Request, res: Response): void => {
|
||||
// TODO: validate method and uploaderPeerID
|
||||
|
||||
channelRepo
|
||||
.create(req.body.uploaderPeerID)
|
||||
.then((channel) => {
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify(channel))
|
||||
})
|
||||
.catch((err) => {
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify({ error: err.toString() }))
|
||||
})
|
||||
}
|
||||
export default routeHandler<Channel>(
|
||||
(req: NextApiRequest, _res: NextApiResponse): Promise<Channel> => {
|
||||
const uploaderPeerID = getBodyKey(req, 'uploaderPeerID')
|
||||
return channelRepo.create(uploaderPeerID)
|
||||
},
|
||||
)
|
||||
|
||||
@ -1,19 +1,10 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { channelRepo } from '../../channel'
|
||||
import { routeHandler, getBodyKey } from '../../routes'
|
||||
|
||||
export default (req: Request, res: Response): void => {
|
||||
// TODO: validate method and slug
|
||||
|
||||
channelRepo
|
||||
.destroy(req.body.slug)
|
||||
.then((channel) => {
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify(channel))
|
||||
})
|
||||
.catch((err) => {
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify({ error: err.toString() }))
|
||||
})
|
||||
}
|
||||
export default routeHandler<void>(
|
||||
(req: NextApiRequest, _res: NextApiResponse): Promise<void> => {
|
||||
const slug = getBodyKey(req, 'slug')
|
||||
return channelRepo.destroy(slug)
|
||||
},
|
||||
)
|
||||
|
||||
@ -1,19 +1,10 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import { channelRepo } from '../../channel'
|
||||
import { routeHandler, getBodyKey } from '../../routes'
|
||||
|
||||
export default (req: Request, res: Response): void => {
|
||||
// TODO: validate method and slug
|
||||
|
||||
channelRepo
|
||||
.renew(req.body.slug)
|
||||
.then((channel) => {
|
||||
res.statusCode = 200
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify(channel))
|
||||
})
|
||||
.catch((err) => {
|
||||
res.statusCode = 500
|
||||
res.setHeader('Content-Type', 'application/json')
|
||||
res.end(JSON.stringify({ error: err.toString() }))
|
||||
})
|
||||
}
|
||||
export default routeHandler<void>(
|
||||
(req: NextApiRequest, _res: NextApiResponse): Promise<void> => {
|
||||
const slug = getBodyKey(req, 'slug')
|
||||
return channelRepo.renew(slug)
|
||||
},
|
||||
)
|
||||
|
||||
@ -1,25 +1,49 @@
|
||||
import React from 'react'
|
||||
import WebRTCProvider from '../../components/WebRTCProvider'
|
||||
import { useRouter } from 'next/router'
|
||||
import Downloader from '../../components/Downloader'
|
||||
import { NextPage } from 'next'
|
||||
import { NextPage, GetServerSideProps } from 'next'
|
||||
import { channelRepo } from '../../channel'
|
||||
|
||||
const DownloadPage: NextPage = () => {
|
||||
const router = useRouter()
|
||||
const { slug } = router.query
|
||||
type Props = {
|
||||
slug: string
|
||||
uploaderPeerID: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
const DownloadPage: NextPage<Props> = ({ slug, uploaderPeerID }) => {
|
||||
return (
|
||||
<WebRTCProvider>
|
||||
<>
|
||||
<div>{JSON.stringify(slug)}</div>
|
||||
<Downloader roomName="my-room" />
|
||||
<div>{slug}</div>
|
||||
<div>{uploaderPeerID}</div>
|
||||
<Downloader uploaderPeerID={uploaderPeerID} />
|
||||
</>
|
||||
</WebRTCProvider>
|
||||
)
|
||||
}
|
||||
|
||||
DownloadPage.getInitialProps = () => {
|
||||
return {}
|
||||
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
|
||||
const slug = normalizeSlug(ctx.query.slug)
|
||||
const channel = await channelRepo.fetch(slug)
|
||||
|
||||
if (!channel) {
|
||||
ctx.res.statusCode = 404
|
||||
return {
|
||||
props: { slug, uploaderPeerID: '', error: 'not found' },
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
props: { slug, uploaderPeerID: channel.uploaderPeerID },
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeSlug = (rawSlug: string | string[]): string => {
|
||||
if (typeof rawSlug === 'string') {
|
||||
return rawSlug
|
||||
} else {
|
||||
return rawSlug.join('/')
|
||||
}
|
||||
}
|
||||
|
||||
export default DownloadPage
|
||||
|
||||
@ -0,0 +1,53 @@
|
||||
import { NextApiRequest, NextApiResponse } from 'next'
|
||||
import config from './config'
|
||||
|
||||
export type APIError = Error & { statusCode?: number }
|
||||
|
||||
export type BodyKey = keyof typeof config.bodyKeys
|
||||
|
||||
export function throwAPIError(message: string, statusCode = 500): void {
|
||||
const err = new Error(message) as APIError
|
||||
err.statusCode = statusCode
|
||||
throw err
|
||||
}
|
||||
|
||||
export function routeHandler<T>(
|
||||
fn: (req: NextApiRequest, res: NextApiResponse) => Promise<T>,
|
||||
): (req: NextApiRequest, res: NextApiResponse) => Promise<void> {
|
||||
return async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
|
||||
if (req.method !== 'POST') {
|
||||
res.statusCode = 405
|
||||
res.json({ error: 'method not allowed' })
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await fn(req, res)
|
||||
res.statusCode = 200
|
||||
res.json(result)
|
||||
} catch (err) {
|
||||
res.statusCode = err.statusCode || 500
|
||||
res.json({ error: err.message })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getBodyKey(req: NextApiRequest, key: BodyKey): string {
|
||||
const { min, max } = config.bodyKeys[key]
|
||||
|
||||
const val = req.body[key]
|
||||
|
||||
if (typeof val !== 'string') {
|
||||
throwAPIError(`${key} must be a string`)
|
||||
}
|
||||
|
||||
if (val.length < min) {
|
||||
throwAPIError(`${key} must be at least ${min} chars`)
|
||||
}
|
||||
|
||||
if (val.length > max) {
|
||||
throwAPIError(`${key} must be at most ${max} chars`)
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
import xkcdPassword from 'xkcd-password'
|
||||
import config from './config'
|
||||
|
||||
export const generateShortSlug = (): string => {
|
||||
let result = ''
|
||||
for (let i = 0; i < config.shortSlug.numChars; i++) {
|
||||
result +=
|
||||
config.shortSlug.chars[
|
||||
Math.floor(Math.random() * config.shortSlug.chars.length)
|
||||
]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const longSlugGenerator = new xkcdPassword()
|
||||
longSlugGenerator.initWithWordList(config.longSlug.words)
|
||||
|
||||
export const generateLongSlug = (): Promise<string> => {
|
||||
return longSlugGenerator.generate({
|
||||
numWords: config.longSlug.numWords,
|
||||
minLength: 1,
|
||||
maxLength: 256,
|
||||
})
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
import socket from 'filepizza-socket'
|
||||
|
||||
export function getClient() {
|
||||
return new Promise((resolve, reject) => {
|
||||
socket.emit('trackerConfig', {}, (trackerConfig) => {
|
||||
const client = new WebTorrent({
|
||||
tracker: trackerConfig,
|
||||
})
|
||||
resolve(client)
|
||||
})
|
||||
})
|
||||
}
|
||||
Loading…
Reference in New Issue