vault backup: 2026-09-26 11:26:54

This commit is contained in:
2026-09-26 11:26:54 +02:00
parent 6c149e0a85
commit 5f357f351e
10 changed files with 145 additions and 7388 deletions
+2
View File
@@ -1,2 +1,4 @@
/.obsidian/workspace.json
/.obsidian/plugins/*
/.obsidian/workspace.json
+6 -1
View File
@@ -1 +1,6 @@
{}
{
"cssTheme": "",
"accentColor": "#16ac20",
"theme": "system",
"interfaceFontFamily": ""
}
+2 -1
View File
@@ -1,5 +1,6 @@
[
"obsidian-git",
"tasknotes",
"advanced-canvas"
"advanced-canvas",
"awesome-format-bar"
]
-39
View File
@@ -1,39 +0,0 @@
{
"settings": {
"migrated": 6,
"iconPacksPath": ".obsidian/icons",
"fontSize": 16,
"emojiStyle": "native",
"iconColor": null,
"recentlyUsedIcons": [
"LiCheck",
"LiTasknotesSimple",
"LiBookMarked",
"LiInfo"
],
"recentlyUsedIconsSize": 5,
"rules": [],
"extraMargin": {
"top": 0,
"right": 4,
"bottom": 0,
"left": 0
},
"iconInTabsEnabled": false,
"iconInTitleEnabled": false,
"iconInTitlePosition": "above",
"iconInFrontmatterEnabled": false,
"iconInFrontmatterFieldName": "icon",
"iconColorInFrontmatterFieldName": "iconColor",
"iconsBackgroundCheckEnabled": false,
"iconsInNotesEnabled": true,
"iconsInLinksEnabled": true,
"iconIdentifier": ":",
"lucideIconPackType": "native",
"debugMode": false,
"useInternalPlugins": false
},
"00 About": "LiInfo",
"01 Project docs": "LiBookMarked",
"TaskNotes": "LiCheck"
}
File diff suppressed because one or more lines are too long
-10
View File
@@ -1,10 +0,0 @@
{
"id": "obsidian-icon-folder",
"name": "Iconize",
"version": "2.14.7",
"minAppVersion": "0.9.12",
"description": "Add icons to anything you desire in Obsidian, including files, folders, and text.",
"author": "Florian Woelki",
"authorUrl": "https://florianwoelki.com/",
"isDesktopOnly": false
}
-120
View File
@@ -1,120 +0,0 @@
.iconize-inline-title-wrapper {
width: var(--line-width);
max-width: var(--max-width);
margin-inline: var(--content-margin);
}
.iconize-title-icon {
max-width: var(--max-width);
margin-right: var(--size-4-2);
}
.iconize-icon-in-link {
transform: translateY(20%);
margin-right: var(--size-2-2);
display: inline-flex;
}
.iconize-icon {
border: 1px solid transparent;
margin: 0px 4px 0px 0px;
display: flex;
align-self: center;
margin: auto 0;
}
.nav-folder-title,
.nav-file-title {
align-items: center;
}
.iconize-setting input[type='color'] {
margin: 0 6px;
}
.iconize-modal.prompt-results {
margin: 0;
overflow-y: auto;
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
}
.prompt .iconize-subheadline {
margin-top: 12px;
font-size: 12px;
color: gray;
grid-column-start: 1;
grid-column-end: 6;
}
@media (max-width: 640px) {
.iconize-modal.prompt-results {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.prompt .iconize-subheadline {
grid-column-end: 4;
}
}
.iconize-modal.prompt-results .suggestion-item {
cursor: pointer;
white-space: pre-wrap;
display: flex;
justify-content: flex-end;
align-items: center;
flex-direction: column-reverse;
text-align: center;
font-size: 13px;
color: var(--text-muted);
padding: 16px 8px;
line-break: auto;
word-break: break-word;
line-height: 1.3;
}
.iconize-modal.prompt-results .suggestion-item.suggestion-item__center {
justify-content: center;
}
.iconize-icon-preview {
font-size: 22px;
}
.iconize-icon-preview img {
width: 16px;
height: 16px;
}
.iconize-icon-preview svg {
width: 24px;
height: 24px;
color: currentColor;
margin-bottom: 4px;
}
.iconize-dragover {
position: relative;
}
.iconize-dragover-el {
position: absolute;
width: 100%;
height: 100%;
color: var(--text-normal);
background-color: var(--background-secondary-alt);
display: flex;
align-items: center;
justify-content: center;
}
/* Custom rule modal. */
.iconize-custom-modal .modal-content {
display: flex;
align-items: center;
justify-content: center;
}
.iconize-custom-modal .modal-content input {
width: 100%;
margin-right: 0.5rem;
}
+21 -17
View File
@@ -4,21 +4,24 @@
"type": "split",
"children": [
{
"id": "c2be3e9a062c0b56",
"id": "6cfedf15c65a27df",
"type": "tabs",
"children": [
{
"id": "87c3a13251758aec",
"id": "060120ed5e7b1b64",
"type": "leaf",
"state": {
"type": "split-diff-view",
"type": "canvas",
"state": {
"aFile": ".gitignore",
"bFile": ".gitignore",
"aRef": ""
"file": "00 About/Overview diagram.canvas",
"viewState": {
"x": -630,
"y": -340,
"zoom": -0.5081109222000615
}
},
"icon": "diff",
"title": "Diff: .gitignore (Working Tree)"
"icon": "lucide-layout-dashboard",
"title": "Overview diagram"
}
}
]
@@ -182,34 +185,35 @@
}
],
"direction": "horizontal",
"width": 300
"width": 323.5
},
"left-ribbon": {
"hiddenItems": {
"switcher:Open quick switcher": false,
"graph:Open graph view": false,
"switcher:Open quick switcher": true,
"canvas:Create new canvas": false,
"daily-notes:Open today's daily note": false,
"daily-notes:Open today's daily note": true,
"templates:Insert template": false,
"command-palette:Open command palette": false,
"bases:Create new base": false,
"bases:Create new base": true,
"obsidian-git:Open Git source control": false,
"tasknotes:Open mini calendar view": true,
"tasknotes:Open calendar view": true,
"tasknotes:Open tasks view": true,
"tasknotes:Open tasks view": false,
"tasknotes:Open agenda view": true,
"tasknotes:Open Kanban board": false,
"graph:Open graph view": false,
"tasknotes:Open Pomodoro timer": true,
"tasknotes:Open Pomodoro statistics": true,
"tasknotes:Create new task": false
"tasknotes:Create new task": true
}
},
"active": "87c3a13251758aec",
"active": "060120ed5e7b1b64",
"lastOpenFiles": [
"00 About/Requirements.md",
"00 About/Overview diagram.canvas",
"TaskNotes/Start Here.md",
"Untitled Document",
"TaskNotes/Views/kanban-default.base",
"TaskNotes/Start Here.md",
"TaskNotes/Views/calendar-default.base"
]
}
+42 -42
View File
@@ -51,51 +51,10 @@
"styleAttributes":{"textAlign":"center"},
"x":-760,
"y":-440,
"width":180,
"width":220,
"height":270,
"color":"6"
},
{
"id":"0648cf705ffedc6f",
"type":"text",
"text":"Overview board:\n- pregled nad vsemi naročili\n",
"styleAttributes":{"textAlign":"center"},
"x":-440,
"y":-300,
"width":260,
"height":100
},
{
"id":"5e0af7712a4944dd",
"type":"text",
"text":"Admin",
"styleAttributes":{"textAlign":"center"},
"x":-220,
"y":-440,
"width":140,
"height":60
},
{
"id":"909284a7d8e1f746",
"type":"text",
"text":"QR code scan\n",
"styleAttributes":{"textAlign":"center"},
"x":-120,
"y":-300,
"width":260,
"height":100
},
{
"id":"9e2bb4fd16870db1",
"type":"text",
"text":"Prijava",
"styleAttributes":{"textAlign":"center"},
"x":-220,
"y":-600,
"width":140,
"height":60,
"color":"2"
},
{
"id":"02dbc1e4176bfdb2",
"type":"text",
@@ -126,6 +85,47 @@
"width":220,
"height":60,
"color":"2"
},
{
"id":"0648cf705ffedc6f",
"type":"text",
"text":"Overview board:\n- pregled nad vsemi naročili\n",
"styleAttributes":{"textAlign":"center"},
"x":-440,
"y":-300,
"width":260,
"height":160
},
{
"id":"5e0af7712a4944dd",
"type":"text",
"text":"Admin",
"styleAttributes":{"textAlign":"center"},
"x":-220,
"y":-440,
"width":140,
"height":60
},
{
"id":"909284a7d8e1f746",
"type":"text",
"text":"QR code scan\n",
"styleAttributes":{"textAlign":"center"},
"x":-120,
"y":-300,
"width":260,
"height":100
},
{
"id":"9e2bb4fd16870db1",
"type":"text",
"text":"Prijava",
"styleAttributes":{"textAlign":"center"},
"x":-220,
"y":-600,
"width":140,
"height":60,
"color":"2"
}
],
"edges":[
+72
View File
@@ -0,0 +1,72 @@
### 📱 PERSON A — Web App (Customer-Facing)
#### Functional Requirements
- **WEB-FR-1**: User registration (email + password) with email verification
- **WEB-FR-2**: Login / logout
- **WEB-FR-3**: Password reset flow
- **WEB-FR-4**: Display the event's card offering — name, description, price, expiration date
- **WEB-FR-5**: Checkout flow via Stripe (EUR)
- _Open item: single card per order, or multiple?_
- **WEB-FR-6**: On confirmed payment (via Stripe webhook), trigger card/QR generation (calls shared backend)
- **WEB-FR-7**: Send confirmation email with QR code attached/embedded (via transactional email service)
- **WEB-FR-8**: "My Cards" account page — shows purchase history, card status (unused/used/expired), QR re-download button
- **WEB-FR-9**: Basic account settings page (update email/password)
#### Non-Functional Requirements
- **WEB-NFR-1**: HTTPS only
- **WEB-NFR-2**: GDPR-compliant — privacy policy, consent checkbox at registration, data retention/erasure process
- **WEB-NFR-3**: No raw card/payment data stored — Stripe handles PCI compliance, you only store payment references
- **WEB-NFR-4**: Responsive design (mobile browser support, since users will pull up emails on phones too, though the QR itself is scanned by the admin's phone, not theirs)
#### Open Items for This Side
1. Multi-card orders — yes/no, and if yes, does each card need a separate recipient email field?
2. Refund/cancellation flow — in scope for v1 or not?
3. Any requirement to resend the QR email if a user loses it (vs. just downloading from account page)?
---
### 📷 PERSON B — Android Scanner App (Admin-Facing)
#### Functional Requirements
- **APP-FR-1**: Single admin login (one hardcoded/managed admin role — no multi-account complexity)
- **APP-FR-2**: Camera-based QR scanning (use a standard library — e.g., ML Kit or ZXing)
- **APP-FR-3**: On scan, send token to backend validation endpoint
- **APP-FR-4**: Display result clearly and immediately:
- ✅ **Valid** → show card owner name + card type, mark as used
- ❌ **Already Used** → show when it was originally used
- ❌ **Expired** → show expiration date
- ❌ **Not Found** → invalid/forged code
- **APP-FR-5**: Local on-device scan log (timestamp + result) for admin's own reference during the event
- **APP-FR-6**: Manual entry fallback (in case camera/QR fails to scan — type in code manually)
#### Non-Functional Requirements
- **APP-NFR-1**: Requires internet connectivity to validate (confirm: acceptable, or do you need offline fallback?)
- **APP-NFR-2**: Fast response time — scan-to-result should feel instant (sub-1s ideally) since there may be a line of people
- **APP-NFR-3**: Simple, high-contrast UI readable in bright outdoor daylight or dark event lighting
- **APP-NFR-4**: Since it's single-admin/single-device, no need to handle concurrent scan conflicts — backend validation is still atomic, but app itself doesn't need sync logic
#### Open Items for This Side
1. **Offline fallback** — if there's no signal at the venue, what happens? Worth deciding now since it affects architecture (native online-only call vs. local cache + sync).
2. Does the admin need to _undo_ an accidental valid scan (e.g., mis-scan, wrong card)?
3. Any requirement to see a running count (e.g., "142 checked in") during the event?
---
### 🔗 SHARED — Backend Contract (Both of you depend on this)
This is the glue between your two apps — worth agreeing on before you start building independently, since a mismatch here is the most likely source of integration pain later.
- **API-1**: `POST /api/orders` — creates order, integrates Stripe checkout
- **API-2**: Stripe webhook handler — on payment success, generates Card record + signed QR token, triggers email
- **API-3**: `POST /api/cards/validate` — takes scanned token, returns `{status: valid|used|expired|not_found, owner_name, card_type, used_at?}`, atomically updates status if valid
- **API-4**: `GET /api/users/me/cards` — for the web app's "My Cards" page
- **API-5**: Shared **Card** data model: `id, unique_token (signed), user_id, card_type, status, purchased_at, expires_at, used_at`
- **API-6**: Auth — separate token/role for admin scanner vs. customer web sessions
**Recommendation:** whoever owns the backend (or whoever's building it jointly) should nail down the exact JSON shape of `API-3`'s response first, since that's the contract Person B's UI directly renders.