Compare commits

...

9 Commits

Author SHA1 Message Date
admin d78ada2c0b add mundane programming entry 2026-05-28 22:52:57 +02:00
admin a24644ca87 update fileclap blog 2026-05-18 23:58:54 +02:00
admin bba149acac set up fshare cache 2026-04-28 15:27:48 +02:00
admin 3282cc518e move namespace 2026-04-13 16:58:31 +02:00
admin e772800038 bruh woopsis 2026-04-11 13:54:54 +02:00
dependabot[bot] cda357a972 build(deps): bump smol-toml from 1.6.0 to 1.6.1 (#7)
Bumps [smol-toml](https://github.com/squirrelchat/smol-toml) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/squirrelchat/smol-toml/releases)
- [Commits](https://github.com/squirrelchat/smol-toml/compare/v1.6.0...v1.6.1)

---
updated-dependencies:
- dependency-name: smol-toml
  dependency-version: 1.6.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 13:47:43 +02:00
dependabot[bot] b13c4e2d86 build(deps): bump picomatch (#8)
Bumps  and [picomatch](https://github.com/micromatch/picomatch). These dependencies needed to be updated together.

Updates `picomatch` from 4.0.3 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

Updates `picomatch` from 2.3.1 to 2.3.2
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/4.0.3...4.0.4)

---
updated-dependencies:
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
- dependency-name: picomatch
  dependency-version: 2.3.2
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-11 13:47:32 +02:00
admin 4d554056a2 add sso, gittea, kuma 2026-04-11 12:55:18 +02:00
admin 5606646ab0 update dependencys 2026-04-11 12:53:54 +02:00
11 changed files with 1453 additions and 1396 deletions
-1
View File
@@ -1 +0,0 @@
flake-profile-1-link
-1
View File
@@ -1 +0,0 @@
/nix/store/p7z5gbs1jx39z6x9mb6rgg3izkkjgw24-nix-shell-env
+1
View File
@@ -22,3 +22,4 @@ pnpm-debug.log*
# jetbrains setting folder # jetbrains setting folder
.idea/ .idea/
.direnv
+1069 -1377
View File
File diff suppressed because it is too large Load Diff
+5 -5
View File
@@ -12,11 +12,11 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.6", "@astrojs/check": "^0.9.2",
"@astrojs/mdx": "^4.3.13", "@astrojs/mdx": "^5.0.3",
"@astrojs/rss": "^4.0.15", "@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.0", "@astrojs/sitemap": "^3.7.2",
"astro": "^5.17.1", "astro": "^6.1.5",
"sharp": "^0.34.3", "sharp": "^0.34.3",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

+2 -1
View File
@@ -1,4 +1,5 @@
import { defineCollection, z } from "astro:content"; import { defineCollection } from "astro:content";
import { z } from "astro/zod";
import { glob } from "astro/loaders"; import { glob } from "astro/loaders";
const personal = defineCollection({ const personal = defineCollection({
+104 -7
View File
@@ -8,6 +8,7 @@ gitLink: "https://github.com/juli0n21/fileclap"
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import homeview from "@assets/fileclap/homeview.png"; import homeview from "@assets/fileclap/homeview.png";
import search from "@assets/fileclap/search.png"; import search from "@assets/fileclap/search.png";
import cicd from "@assets/fileclap/cicd.png";
<style is:global>{` <style is:global>{`
h1, h2, h3, h4, h5, h6, figcaption::before { color: #ffdf20 } h1, h2, h3, h4, h5, h6, figcaption::before { color: #ffdf20 }
@@ -26,19 +27,115 @@ import search from "@assets/fileclap/search.png";
# FileClap: Clear the paperwork off the table # FileClap: Clear the paperwork off the table
Your digital assistant for students and trainees
No more paper chaos! FileClap helps you easily organize photos, receipts, and important documents. Securely stored and accessible from anywhere—for a stress-free daily life with more clarity! No more paper chaos! FileClap helps you easily organize photos, receipts, and important documents. Securely stored and accessible from anywhere—for a stress-free daily life with more clarity!
<Image src={homeview} alt="homeview" /> <Image src={homeview} alt="homeview" />
Advanced Search Capabilites using Vector embeddings for shit Advanced Search Capabillites using Vector embeddings.
# Architecture and Technologies
## General
FileClap is built on golang with <a>a-h/templ</a> as a frontend technology, works with any s3 compatible Objectstorage. <a>Keycloak</a> is used for the authentication migrated from a previously self contained login flow.
Redis is used to allow scalability in a microservice deployment.
A seperate Thumbnail Generator was created in addition to create thumbnails for all types of files. Because it uses a large libraries like ffmped and pdf parsers, it was extracted into a seperate service. This allows the main service to be a small 32mb docker image, while the thumbnail generator is at 163mb after heavy optimization from an original 1gb+ container, the connection between the fileclap and the thumbnail generator runs over grpc.
To allow for the vector embeddings that enable the semantic search, a migration from sqlite to postgres has taken place.
For Observability OpenTelemetry is used on a function level basis, focussing on heavy operations like storage actions, database accessing and using the ocr service:
```go
func (c *S3client) UploadObject(ctx context.Context, key string, body io.Reader, contentType string, user models.User) error {
//add new span for this function
tracer := otel.Tracer("fileclap_" + Version)
ctx, span := tracer.Start(ctx, "s3.UploadObject")
defer span.End()
_, err := c.Client.PutObject(ctx, &s3.PutObjectInput{
Bucket: aws.String(user.ID.String()),
Key: aws.String(key),
Body: body,
ContentType: aws.String(contentType),
})
if err != nil {
//add error to span in cause something breaks
span.RecordError(err)
return err
}
return nil
}
```
To minimize risks of data collisions and chances of different tenants accessing data of each other, each tenant has been given a s3-bucket that is just his user id. in the same way are all file / web requests handled:
`https://fileclap.com/{userid}/operation/{fileid}/etc` `{userid}` representing a user and `{fileid}` representing a file inside that user context
## Frontend
htmx was used to enhance component based template generation from a-h/temple, the both of them work really well together since it is trivial to make use of rendering conditional components or full sides with just a simple header check, in addition u can save a lot of computing power when not even fetching unneeded data in case u just need sub components:
```go
func (s *Server) GetLatestFiles(w http.ResponseWriter, r *http.Request) response {
u := models.GetUser(r.Context())
limit, offset := pagination(r)
files, err := s.FileRepository.GetRecentFiles(r.Context(), u, limit, offset)
if err != nil {
return response{err: err}
}
if hxrequest(r) { //if htmx request return file component directly
cmp := components.Wrapper(web.Folders(files, "Latest", limit, offset))
return response{err: cmp.Render(r.Context(), w)}
}
//fetch folders to render full page which contains more stuff then just the fragment
folder, err := s.FileRepository.GetAllFolders(r.Context(), u)
if err != nil {
return response{err: err}
}
cmp := components.Wrapper(views.Index("Latest", folder, "latest"))
return response{err: cmp.Render(r.Context(), w)}
}
```
while in conclusion a nice pair to work together, when ur used to force logic into the frontend to minimize server calls it realy becomes a mess and harder to debug. for example the file uploading is delegated to the frontend using presigned links which cant be done with just htmx so u have to create javascript which just isnt nice in temple if u again dont want to make unnecessary server calls
## Vector embeddings
to allow for semantic search, currently all text files are scanned and turned into embeddings.
The contents of a document is prepared first. in the first phase it has an llm generate:
- keywords: a list of words a user would potentially use to associate with the document
- summary: a two sentence summary of the content
- metadata: Dates, places, contenttype, contacts, bills or what ever could be relevant based on the content
- tags: simple one sentence words that add in user filters: invoice may payment 2025 work
- folder: based on the list of existing folder names which one could fit / or create a new one
in the second phase embeddings are created for the each of the generated values, and every piece of content is split in to 75 long chunks with a 5 char overlap to the previous chunk. this increases short search term accuracy immensely
## Ci/cd
for integration and deployment is a github actions pipeline used thats run on main push
- it builds the go application
- it builds the docker application
- it runs the go binary, including a postgres db and runs playwright integration tests testing on a majority of browsers the uploading, searching, downloading, deleting of files
- if the docker build and integration tests are complete it pushes the container to the docker-hub repository which is later deployed using gitOps (argocd) manually. in a previous iteration it was set up inside the repository but due to having one repository for all deployments now its no longer allowed due to security risks
<figure>
<Image src={cicd} alt="cicd" />
<figcaption> Github actions pipeline, funnily enough the pipeline spends the majority of time downloading dependencies, the time could be reduced to 2 mins total, but playwright caching is not properly doable and the 500mb action cache store in total across ur entire account is just 10 times to little to be of any usage using even a basic alpine image as a builder </figcaption>
</figure>
## Performance testing
Since using heavy caching for almost everything and delegating stuff like object management to the s3 provider, is the application in a simple locust test the service was able to serve 100s of requests every second without the user noticing any latency.
on the other hand the searching through the documents is comparativley slow, its probaly due to missing db indexes in the vector space and the sheer amount of items considering uploading a single book creats thousands of embeddings, and the extra round trip off embedding the value using openais api. there's also 0 caching in either the embedding request themself or the results
<figure> <figure>
<Image src={search} alt="search" /> <Image src={search} alt="search" />
<figcaption>Preview of the Search result for value "golang books"</figcaption> <figcaption>Preview of the Search result for value "golang books"</figcaption>
</figure> </figure>
<a href="https://fileclap.com/home" target="_blank">
<h1>check it out!</h1>
</a>
+5 -3
View File
@@ -44,12 +44,16 @@ p, a, ul { color: #b1b4c0}
</div> </div>
<div style="text-align:left;"> <div style="text-align:left;">
#### Server stuff #### Server Stuff
<a target="_blank" href='https://github.com/juli0n21/deployments' title="deployments - github"><GithubIcon width="100px" height="100px" /></a> <a target="_blank" href='https://github.com/juli0n21/deployments' title="deployments - github"><GithubIcon width="100px" height="100px" /></a>
<a target="_blank" href="https://argocd.illegalesachen.download/" title="argocd"><img src="https://argo-cd.readthedocs.io/en/stable/assets/logo.png" heigth="100px" width="100px" /></a> <a target="_blank" href="https://argocd.illegalesachen.download/" title="argocd"><img src="https://argo-cd.readthedocs.io/en/stable/assets/logo.png" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://metrics.illegalesachen.download/" title="metrics"><img src="https://grafana.kami.wtf/public/build/img/apple-touch-icon.png" heigth="100px" width="100px" /></a> <a target="_blank" href="https://metrics.illegalesachen.download/" title="metrics"><img src="https://grafana.kami.wtf/public/build/img/apple-touch-icon.png" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://docker-ui.illegalesachen.download/" title="docker-ui"><img src="https://docker-ui.illegalesachen.download/favicon.ico" heigth="100px" width="100px" /></a> <a target="_blank" href="https://docker-ui.illegalesachen.download/" title="docker-ui"><img src="https://docker-ui.illegalesachen.download/favicon.ico" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://sso.illegalesachen.download/" title="keycloak"><img src="https://www.keycloak.org/resources/images/icon.svg" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://gittea.illegalesachen.download/" title="gittea"><img src="https://about.gitea.com/gitea.svg" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://status.illegalesachen.download/" title="uptime-kuma"><img src="https://uptimekuma.org/wp-content/uploads/2025/01/Uptime-Kuma-Logo.png" heigth="100px" width="100px" /></a>
<a target="_blank" href="https://web-ui.illegalesachen.download/" title="open-webui"><img src="https://web-ui.illegalesachen.download/static/favicon-dark.png" heigth="100px" width="100px" /></a>
</div> </div>
<div style="text-align:right;"> <div style="text-align:right;">
@@ -71,8 +75,6 @@ p, a, ul { color: #b1b4c0}
<a target="_blank" href='https://github.com/juli0n21/kickstart.nvim' title="kickstart - github"><GithubIcon width="100px" height="100px" /></a> <a target="_blank" href='https://github.com/juli0n21/kickstart.nvim' title="kickstart - github"><GithubIcon width="100px" height="100px" /></a>
</div> </div>
<div style="text-align:left;">
</div>
</div> </div>
# Hardware # Hardware
@@ -0,0 +1,266 @@
---
title: "mundane programming"
description: "build software, that is easy to read, write and maintain"
pubDate: "Apr 01 2026"
---
<style is:global>
h1,
h2,
h3,
h4,
ul li,
figcaption:before { color: #00d5ec }
body, .wrapper, nav::after {background: #202023 }
a, p, ul { color: #ffc107 }
img {
border: 5px solid;
border-color: #101010;
}
</style>
# Mundane Programming
In the Age of ever Approaching Agi, one needs to be prepared, because until then you are still better off having an understanding of your application and system. This is an opinionated guide on how to build mundane crud applications, fast, which are easy to understand and debug.
## Wants and Lacks
what would a simpleton want from a programming language to aid in building a mundane crud application?
- easy to use toolchain: a handful of commands to let the toolchain, compile (fast), check and test the application, bonus points if dependency management is included but be careful with them more on that later.
- a list of simple features, if/else, switches, structs, functions, enums and loops, arrays and maps. Lastly a decently sized standard library providing a ready to use webserver implementation. I.E. simple request routing, and basic request handling.
- an idiomatic way to be written, forcing the same (code|formatting) style onto everyone.
- type safety, exhaustive switches, errors as values, and multiple return values
- garbage collected, memory management is a waste of time
for the crud aspect a simpleton would need a simple database to store the data
- sql compatible
- widely used
- type safety
as to stuff a simpleton wouldn't want:
- multiple ways of doing things
- magical syntactical sugar
- complicated features
## Dependencies
More than ever before have dependencies been filled with exploits and vulnerabilities, so the simpletons way is to just not use them, if every dependency is a liability wouldnt it make sense to just reduce it to the bare minimum? Even then, quite often a very small subset of features is actually required. So make sure to think twice before using any dependencys
In the same ways versions should be locked by default, and or directly vendored into your own source code to prevent any infections from outside to get in.
## Programm layout
Books are read from Left to right, top to bottom. so why shouldn't software be written the same way?
Abstraction should only be used to reduce complexity not to introduce it.
Interfaces are only of worth if multiple implementations exist and its less effort to use then just a simple enum switch
Layers should be kept to a minimum
encapsulation as a goal is very bad, the design should be as transparent as possible to easily see how and when components interact with each other
code sharing should be done with caution.
## Programming Patterns
### functions
functions that change state should be obviously named and only should act on the state management like, databases, object stores or caches.
early exit should be the default, be it validation failure, missing data or incomplete requirements
### enums and switches
need to make a decision based on a specific state of an object? enums are your best friend
enums allows a simpleton to deal easily with everchanging business requirements and all the other unique annoying ways software tends to change over time.
### imperative
top-down, left to right why make it complicated just do it like one would write a book
## Implementation ways
How does a mundane system look like? let's build a small one together:
although go doens't fit into all of our requirements missing many things in the safety aspect it still comes the closest. if only gleam had loops ...
the entire program should follow this pattern to handle a request:
state management, routing delegation, authorization and authentication, input validation, data handling, view / presentation generation, return
now this doesn't seem very simpleton friendly, but seeing it in an example a simpleton might be able to understand.
### State management
While dependency injection is still all the rage in the enterprise environment. a simpleton knows a struct containing the state is all you need. it will contain anything one might need.
```go
type Server struct {
db Database
storage Storage
}
```
### Route delegation
```go
func NewHandler(s *Server) http.Handler {
router := http.NewServeMux()
//list all endpoints groupd by functions and pahts
router.HandleFunc("/", s.Index)
router.HandleFunc("/login", s.Login)
router.HandleFunc("/logout", s.Logout)
router.HandleFunc("GET /user/{userid}", s.User)
return router
}
```
### Authorization and Authentication
to follow the simple written like a book approach we want the authentication and authorization to be handled even before the route specific handler is reached (return early), to achieve this in golang one follows the middleware approach, which boils down to a function which the request goes through before reaching the actual handler.
here the required features are added one by one.
```go
var Users sync.Map
const AuthCookie string = "my_session_cookie"
func (s *Server) AuthUser(next func(w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
cookie, err := r.Cookie(AuthCookie);
if err != nil {
http.Error(w, "Cookie not found", http.StatusUnauthorized)
return
}
_, ok := Users.Load(cookie)
if !ok {
if !s.db.isUserLoggedIn(r.Context(), cookie) {
http.Redirect(w, r, "/login", http.StatusFound)
return
}
}
return next(w, r)
})
}
```
### Putting it together
now that we have our state managed and the difficult parts secured we can go back to the actual meat of the crud application.
to fulfill our request of it being written like a book, the simpleton just writes one function that describes the entire flow until the return.
```go
func (s *Server) GuideLine(w http.ResponseWriter, r *http.Request) {
type Request struct {
//a Request object that contains the required / optional variables needed for this specific request
Value string
Math int
}
var req Request
// parsing the raw request and validate the input
req.Value = r.PathValue("value")
if req.Value == "" {
http.Error(w, "bad value provided", http.StatusBadRequest)
return
}
// followed by the buisness logic
// fetch some data
data, err := s.db.GetTheValues(r.Context(), req.Value)
if err !=...
// do some things
data = applyBusinessLogic(data)
// save the data
err = s.db.SaveTheValues(r.Context(), data)
if er...
// render / return the result or error incase something unexpected happens
fmt.Fprintf(w, "Successfully handled the request")
}
```
for example the Login function could look like the following
```go
func (s *Server) Login(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
LoginPage(w, r)
return
}
type Request struct {
Username string
Password string
}
var req Request
err := r.ParseForm()
if err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
req.Password = r.PostForm.Get("password")
req.Username = r.PostForm.Get("username")
if (req.Password == "" || req.Username == "") {
http.Error(w, "Missing required fields", http.StatusBadRequest)
return
}
user, err := s.db.LoginUser(r.Context(), req.Username, req.Password)
if err != nil {
http.Error(w, "invalid credentials", http.StatusUnauthorized)
return
}
cookieVal := rand.Text()
Users.Store(cookieVal, user)
cookie := http.Cookie{
Name: AuthCookie,
Value: cookieVal,
MaxAge: 3600 * 24,
HttpOnly: true,
SameSite: http.SameSiteLax,
Secure: true,
}
http.SetCookie(w, &cookie)
http.Redirect(w, r, "/", http.StatusFound)
}
```
and to handle the `User` request we just follow the exact same pattern of
input variables, business logic, redirect / render / or return
```go
func (s *Server) User(w http.ResponseWriter, r *http.Request) {
type Request struct {
UserId string
}
var req Request
req.UserId = r.PathValue("userid")
if req.UserId == "" {
NotFoundPage(w, r, "UserId not provided")
return
}
user, err := s.db.GetUserById(r.Context(), req.UserId)
if err != nil {
NotFoundPage(w, r, "User not found")
return
}
UserPage(w, r, user)
}
```
and that's about it.
## Nuances
Now Software development is about much then coding your unique situation, business context, and user count changes the even the basic ideas about programming, for example the simple `sync.Map` approach for session storage works well for a few users but can quickly bend its knees, so one could replace it with a custom read-lock optimized map a or even external cache like redis, to allow multiple instances sharing the same cache.
But this text advocates for building a house for the people you have, not that you potentially could hypothetically have in the future. There's no advantage in splitting up into microservices, adding queues or complex routing if a single sever with a single instance running can handle the requests without any issues.
+1 -1
View File
@@ -12,7 +12,7 @@ const response = await fetch(api);
if (response.ok) { if (response.ok) {
const data = await response.json(); const data = await response.json();
const files = data.files.map( const files = data.files.map(
(f: Response) => `https://fshare.kami.boo/raw${f.url.substring(2)}`, (f: Response) => `https://shit-cache.illegalesachen.download/${btoa('https://fshare.kami.boo/raw' + f.url.substring(2))}`,
) as string[]; ) as string[];
const middle = Math.round(files.length / 2); const middle = Math.round(files.length / 2);