Coder Social home page Coder Social logo

maxleiter / drift Goto Github PK

View Code? Open in Web Editor NEW
1.3K 1.3K 56.0 25.53 MB

Drift is a self-hostable Gist and paste service. Built with Next.js 13 and React Server Components.

Home Page: https://drift.lol

License: MIT License

CSS 9.21% TypeScript 88.20% JavaScript 1.40% Dockerfile 0.41% Shell 0.78%
drift gist nextjs nextjs13 nodejs pastebin pastebin-service react self-hosted typescript

drift's People

Contributors

dependabot[bot] avatar emilyst avatar examknow avatar icepaq avatar jazcarate avatar kinghat avatar kjaonline avatar maxall41 avatar maxleiter avatar qwertzdenek avatar reeseovine avatar renovate[bot] avatar sampaioxsamuel avatar spykelionel avatar tehpegasus avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

drift's Issues

API tokens

Users should be able to generate API tokens so they can write scripts / plugins

Logo

I designed a basic logo inspired by snowdrifts and the (mostly) achromatic theme of the web client:

drift

Let me know what you think!

Private posts are actually unlisted

Describe the bug
You can view private posts is if they are unlisted.

To Reproduce
Steps to reproduce the behavior:

  1. Create a private post and copy the link
  2. Open private browsing or another browser and go to the link.
  3. You will see the post.

Expected behavior
You shouldn't see the post

Client: better client-side validation when publishing a post

The following is based only on the demo server at https://drift.maxleiter.com/, so disregard if the most recent builds do not display this behavior.

Currently, if the client's request to create a post is met with a 403 (such as when the title is blank or the post contains no files), the client shows no indication of this and the submit button stalls in a loading animation.

Consider handling this case gracefully, perhaps even duplicating the validation on the client's side to highlight required fields in real time.

image

Support uploading images

I mean, is there some technical stuff I should take care if I remove the limit on the file type in the uploader component?

Add description field for new posts

Posts with long titles on gist work better than Drift, and rather than changing our styles I'd rather add a description field and enforce keeping titles on the shorter side.

Store Token and User ID as a Cookie

As discussed in issue #22 storing tokens in localstorage becomes a problem when trying to retrieve them on the server side.

Will just set token and userid Cookies for now just to make testing easier.

Issue with uploading files

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behaviour:

  1. Try uploading a file with an extension of .go or .mod
  2. Try uploading a large JSON

Expected behaviour
I would expect the upload to work since they are text files (and python files do work with .py). And the JSON is only 18MB, but it claims to be too large

Screenshots
Screenshot 2022-03-28 at 11 30 57 PM

Screenshot 2022-03-28 at 11 31 22 PM

โฏ ls -alh not_matched_x.json
-rw-r--r--@ 1 adyah  staff    18M Jan 25 18:39 not_matched_x.json

Desktop (please complete the following information):

  • OS: macOS
  • Browser Safari
  • Version 15.4

Additional context
Add any other context about the problem here.

Add sqlite backend

This should be easy with sequelize, the config and what-not just need to be added.

[Discussion] Implementing post updating and history

Opening this issue to memorialize a brief discussion we had in IRC about adding post updating.

Currently it's not possible to update a Post in place, and there's no concept of revisions that can be compared. Right now, there's a Post model and a File model. Posts have a one-to-many relationship with Files (each Post has one or more Files). My suggestion is that it should be possible to elaborate on this design slightly to add the ability to add updating and history.

First, update the File model:

  • Add a replaces nullable foreign key column.
  • Add a checksum nullable column of some sort to contain a checksum (maybe a BLAKE3 checksum, but it doesn't matter).
  • Add a diff column, as a nullable string.

Wherever we render the Files belonging to a Post, we would filter out any File referenced by another File as replaces. Whatever is left is the Post's current Files.

Updating a Post would mean presenting the contents of each File to edit. When the form is posted to update the Post's Files, it would be necessary to figure out which, if any, Files got their contents changed. You could use a checksum for determining this. For any File whose contents' checksum has changed:

  • Create a new File.
  • Associate that new File with the same Post.
  • Set the new File's replaces to the ID of the old File which it replaces.
  • Set the new File's contents in the contents column.
  • Diff the new File's contents against the old, and store that in the diff column.
  • Calculate the checksum of the new File's contents, and store that in the checksum column.

To show the history for a given Post, iterate over each of its current Files (any File not referenced by another File as replaces). Then for each File:

  • If the File's replaces column is null, skip and move on to the next File current for the Post.
  • If the File replaces another File, grab the replaced File's diff.
  • Repeat the first two steps with the replaced File.

You can just display those diffs somewhere for each File as its history.

Hopefully the above captures the idea well enough. Every bit of it is just an optional suggestion, so don't take any of it too literally.

SSO Support?

Somewhat related to #2, maybe a different direction to think about.

Would it be worth integrating some form of SSO (e.g. OAuth2) in addition to/instead of direct registrations? Besides the obvious benefit of "allowing an easier way to integrate with an existing stack", could also solve the issue of limiting registration by just making who can register be a different platform's problem -- for generic SSO, would be reasonable enough to just assume anyone who's in the SSO system could access; for specific SSO options (e.g. GitHub), could have options for limiting to users who are members of certain orgs or whatnot.

Something like Passport.js could be used to accomplish most of this

Missing Gist features

Not all of these should be or need to be met, but I figure it may be helpful to see them listed here. Anything struck through is not currently being planned.

  • ability to specify spaces/tabs, indent size, and line wrapping behavior
  • embeddable files
  • post editing (you can make a copy, and I'm open to being able to edit descriptions so you can add a link)
  • revision history view (as we have no git backing, we would just walk up the parent attribute that Posts have)
  • search all users gists

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Open

These updates have all been created already. Click a checkbox below to force a retry/rebase of any.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

docker-compose
docker-compose.yml
dockerfile
client/Dockerfile
  • node 17-alpine
  • node 17-alpine
  • node 17-alpine
server/Dockerfile
  • node 17-alpine
  • node 17-alpine
  • node 17-alpine
github-actions
.github/workflows/server-CI.yaml
  • actions/checkout v3
  • actions/setup-node v3
npm
client/package.json
  • @geist-ui/core ^2.3.8
  • @geist-ui/icons 1.0.2
  • @types/cookie 0.5.1
  • @types/js-cookie 3.0.2
  • client-zip 2.2.1
  • cookie 0.5.0
  • dotenv 16.0.0
  • js-cookie 3.0.1
  • next 13.0.2
  • next-themes 0.2.1
  • rc-table 7.24.1
  • react 18.2.0
  • react-datepicker 4.8.0
  • react-dom 18.2.0
  • react-dropzone 14.2.3
  • react-loading-skeleton 3.1.0
  • swr 1.3.0
  • textarea-markdown-editor 0.1.13
  • @next/bundle-analyzer 12.1.6
  • @types/node 17.0.23
  • @types/react 18.0.9
  • @types/react-datepicker 4.4.1
  • @types/react-dom 18.0.3
  • cross-env 7.0.3
  • eslint 8.27.0
  • eslint-config-next 13.0.2
  • next-unused 0.0.6
  • prettier 2.6.2
  • typescript 4.6.4
  • typescript-plugin-css-modules 3.4.0
  • sharp ^0.31.2
  • next 13.0.2
server/package.json
  • bcryptjs ^2.4.3
  • body-parser ^1.18.2
  • celebrate ^15.0.1
  • cors ^2.8.5
  • dotenv ^16.0.0
  • express ^4.16.2
  • express-jwt ^6.1.1
  • jsonwebtoken ^8.5.1
  • marked ^4.0.12
  • nodemon ^2.0.15
  • prism-react-renderer ^1.3.1
  • react ^18.0.0
  • react-dom ^18.0.0
  • reflect-metadata ^0.1.10
  • sequelize ^6.17.0
  • sequelize-typescript ^2.1.3
  • sqlite3 ^5.0.3
  • strong-error-handler ^4.0.0
  • umzug ^3.1.0
  • @types/bcryptjs 2.4.2
  • @types/cors 2.8.12
  • @types/express 4.17.13
  • @types/express-jwt 6.0.4
  • @types/jest 27.5.0
  • @types/jsonwebtoken 8.5.8
  • @types/marked 4.0.3
  • @types/node 17.0.21
  • @types/node-fetch 2.6.1
  • @types/react-dom 17.0.16
  • @types/supertest 2.0.12
  • @types/validator ^13.7.10
  • cross-env 7.0.3
  • jest 27.5.1
  • prettier 2.6.2
  • supertest 6.2.3
  • ts-jest 27.1.4
  • ts-node 10.7.0
  • tsc-alias 1.6.7
  • tsconfig-paths 3.14.1
  • tslint 6.1.3
  • typescript 4.6.4

  • Check this box to trigger a request for Renovate to run again on this repository

Post deletion is broken: sequelize constraint failed

Request DELETE /admin/post/e2b265c1-63e8-4434-b20d-1ae1a11edf74 failed: Error
    at Database.<anonymous> (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:227:27)
    at /Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:225:50
    at new Promise (<anonymous>)
    at Query.run (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:225:12)
    at /Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/sequelize.js:643:28
    at async SQLiteQueryInterface.delete (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/abstract/query-interface.js:941:12)
    at async Post.destroy (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/model.js:4269:16)
{
	"error": {
		"statusCode": 500,
		"name": "SequelizeUniqueConstraintError",
		"message": "Validation error",
		"errors": [],
		"parent": {
			"errno": 19,
			"code": "SQLITE_CONSTRAINT",
			"sql": "DELETE FROM `posts` WHERE `id` = 'e2b265c1-63e8-4434-b20d-1ae1a11edf74'"
		},
		"original": {
			"errno": 19,
			"code": "SQLITE_CONSTRAINT",
			"sql": "DELETE FROM `posts` WHERE `id` = 'e2b265c1-63e8-4434-b20d-1ae1a11edf74'"
		},
		"fields": [],
		"sql": "DELETE FROM `posts` WHERE `id` = 'e2b265c1-63e8-4434-b20d-1ae1a11edf74'",
		"stack": "Error\n    at Database.<anonymous> (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:227:27)\n    at /Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:225:50\n    at new Promise (<anonymous>)\n    at Query.run (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/sqlite/query.js:225:12)\n    at /Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/sequelize.js:643:28\n    at async SQLiteQueryInterface.delete (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/dialects/abstract/query-interface.js:941:12)\n    at async Post.destroy (/Users/maxleiter/Documents/drift/server/node_modules/sequelize/src/model.js:4269:16)"
	}
}

Embeddable files

We already server raw HTML, so if we set the proper content-type and some headers this should be fairly easy.

[Discussion] storing the HTML and the content for posts in the database

Someone hopped in IRC to ask about this and left before I could answer, so thought I'd leave this here in case they or anyone else wants to comment.

At the moment, Drift stores the generated HTML (from markdown) in the Post database model. I figured this was ideal so less logic had to run each time the post was generated/viewed (in case caching isn't setup/failing/not possible). The downsides are database bloat and lack of updates (there's no versioning, so if the HTML parser is updated old posts will not be). The second point can be fixed by adding a version column, but I'm not sure about the DB bloat (but I'm also unsure that's a problem initially)

SSR published posts

We don't need to use react-markdown on published posts, and should instead render the markdown to HTML on the server.

Move design framework

geist-ui is great for prototyping, but heavily reliant on JS and not very customizable.

Kubernetes Helm chart

Is there a Kubernetes Helm chart by chance? I understand that Drift is very young, and configuration may change, so building Docker containers and a Kubernetes Helm chart may be a bit premature.

client: use css variables for color

right now we rely on geist for it's default colors, but we should instead rely on our own CSS (we can use theirs as a start) for handling dark/light mode colors. This would also allow finer tuning of the UI and custom components.

Dark mode client flash effect when opening on the browser

Describe the bug

A flash effect (quick transition to black from white) happens when opening the client in browser

To Reproduce
Steps to reproduce the behavior:

  1. Open in dark mode
  2. Reload page multiple times

Expected behavior
A clear and concise description of what you expected to happen.

Screenshots
If applicable, add screenshots to help explain your problem.

Desktop (please complete the following information):

  • MacOs Monterey 12.3
  • Chrome
  • Version 99.0.4844.83 (Official Build) (x86_64)

Additional context
Add any other context about the problem here.

Create a docker-compose

Server and client both have their respective Dockerfiles, which seem to work, but they can't communicate with one another.

[Proposal] (Server) Decouble DB from routes (from domain logic)

I actually wanted to propose a slightly different change, and decouple the controller with the database.
Right now tests are very slow because they spool a whole DB, and any new changes will be very sequilize bound. Maybe it's because I don't really like sequalize, but I would like to have the domain logic (e.g.: hashing the password, or failing if there are no file) to live outside the class Post extends Model and leave the current Post class as a mere DTO to the database

Current implementation

Lets explore how one route would change. /posts/create is a big ofender:

// server/src/routes/posts.ts
posts.post(
	"/create",
	jwt,
	celebrate({                                 // ---- All good. This is a route responsability ๐Ÿ†— 
		body: {
			title: Joi.string().required(),
			files: Joi.any().required(),
			visibility: Joi.string()
				.custom(postVisibilitySchema, "valid visibility")
				.required(),
			userId: Joi.string().required(),
			password: Joi.string().optional(),
			//  expiresAt, allow to be null
			expiresAt: Joi.date().optional().allow(null, ""),
			parentId: Joi.string().optional().allow(null, "")
		}
	}),
	async (req, res, next) => {
		try {
			// check if all files have titles
			const files = req.body.files as File[]
			const fileTitles = files.map((file) => file.title)
			const missingTitles = fileTitles.filter((title) => title === "")
			if (missingTitles.length > 0) {    // ---- This is an invariant on all post->files. There should never be a post without titles ๐Ÿ™… 
				throw new Error("All files must have a title")
			}

			if (files.length === 0) {    // ---- This again is an invariant on all post->files. ๐Ÿ™… 
				throw new Error("You must submit at least one file")
			}

			let hashedPassword: string = ""
			if (req.body.visibility === "protected") {    // ---- This again is transformation of the Post creation; not the router๐Ÿ™… 
				hashedPassword = crypto
					.createHash("sha256")
					.update(req.body.password)
					.digest("hex")
			}

			const newPost = new Post({
				title: req.body.title,
				visibility: req.body.visibility,
				password: hashedPassword,
				expiresAt: req.body.expiresAt
			})

			await newPost.save()  // ---- Now we are crosing a lot of boundries, directly accesing the DB๐Ÿ™… 
			await newPost.$add("users", req.body.userId)
			const newFiles = await Promise.all(
				files.map(async (file) => {
					const html = getHtmlFromFile(file)
					const newFile = new File({
						title: file.title || "",
						content: file.content,
						sha: crypto
							.createHash("sha256")
							.update(file.content)
							.digest("hex")
							.toString(),
						html: html || "",
						userId: req.body.userId,
						postId: newPost.id
					})
					await newFile.save()
					return newFile
				})
			)

			await Promise.all(
				newFiles.map(async (file) => {
					await newPost.$add("files", file.id)  // ---- We have to manage timings on how sequalize like things to be stored๐Ÿ™… 
					await newPost.save()
				})
			)
			if (req.body.parentId) {
				// const parentPost = await Post.findOne({
				// 	where: { id: req.body.parentId }
				// })
				// if (parentPost) {
				// 	await parentPost.$add("children", newPost.id)
				// 	await parentPost.save()
				// }
				const parentPost = await Post.findByPk(req.body.parentId)
				if (parentPost) {
					newPost.$set("parent", req.body.parentId)
					await newPost.save()
				} else {
					throw new Error("Parent post not found")
				}
			}

			res.json(newPost)
		} catch (e) {
			res.status(400).json(e)
		}
	}
)

Proposed implementation

// server/src/routes/posts.ts
function render({ title, files, visibility, userId, password: hashedPassword, expiresAt }): PostResponse { 
	return { title, files, visibility, userId, password: hashedPassword, expiresAt }
}

posts.post(
	"/create",
	jwt,
	celebrate(...),
	hashPassword,
	async (req, res, next) => {
		try {
                        const { title, files, visibility, userId, hashedPassword, expiresAt, parentId } = req.body

                        const newPost = await postService.create({ title, files, visibility, userId, password: hashedPassword, expiresAt, parentId })
			res.json(render(newPost))
		} catch (e) {
			res.status(400).json(e)
		}
	}
)

We can then discuss how to implement the dependency injection mechanism of the postService.

Client: Display which fields are causing an Internal Server Error

drift_500

Issue:

When creating a new post https://drift.maxleiter.com/new, a missing filename results in an Internal Server Error but there is no clear indication letting me know that is the reason.

It took me a few seconds to notice that was why and then I was able to create the post. Though that may not be the case with other users.

Suggestions:
It would be nice if a field with missing input would be styled with border: 1px solid #F00; and/or a popover letting the user know that a missing input is preventing the post from being created.

drift_possible_fix

Another possible feature could be adding a succinct message in the red bubble,
e.g.: Internal Server Error: Enter filename

Bad padding at bottom of page when creating post

Describe the bug
From reddit:

Looks really good!

One thing, which should be a quick fix: When you are creating a new gist, the dropdown on the submit button will overflow outside of the viewport (so you have to scroll down after opening it). I would just add some extra padding to the bottom, or alternatively scroll down automatically when you open it.

Client: real-time syntax highlighting

GitHub Gists offers syntax highlighting inferred from the filename while modifying the text in the editor, which leads to a considerably nicer experience compared to having a separate tab for previews. Only when the filename ends in .md, do they render the preview tab.

Perhaps implementing something similar should be considered for this project as well. I realize this would not be trivial, as the syntax highlighting seems to take place on the server side at the moment.

Add denylist for invalid JWT tokens

When a user logs out we should add the JWT token to a table and add checks against it in the jwt middleware so it can't be used by a third party.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.