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" />
|
||||||
/// <reference types="next/types/global" />
|
/// <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 {
|
export default {
|
||||||
redisURL: 'redis://localhost:6379/0',
|
redisURL: 'redis://localhost:6379/0',
|
||||||
channel: {
|
channel: {
|
||||||
ttl: 60 * 60, // 1 hour
|
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 { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { channelRepo } from '../../channel'
|
import { Channel, channelRepo } from '../../channel'
|
||||||
|
import { routeHandler, getBodyKey } from '../../routes'
|
||||||
|
|
||||||
export default (req: Request, res: Response): void => {
|
export default routeHandler<Channel>(
|
||||||
// TODO: validate method and uploaderPeerID
|
(req: NextApiRequest, _res: NextApiResponse): Promise<Channel> => {
|
||||||
|
const uploaderPeerID = getBodyKey(req, 'uploaderPeerID')
|
||||||
channelRepo
|
return channelRepo.create(uploaderPeerID)
|
||||||
.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() }))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
import type { Request, Response } from 'express'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { channelRepo } from '../../channel'
|
import { channelRepo } from '../../channel'
|
||||||
|
import { routeHandler, getBodyKey } from '../../routes'
|
||||||
|
|
||||||
export default (req: Request, res: Response): void => {
|
export default routeHandler<void>(
|
||||||
// TODO: validate method and slug
|
(req: NextApiRequest, _res: NextApiResponse): Promise<void> => {
|
||||||
|
const slug = getBodyKey(req, 'slug')
|
||||||
channelRepo
|
return channelRepo.destroy(slug)
|
||||||
.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() }))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
import type { Request, Response } from 'express'
|
import { NextApiRequest, NextApiResponse } from 'next'
|
||||||
import { channelRepo } from '../../channel'
|
import { channelRepo } from '../../channel'
|
||||||
|
import { routeHandler, getBodyKey } from '../../routes'
|
||||||
|
|
||||||
export default (req: Request, res: Response): void => {
|
export default routeHandler<void>(
|
||||||
// TODO: validate method and slug
|
(req: NextApiRequest, _res: NextApiResponse): Promise<void> => {
|
||||||
|
const slug = getBodyKey(req, 'slug')
|
||||||
channelRepo
|
return channelRepo.renew(slug)
|
||||||
.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() }))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,25 +1,49 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import WebRTCProvider from '../../components/WebRTCProvider'
|
import WebRTCProvider from '../../components/WebRTCProvider'
|
||||||
import { useRouter } from 'next/router'
|
|
||||||
import Downloader from '../../components/Downloader'
|
import Downloader from '../../components/Downloader'
|
||||||
import { NextPage } from 'next'
|
import { NextPage, GetServerSideProps } from 'next'
|
||||||
|
import { channelRepo } from '../../channel'
|
||||||
|
|
||||||
const DownloadPage: NextPage = () => {
|
type Props = {
|
||||||
const router = useRouter()
|
slug: string
|
||||||
const { slug } = router.query
|
uploaderPeerID: string
|
||||||
|
error?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const DownloadPage: NextPage<Props> = ({ slug, uploaderPeerID }) => {
|
||||||
return (
|
return (
|
||||||
<WebRTCProvider>
|
<WebRTCProvider>
|
||||||
<>
|
<>
|
||||||
<div>{JSON.stringify(slug)}</div>
|
<div>{slug}</div>
|
||||||
<Downloader roomName="my-room" />
|
<div>{uploaderPeerID}</div>
|
||||||
|
<Downloader uploaderPeerID={uploaderPeerID} />
|
||||||
</>
|
</>
|
||||||
</WebRTCProvider>
|
</WebRTCProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadPage.getInitialProps = () => {
|
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
|
||||||
return {}
|
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
|
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