Lightweight and easily self-hostable Git forge.
farga DEVELOPMENT.md
4.4 kB
Markdown

Development post-it notes. #

Things/Snippets of code I should not forget.

Made as a quick cheatsheet for me and contributors to check if they're in doubt.

Using context to retrieve important values. #

Certain middlewares will store a couple of values on the context to make your developer life easier. Here's a couple examples:

user, ok := r.Context().Value(lib.UserKey).(*types.User)
session, ok := r.Context().Value(lib.SessionKey).(string)
loggedIn, ok := r.Context().Value(lib.LoggedInKey).(bool) // this should be fine to be used anywhere.
repo, ok := r.Context().Value(md.RepoKey).(types.Repo) // provided by the repo middleware

IMPORTANT AND TODO: use val, ok := ctx.Value(key).(Type); if !ok { ... } to check if the value is set.

Form validation. #

Use the md.ValidateForm such as

type LoginForm struct {
	Email    string `form:"email"    validate:"required,email"`
	Password string `form:"password" validate:"required,min=8"`
}

md.ValidateForm[LoginForm](http.HandlerFunc(a.login)) // Use handle, not handleFunc.

and retrieve the form using

form, ok := md.Form[LoginForm](r) // r being the request

Bear in mind forms have that special property because of contextKey.

Forms are CSRF-protected. {{ .csrfField }} must be added inside each form.

Mux routing. #

I use gorilla/mux as a router:

  • Use a blank space "" for indexes.
  • Use {path:.+} for those recursive paths.
  • Define all routers in app.go since it's be easier to manage.

Rendering templates. #

Use the following code:

a.Render(w, r, "base", "repo/view", map[string]any{"Repo": repo})

Where a is your *App, base the layout, repo/view is the template, and map[string]any{"Repo": repo} is the data, accessible as .Repo

Use {{.sshDomain}} and {{.sshPort}} to get the domain and port the SSH server will be listening at.

Useful variables:

{{.loggedIn}} //  bool, is user logged in? self-explanatory.
{{.user}} // types.User (if loggedIn is true)
{{.sshDomain}} // domain of the SSH server.
{{.sshPort}} // port which the SSH server is listening from.

When you need to access the root "." while (e.g) ranging, use "$" to access it. Example:

{{range .Files}}
    {{$.DirectoryName}}/{{.Name}}
{{end}}

Template rendering utils. #

I made a couple utils to be used on the templates.

For instance:

{{if IsText .Blob.Type}}
    <pre><code>{{.Blob.Content}}</code></pre>
{{else if IsImage .Blob.Mime}}
    <img src="{{Src .Blob.Content .Blob.Mime}}">
{{end}}

ALWAYS USE {{RepoRoot .Meta}} instead of "/~(user)/(repo-name)/". It's easier to change later. It has a leading slash and no trailing slash.

{{RepoRoot .DBRepo}} -> "/~poyo/banana"
{{TruncateStr "hello world" 5}} // -> "hello"
{{Mime .Blob}} // -> "image/png"

You can check the full list at the templating function itself.

Time. #

When rendering a date, pass in a UTC date into any element with a class local-time, and time.js will convert it to the user's local time.

Example:

<time class="local-time" datetime="{{.CreatedAt}}">{{.CreatedAt}}</time>

CSS Styling: #

.inline for inlined and gapped containers.

Message and Error displaying. #

?msg=AuthSuccess and ?err=RequireAuth are examples of query params that will show a banner on the page.

Keep in mind, validation errors are Base64 encoded (urlencoded) and are passed as ?err=ValidationErr-(base64). The messages and validation middleware handle it, so don't worry about it much. CSS for errors and messages is on the main stylesheet (styles.css).

.message, .warning and .error have a slight vertical margin. If you use the same class names as HTML attributes, they will have no margin.

Lib/Submodules. #

Please, separate stuff into lib submodules to avoid circular imports.

I had to suffer an hour of debugging because I implemented git handling on a db function. I scrapped everything and rewrote it separately, and it worked. That's proof separating is better!

Hackatime. #

Users can login using Hack Club's Hackatime. Why? To get stats on their projects.

Git. #

When opening a repo with git.Repo(), make sure to add defer repo.Close() after it.

Resources #