~cadence/Frames

c87abb20850bbadaf378213a4d2b02b25484cc81 — Cadence Ember 1 year, 9 months ago fb5bec3
Add content warning field
7 files changed, 105 insertions(+), 17 deletions(-)

M api/submit.js
M pug/feed.pug
M pug/includes/layout.pug
M pug/includes/post.pug
M sass/main.sass
M server.js
A utils/upgradedb.js
M api/submit.js => api/submit.js +4 -2
@@ 20,14 20,16 @@ module.exports = [

				const content = state.params.get("content") || null
				const url = state.params.get("url")
				const cw = state.params.get("cw") || null
				const id = String(Date.now())
				const prepared = {
					handle: user.row.handle,
					id,
					content,
					url
					url,
					cw
				}
				db.prepare("INSERT INTO Posts (handle, id, content, url) VALUES (@handle, @id, @content, @url)").run(prepared)
				db.prepare("INSERT INTO Posts (handle, id, content, url, cw) VALUES (@handle, @id, @content, @url, @cw)").run(prepared)
				return redirect(303, `/view/${user.row.handle}/${id}`)
			})
			.go()

M pug/feed.pug => pug/feed.pug +7 -4
@@ 14,15 14,18 @@ block main
    div You are viewing #[span.highlight #{handle}]

  if isSelf
    details
    details.publish-frame
      summary Publish a new frame
      form(method="post" action="/submit")
        label.vertical-label
          | Text content (optional)
          textarea(name="content")
        label
          | Image URL
          input(type="text" name="url" placeholder="https://....." required)
        label
          | CW (optional)
          input(type="text" name="cw" placeholder="e.g. food")
        label.vertical-label
          | Text content (optional)
          textarea(name="content")

        .hint.
          Need to upload your image somewhere? You could put it on the fediverse as a direct with no recipients, and take the URL.

M pug/includes/layout.pug => pug/includes/layout.pug +1 -0
@@ 1,3 1,4 @@
doctype html
html
  head
    meta(charset="utf-8")

M pug/includes/post.pug => pug/includes/post.pug +12 -3
@@ 3,9 3,18 @@ mixin post(post, isSelf)
    - const url = `/view/${post.handle}/${post.id}`
    .post__author
      a(href=url).post__link #{post.handle} at #{new Date(+post.id).toISOString().replace("T", " ").slice(0, -8)}
    if post.content
      .post__content= post.content
    if isSelf
      form(method="post" action=`${url}/delete`).single-button-form.post__delete
        button= `[Delete post forever]`
    a(href=post.url): img(src=post.url).post__image
    +post-optional-cw(post)
      if post.content
        .post__content= post.content
      a(href=post.url): img(src=post.url).post__image

mixin post-optional-cw(post)
  if post.cw
    details
      summary.post__cw CW: #{post.cw}
      block
  else
    block

M sass/main.sass => sass/main.sass +14 -8
@@ 57,13 57,6 @@ fieldset
  border: 1px solid #bbb
  padding: 0px 24px 24px

details
  margin: 16px 0px

  &[open]
    padding-bottom: 8px
    border-bottom: 3px solid #bbb

summary
  cursor: pointer



@@ 152,6 145,13 @@ ul
    text-decoration: underline
    cursor: pointer

.publish-frame
  margin: 16px 0px

  &[open]
    padding-bottom: 8px
    border-bottom: 3px solid #bbb

.post
  border: 1px solid #888
  padding: 8px


@@ 178,9 178,15 @@ ul
    max-width: 100%
    max-height: 80vh

  &__cw
    color: #fc6

    @at-root [open] &
      margin-bottom: 12px

  &__delete
    display: block
    margin-bottom: 16px
    margin: 20px 0

.auth-explanation
  margin: 24px 0

M server.js => server.js +3 -0
@@ 2,6 2,9 @@ const {Pinski} = require("pinski")
const {setInstance} = require("pinski/plugins")

;(async () => {
	const upgrade = require("./utils/upgradedb")
	await upgrade()

	const server = new Pinski({
		port: 10413,
		relativeRoot: __dirname,

A utils/upgradedb.js => utils/upgradedb.js +64 -0
@@ 0,0 1,64 @@
const pj = require("path").join
const db = require("./db")

const deltas = [
	// 0: from empty file, +Instances, +Users, +Posts, +KnownFollows, +RecentAuths, +DatabaseVersion
	function() {
		db.prepare("CREATE TABLE Instances (origin TEXT NOT NULL, brand TEXT NOT NULL, client_id TEXT NOT NULL, client_secret TEXT NOT NULL, auth_url TEXT NOT NULL, primary key (origin))")
			.run()
		db.prepare("CREATE TABLE Users (instance TEXT NOT NULL, access_token TEXT NOT NULL, cookie TEXT NOT NULL UNIQUE, handle TEXT NOT NULL, primary key (instance, access_token))")
			.run()
		db.prepare("CREATE TABLE Posts (handle TEXT NOT NULL, id TEXT NOT NULL, content TEXT, url TEXT NOT NULL, primary key (handle, id))")
			.run()
		db.prepare("CREATE TABLE KnownFollows (viewer TEXT NOT NULL, framer TEXT NOT NULL, time INTEGER NOT NULL, primary key (viewer, framer))")
			.run()
		db.prepare("CREATE TABLE RecentAuths (token TEXT NOT NULL, time INTEGER NOT NULL, id TEXT NOT NULL, primary key (token))")
			.run()
		db.prepare("CREATE TABLE DatabaseVersion (version INTEGER NOT NULL, primary key (version))")
			.run()
	},
	// 1: Posts +cw
	function() {
		db.prepare("ALTER TABLE Posts ADD COLUMN cw TEXT")
			.run()
	}
]

async function createBackup(entry) {
	const filename = `db/backups/cloudtube.db.bak-v${entry-1}`
	process.stdout.write(`Backing up current to ${filename}... `)
	await db.backup(pj(__dirname, "../", filename))
	process.stdout.write("done.\n")
}

/**
 * @param {number} entry
 * @param {boolean} log
 */
function runDelta(entry, log) {
	process.stdout.write(`Upgrading database to version ${entry}... `)
	deltas[entry]()
	db.prepare("DELETE FROM DatabaseVersion").run()
	db.prepare("INSERT INTO DatabaseVersion (version) VALUES (?)").run(entry)
	process.stdout.write("done.\n")
}

module.exports = async function() {
	let currentVersion = -1
	const newVersion = deltas.length - 1

	try {
		currentVersion = db.prepare("SELECT version FROM DatabaseVersion").pluck().get()
	} catch (e) {} // if the table doesn't exist yet then we don't care

	if (currentVersion !== newVersion) {
		// go through the entire upgrade sequence
		for (let entry = currentVersion+1; entry <= newVersion; entry++) {
			// Back up current version
			if (entry > 0) await createBackup(entry)

			// Run delta
			runDelta(entry)
		}
	}
}