maxleiter / drift Goto Github PK
View Code? Open in Web Editor NEWDrift 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
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
Users should be able to generate API tokens so they can write scripts / plugins
Describe the bug
You can view private posts is if they are unlisted.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
You shouldn't see the 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.
Should be do-able with getStaticProps.
I mean, is there some technical stuff I should take care if I remove the limit on the file type in the uploader component?
Is there a list of websites that use Drift/part of Drift?
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.
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.
This will eliminate the need for the header to determine what buttons to show on the client, as we can know the users authentication state at page build.
Describe the bug
A clear and concise description of what the bug is.
To Reproduce
Steps to reproduce the behaviour:
.go
or .mod
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
โฏ 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):
Additional context
Add any other context about the problem here.
This should be easy with sequelize, the config and what-not just need to be added.
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:
replaces
nullable foreign key column.checksum
nullable column of some sort to contain a checksum (maybe a BLAKE3 checksum, but it doesn't matter).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:
replaces
to the ID of the old File which it replaces.contents
column.diff
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:
replaces
column is null
, skip and move on to the next File current for the Post.replaces
another File, grab the replaced File's diff.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.
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
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.
parent
attribute that Posts have)This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.
These updates have all been created already. Click a checkbox below to force a retry/rebase of any.
@next/bundle-analyzer
, @types/express
, @types/jest
, @types/jsonwebtoken
, @types/marked
, @types/node-fetch
, @types/react
, @types/react-datepicker
, @types/react-dom
, client-zip
, dotenv
, eslint
, eslint-config-next
, next
, prettier
, rc-table
, supertest
, ts-jest
, ts-node
, tsc-alias
, typescript
)@types/jest
, jest
, ts-jest
)These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.
docker-compose.yml
client/Dockerfile
node 17-alpine
node 17-alpine
node 17-alpine
server/Dockerfile
node 17-alpine
node 17-alpine
node 17-alpine
.github/workflows/server-CI.yaml
actions/checkout v3
actions/setup-node v3
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
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)"
}
}
We already server raw HTML, so if we set the proper content-type and some headers this should be fairly easy.
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)
We don't need to use react-markdown on published posts, and should instead render the markdown to HTML on the server.
... it's not pretty.
geist-ui is great for prototyping, but heavily reliant on JS and not very customizable.
not very important, but would be a nice addition for on-boarding.
We can accomplish this with https://github.com/remarkjs/remark-math
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.
A "download all" button combined with this would allow easy sharing of entire (small) projects/directories.
As mentioned by @jazcarate in #62, we should use https://sequelize.org/v3/docs/transactions/ throughout the code base (where it's appropriate)
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.
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:
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):
Additional context
Add any other context about the problem here.
Describe the bug
In the demo page (https://drift.maxleiter.com/) the welcome content (set by WELCOME_CONTENT
env
To Reproduce
Steps to reproduce the behavior:
Expected behavior
The welcome content looks good
Someone mentioned this on HN and I can see a valid use-case as long as it's made clear that the post will be available only in that browser + never sent to the server + could be removed by the browser at any time.
We should try to use https://developer.mozilla.org/en-US/docs/Web/API/StorageManager and fall-back to indexedb if it's unavailable.
Not currently very easy to implement, but eventually it would be nice to warn the user while they're editing if they have an image element without an alt attribute.
Server and client both have their respective Dockerfiles, which seem to work, but they can't communicate with one another.
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
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)
}
}
)
// 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
.
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.
Another possible feature could be adding a succinct message in the red bubble,
e.g.: Internal Server Error: Enter filename
We can use https://geist-ui.dev/en-us/components/file-tree on both the new post and viewing post views to allow easier jumping between files. Bonus points if we add icons for different file types.
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.
I'm very bad at running prettier, and there is a very easy way to hook it to the git pre-commit in both server/
and client/
https://twitter.com/diegohaz/status/1516516547342352391?s=21&t=glWCZjqu5Ez06KJE2HXenw
We should support both GHs anchor markup and our own.
I envision server hosts giving their friends their 'secret', allowing people that know it to register accounts
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.
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.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.