Git Product home page Git Product logo

buenzlimarks's Introduction

buenzlimarks

dependency status

This is a work in progress!

buenzlimarks is a web application for bookmark management and more.

Check it out at marks.buenzli.dev.

The full documentation is embedded in the application (marks.buenzli.dev/docs).

buenzlimarks's People

Contributors

senekor avatar robrisi avatar

Watchers

 avatar

buenzlimarks's Issues

Use on different devices

As a user, I want to use buenzlimarks on different devices and different browsers. They all shall show the same persistent bookmarks, so that i can use my bookmarks from anywhere.

ensure url schema

A bookmark stores a URL. Every url should have a schema at the beginning, for example:

  • http://
  • https://
  • file://
  • ...and more!

If users submit a bookmark with a url that lacks a schema, we should add a default schema of https, before storing the bookmark in the database.

This is the first example of business logic that we will have in our program. Business logic should go in the module models.

Show the favicon of a page

In front of the saved bookmarks, the favicon of the page should be shown. If the page is not fund, or has no favicon, a default buenzlimarks-icon should be displayed.

Refactor the database so filesystem path handling is centralized

Currently, database methods contain expressions like:

self.root_dir.join(format!("users/{user_id}/bookmarks"))

to create the filesystem paths for accessing the files in the database. Constructing these paths inline is error prone.

Create some sort of abstraction (function, module, trait... ??) which is in charge of the file paths. It should expose a type safe API to help the programmer avoid silly mistakes when trying to access the files stored on disk.

Add more auth methods

  • apple
  • google

which ones are enabled should be configurable at startup time.

to consider:

  • invitation system
  • password auth
  • guest accounts (purged after some time / number of guest accounts)

Filter bookmarks by widget_id

The GUI needs to group bookmarks by their widget, so the server should be able to provide a list of bookmarks only for a given widget_id.

Implement this additional behavior in the handler get_bookmarks that already exists. Use the Query extractor to get the query arguments form the HTTP request. (Similar to how path arguments are extracted with the Path extractor, for example in delete_bookmark.)

Query(params): Query<HashMap<String, String>>

The query is an optional set of unordered key-value pairs. In the url, it might look like this:

buenzlimarks.remlse.dev/api/bookmarks?widget_id=blabla

The part after the ? is the query. widget_id is the key, blabla is the value, separated by an equal sign.

You can find more information about this extractor here and here.

Document Architecture

  • 01 introduction and goals
  • 03 scope and context
  • 04 solution strategy
  • 07 deployment view
  • Setup documentation in readme

fix light mode UI

Some text appears black for light-mode users. That's a bad contrast on the dark background.

I imagine a <Text></Text> component which encapsulates default tailwind classes for text. This would be a great opportunity to add "tailwind merge" to the code base.

Testing infrastructure on backend

  • properly isolated unit tests for database, handlers and entities using mocking where appropriate
  • integration tests to sanity-check all routes (does this provide value?) do not repeat the same unit tests as integration tests!
  • property testing ?
  • fuzz testing ?

refer to matklad/how-to-test

Put the CD in CI/CD

Compiling for realease takes a long time on the raspberry pi. Doing the build externally will make updating on it more seamless.

other options:

  • cross compile locally, push new build over ssh
  • build in GitHub workflow, have script on raspi that pulls new builds

Building on copr is the option that most seamlessly integrated with the system.

(Maybe even consider making buenzlimarks a systemd service. Update process: dnf update & systemctl restart. Nice.)

https://docs.fedoraproject.org/en-US/quick-docs/publish-rpm-on-copr/

Create justfile

Purpose:

  • efficiently execute any development related commands
  • document these commands for everyone else

Scripts in dev directory may remain so the justfile doesn't get too long. Just recipes can simply call those scripts.

View bookmarks

As a user, I want to see bookmarks when navigating to buenzlimarks.remlse.dev, so that I don't have to remember the URLs of my favorite websites.

I want to be able to click on the bookmarks, so that the linked webpage opens in the same tab. Each bookmark should show the label, not the URL.

Add API endpoints to fetch and create pages and widgets

Currently, it is not possible to add new bookmarks for users other than the default development user. The reason is that other, newly-created users don't have any pages or widgets yet. A valid bookmark requires an ID of a valid widget. A valid widget, in turn, requires an ID of a valid page. Therefore, it must be possible to create and fetch pages and widgets, before bookmarks are fully functional for new users.

Create "Hello World" server

We are going to use axum to write the rust server. Familiarize yourself with its documentation. Once you have a good idea of what axum is and what it does, use it to write a web server that responds with "Hello world!".

Expect to spend some time on reading. You will probably have to search for and read other resources as well, just to understand the documentation of axum. Once you do, the coding part will be easy.

Return 404 when single entity is not found

When a single entity is fetched with an invalid ID, i.e. one that doesn't exist in the database, the server returns a 500 "internal server error" response. It would be more appropriate to return a 404 "not found".

example: GET request to /widgets/invalid-id

Setup CI

  • Rust
  • Solid (covered by the cargo build script)
  • Markdown formatting
  • Conventional commits (shell out to git inside a rust tests to avoid a complicated CI config)

embed documentation in server under `/docs`

This will be a more consistent user experience where everything buenzlimarks-related is served from the same domain.

Also it allows greater flexibility, as we are no longer tied to github pages.

async filesystem access

context

The webserver framework we are using is asynchronous. That means request handlers may be stopped and have to wait while other handlers continue their work. This leads to better performance, because while one handler is waiting for some IO operation like networking or access to the filesystem, another handler may do useful work in the meantime.

You may have noticed the keywords asnyc and await in the code base. An asnyc function can be stopped and wait for some other thing, while other tasks make progress. The await keyword is used to await the completion of such a task, like networking or filesystem access.

what to do

Even though we're technically using async, our filesystem access is using the blocking1 functions from std. Our async runtime tokio has its own functions for accessing the filesystem in an asynchronous manner. Unlike the blocking operations from std, the return values of such asynchronous filesystem operations need to be awaited with .await. See the documentation of tokio for examples.

Replace the use of std::fs with tokio::fs.

Footnotes

  1. "blocking" is the opposite of "async". When a blocking operation is waiting for something, like the filesystem, other tasks are blocked from making progress. We don't want that! โ†ฉ

Simplify persistence

SeoORM is nice, but overkill for our purposes. As such, it feels too heavyweight for our purposes.

Remove SeoOrm and replace it with a simple, file based persistence approach.

  • Apply best practice and let the database implementation depend on a trait defined by the service. The file based approach could then be replaced by another one later. Maybe some middle ground in terms of complexity, e.g. sled.
  • The file based approach will present an opportunity to implement database concepts ourselves in a simple way and learn from it. For example, there should be a lightweight concept of migration. Say, the database interface (trait) could define a function version, based on which the service can determine what migrations should be applied.
  • Document this architectural decision according to the arc42 standard.

Better observability

Let's send our logs to a monitoring service via OpenTelemetry. One option for a self-hosted monitoring service would be Jaeger.

https://broch.tech/posts/rust-tracing-opentelemetry/

Originally posted by @remlse in #30 (comment)


Currently, our tracing logs go to stdout and can be retrieved via journalctl, since we deploy as a systemd service. Sifting through the logs could be much nicer.

Apart from a little enabling work in buenzlimarks itself, I suspect the bigger task would be to self-host Jaeger and wire everything up in the deployment repo.

Use Zod for sever side data fetching

Use something like transform with regular class constructors, mixins etc. to enable a little more object oriented programming, mimicking what can be done on the server side with Rust's traits.

After the ts-trait experiment with falk, I'm not very keen to do this. There's also no reason to believe we'll have enough business logic client-side for this to actually be useful.

Let's stick to functional-ish style on the client.

Prevent deletion of non-empty widgets

As stated in the requirement #10, non-empty widgets should not be able to be deleted.

Most importantly, this should be enforced on the backend.

However, to ensure a good user experience, let's also prevent the user from deleting non-empty widgets in the first place on the frontend. For example, the button that deletes a widget could be disabled and greyed out a little for visual distinction.

And while we're at it, let's also do the same for pages. Same problem there.

Sort widgets in page

A widget shoult be freely placeable on the page and in a column by the user. Changes must be persistent.

Add a bookmark (backend)

Enable users to add new bookmarks. Some of the steps required include:

  • add a method to the database for storing a new bookmark
  • add a new http handler for these requests, which in turn calls the database method
  • add the new http handler to the router at the appropriate route and with the appropriate http method (POST in this case.)
  • add unit tests for the database and the http handler, examples for orientation are present in the source code. (don't just consider the happy path! add unit tests for internal errors and wrong user input as well.)

Considerations and possible challenges:

The id of the bookmark should always be generated randomly by the server. However, for deserialization to work easily, the user has to provide some dummy id. make sure to override the user-provided id with a random one. Use the function uuid::Uuid::new_v4() for this purpose, it provides industry-standard random ids.

A bookmark stores the id of the widget it belongs to. The database should verify that such a widget exists.

The user may provide broken links / urls in the new bookmark. Let's not consider this for now and accept any string as bookmark.

Serve frontend from root

The frontend is a bunch of statically served files located in buenzlimarks/app/dist. So, a GET request to the root path should serve files in that directory. Axum has an examle of serving files from a directory here. Relevant is the ServeDir service from tower-http.

Prefix your existing routes (e.g. the hello world stuff) with /api/.

Also, while you're at it, please change the port from 3000 to 4000. This will make it easier to have both frontend and backend running locally in developer mode.

Once that's done, I can get started with the deployment!

Delete a Bookmark

As a user, I want to delete Bookmarks, so that i get rid of bookmarks that are not needed any more.

Evalute migration to mermaid js

from gaphor, for documentation diagrams.

C4 diagrams are supported experimentally by mermaid.However, it may be preferrable to drop the C4 specific style and go for simpler diagrams instead. Advantages:

  • Learned skills are trasferrable to other use cases calling for diagrams.
  • Obsidian has integrated support for mermaid. Documentation could be written and viewed in Obsidian. (+ hot reload, autoformat) (- how to deploy these docs? would be nice to have them on github pages for example.)
  • Easier setup / onboarding.
  • No more fiddling around in the gaphor GUI.
  • Seems to be under active development.

Layout and sorting

  • The existing bookmarks should be freely sortable by drag and drop. New items should be added at the end of the list.
  • Widgets should be placed in multiple columns in a page. It should be customizable by the user, how many rows he want.
  • A widget should be freely placeable on the page and in a column by the user.

Add a bookmark

As a user, I want to add new bookmarks, so that I don't have to remember the URLs of my favorite websites.

In Addition to the URL, i want to enter a label

User management

As a user, I want to identify myself and only see my bookmarks. This should also apply to any number of other users. My bookmarks must not be visible to other users.

Handle error for bookmarks with invalid widget_id

When creating a new bookmark with an invalid widget_id, the server responds with

500 INTERNAL SERVER ERROR

This is because the database returns DbError::WhoopsieDoopsie in such a case. We recently created a new error variant NotFound, let's reuse that. The user will then see the error message:

404 NOT FOUND

That is much better and will suffice for the moment.

Hidden widget-features

In its default state, the UI should be optimized for finding and using the bookmarks.

Editing, configuration etc. should not clutter the main UI.

One approach to achieve this could be to implement a global "edit mode" which activates a large number of otherwise hidden UI elements.

make database more generic

We have already started abstracting over the concept of an entity with our DbEntity trait. Let's expand this trait to ideally encompass all entity specific behavior.

We will know that we have succeeded once the database doesn't have any methods like get_bookmark anymore, only get_entity.

Improve environment variable handling

environment variables are sometimes loaded by just, sometimes by the program... relative paths can mess things up. find a maintainable way to handle this. maybe go back to dotenvy if that helps.

consider using conditional compilation for the DB directory: in debug mode, it defaults to ../dev/db (unless another directory is specified). in release mode, it should panic at startup if the DB dir is not set.

consider using clap to parse the environment variables, which would also generate documentation about available variables with buenzlimarks --help.

Add API endpoint to update bookmarks

We currently don't have any API endpoints to update an entity. Let's start with bookmarks for now. Once we have an 'update' endpoint, we will have an example of every kind of endpoint of a CRUD API!

Group bookmarks in widgets

As a user, I want to group Bookmarks in widgets, so that I can summarize them according to different topics.

The widgets should be blocks that have an editable title.

Non-empty widgets cannot be deleted.

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.