Init reactors.

This commit is contained in:
2023-02-27 19:41:05 -08:00
parent 60eac45175
commit 0c0e29947a
12 changed files with 857 additions and 4 deletions

View File

@@ -0,0 +1,64 @@
import Stripe from 'stripe';
const stripe = new Stripe('sk_test_51MVz87Ke2JFOuDSNa2PVPrs3BBq9vJQwwDITC3sOB521weM4oklKtQFbJ03MNsJwsxtjHO5NScqOHC9MABREVjU900yYz3lWgL');
import db from '@/db';
import { scrypt, randomBytes, timingSafeEqual } from 'crypto';
import { promisify } from 'util';
const scryptPromise = promisify(scrypt);
function genSalt(bytes = 16) {
return randomBytes(bytes).toString('hex');
};
async function hash(salt, password, rounds = 64) {
const derivedKey = await scryptPromise(password, salt, rounds);
return derivedKey.toString('hex')
}
async function verify(password, hash, salt, rounds = 64) {
const keyBuffer = Buffer.from(hash, 'hex');
const derivedKey = await scryptPromise(password, salt, rounds);
return timingSafeEqual(keyBuffer, derivedKey);
}
function makeMsg(csi, email, text) {
return `/reactors/create-account?csi=${csi}&msg=${encodeURIComponent(text)}&email=${encodeURIComponent(email)}`
};
export default async function handler(req, res) {
if (req.method === 'POST') {
const { email, password, passwordagain, csi } = req.body;
if (email && password && password === passwordagain && csi) {
const session = csi && await stripe.checkout.sessions.retrieve(csi);
const emailFromSession = session && session.customer_details.email;
if (!session || !emailFromSession || email !== emailFromSession) {
res.redirect('/reactors/create-account?unexpected_error=true');
}
const existingUser = await db.get('select id from users where email=?', email);
if (existingUser) {
res.redirect('/reactors/create-account?unexpected_error=true');
}
console.log('inserting user');
const salt = genSalt();
const hashRes = await hash(salt, password);
await db.run('insert into users (email, salt, password_hash) values (?, ?, ?);', email, salt, hashRes);
console.log('done inserting user');
res.redirect('/reactors')
} else {
if (!email || !csi) {
res.redirect('/reactors/create-account?unexpected_error=true');
}
if (!password) {
res.redirect(makeMsg(csi, email, 'Please enter a password'));
}
if (password !== passwordagain) {
res.redirect(makeMsg(csi, email, 'Passwords did not match. Please try again.'));
}
}
} else {
// Handle any other HTTP method
}
}

95
src/pages/api/feed.rss.js Normal file
View File

@@ -0,0 +1,95 @@
import path from 'path';
import fs from 'fs';
import db from '@/db';
import { Podcast } from 'podcast';
/* import mp3Duration from 'mp3-duration'; */
import { getEpisodes } from '@/data/episodes';
export default async function handler(req, res) {
if (req.method === 'GET') {
const feed = new Podcast({
title: 'The React Show (Reactors)',
description: "Discussions about React, JavaScript, and web development by React experts with a focus on diving deep into learning React and discussing what it's like to work within the React industry.",
feedUrl: 'https://www.thereactshow.com/api/feed.rss',
siteUrl: 'https://www.thereactshow.com',
imageUrl: 'https://storage.buzzsprout.com/variants/d1tds1rufs5340fyq9mpyzo491qp/5cfec01b44f3e29fae1fb88ade93fc4aecd05b192fbfbc2c2f1daa412b7c1921.jpg',
author: 'Owl Creek Studio',
copyright: '© 2023 Owl Creek Studio',
language: 'en',
categories: ['Technology','Education','Business'],
pubDate: 'May 20, 2012 04:00:00 GMT',
ttl: 60,
itunesAuthor: 'Owl Creek Studio',
itunesOwner: { name: 'Owl Creek Studio' },
itunesExplicit: false,
itunesCategory: [{
text: 'Technology'
},
{
text: 'Education'
},
{
text: 'Business'
}],
itunesImage: 'https://storage.buzzsprout.com/variants/d1tds1rufs5340fyq9mpyzo491qp/5cfec01b44f3e29fae1fb88ade93fc4aecd05b192fbfbc2c2f1daa412b7c1921.jpg'
});
const episodes = await getEpisodes();
episodes.forEach(({ title, published, description, content, slug, audio: { src, length }, num, id, youtube }) => {
const filename = `0${num}-mixed.mp3`; // TODO auto-add the 0 prefix
const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename);
if (fs.existsSync(filepath)) {
feed.addItem({
title,
description: content,
content,
url: `https://www.thereactshow.com/podcast/${slug}`,
date: published,
itunesExplicit: false,
itunesSummary: description,
/* itunesDuration: await mp3Duration(filepath), TODO */
itunesDuration: 1234,
enclosure : {
url: `https://www.thereactshow.com/files/episodes/${filename}`,
file: filepath
},
});
}
})
const dbUpdates = episodes.map(async ({ title, published, description, content, slug, audio: { src, length }, num, id, youtube }) => {
const filename = `0${num}-mixed.mp3`; // TODO auto-add the 0 prefix
const filepath = path.join(process.cwd(), 'public', 'files', 'episodes', filename);
if (fs.existsSync(filepath)) {
const existsInDb = await db.get('select id from episodes where number=?', num);
if (!existsInDb) {
console.log('adding to db');
await db.run('insert into episodes (number, content, summary, slug, season, episode, filename, title, episode_type, buzzsprout_id, buzzsprout_url, pub_date, youtube_url) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);',
num,
content,
description,
slug,
1,
num,
filename,
title,
'episodic',
id,
src,
published,
youtube);
console.log('added to db', num);
}
}
})
await Promise.all(dbUpdates);
const xml = feed.buildXml();
res.setHeader('Content-Type', 'text/xml; charset=utf-8');
res.send(xml);
}
};

53
src/pages/api/sign-in.js Normal file
View File

@@ -0,0 +1,53 @@
import Stripe from 'stripe';
const stripe = new Stripe('sk_test_51MVz87Ke2JFOuDSNa2PVPrs3BBq9vJQwwDITC3sOB521weM4oklKtQFbJ03MNsJwsxtjHO5NScqOHC9MABREVjU900yYz3lWgL');
import { setCookie } from 'cookies-next';
import { v4 as uuidv4 } from 'uuid';
import db from '@/db';
import { scrypt, randomBytes, timingSafeEqual } from 'crypto';
import { promisify } from 'util';
const scryptPromise = promisify(scrypt);
async function verify(password, hash, salt, rounds = 64) {
const keyBuffer = Buffer.from(hash, 'hex');
const derivedKey = await scryptPromise(password, salt, rounds);
return timingSafeEqual(keyBuffer, derivedKey);
}
function makeMsg(email, text) {
return `/reactors/sign-in?msg=${encodeURIComponent(text)}&email=${encodeURIComponent(email)}`
};
export default async function handler(req, res) {
if (req.method === 'POST') {
const { email, password, remember_me: rememberMe } = req.body;
if (email && password) {
const queryRes = await db.get('select id, salt, password_hash from users where email=?;', email);
const { password_hash, salt, id: userId } = queryRes || { password_hash: '', salt: '', id: '' };
const verifyRes = await verify(password, password_hash, salt);
if (verifyRes) {
const sessionId = uuidv4();
const maxAge = 60 * 60 * 24 * 365;
const today = new Date();
const expiresDate = new Date(today.getTime() + (1000 * maxAge));
await db.run('insert into sessions (user_id, session_id, expires) values (?, ?, ?);', userId, sessionId, expiresDate.toISOString());
setCookie('session', sessionId, { req, res, maxAge: rememberMe ? maxAge : undefined, httpOnly: true, sameSite: true, secure: process.env.NODE_ENV === 'production' });
res.redirect('/reactors/account')
} else {
res.redirect(makeMsg(email, 'Invalid password or account does not exist.'));
}
} else {
if (!email) {
res.redirect(makeMsg(email, 'Please enter an email address.'));
}
if (!password) {
res.redirect(makeMsg(email, 'Please enter a password.'));
}
}
} else {
// Handle any other HTTP method
}
}