Compare commits
11 Commits
rr/localap
...
main
Author | SHA1 | Date |
---|---|---|
Ryan Rix | cda4862f47 | |
Ryan Rix | 0586e6c70e | |
Ryan Rix | 8db6babc7d | |
Ryan Rix | 49f133935d | |
Ryan Rix | 63f32a328b | |
Ryan Rix | e1f02e2772 | |
Ryan Rix | 708480f4ed | |
Ryan Rix | 5c21254a3a | |
Ryan Rix | d8a4b13c11 | |
Ryan Rix | fee6eed87e | |
Ryan Rix | f2a75a8c7a |
28
arcology.org
28
arcology.org
|
@ -234,7 +234,7 @@ class Page(EMOM('page'), models.Model):
|
|||
)
|
||||
site = Site.from_route(route_key)
|
||||
root_heading = f.heading_set.filter(level=0)[0]
|
||||
title = root_heading.title
|
||||
title = root_heading.title or ""
|
||||
return cls.objects.get_or_create(
|
||||
file=f,
|
||||
route_key=route_key,
|
||||
|
@ -442,6 +442,7 @@ class FeedEntry(EMOM('feed_entry'), models.Model):
|
|||
v = pdqs.first().value
|
||||
pubdate = arrow.get(v, "YYYY-MM-DD ddd H:mm").format(arrow.FORMAT_RFC3339)
|
||||
title = heading.title
|
||||
|
||||
rets += [cls.objects.get_or_create(
|
||||
heading=heading,
|
||||
feed=feed,
|
||||
|
@ -528,6 +529,7 @@ These are the route [[https://docs.djangoproject.com/en/3.2/topics/http/urls/][u
|
|||
#+begin_src python :tangle arcology/urls.py
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path, include
|
||||
from django.conf import settings
|
||||
|
||||
from arcology import views
|
||||
|
||||
|
@ -540,11 +542,14 @@ urlpatterns = [
|
|||
path("feeds.json", views.feed_list, name="feed-list"),
|
||||
path("", include("django_prometheus.urls")),
|
||||
path("", include("sitemap.urls")),
|
||||
path("api/v1/", include("localapi.urls")),
|
||||
# ensure these ones are last because they're greedy!
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed, name="feed"),
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page, name="org-page"),
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-\.]+)", views.org_page, name="org-page"),
|
||||
]
|
||||
if settings.ARCOLOGY_ENVIRONMENT != "production":
|
||||
urlpatterns = [
|
||||
path("api/v1/", include("localapi.urls")),
|
||||
] + urlpatterns
|
||||
#+end_src
|
||||
|
||||
This is the topmatter for the views described below:
|
||||
|
@ -843,17 +848,11 @@ And some simple image wrangling:
|
|||
These rules annotate task headings by inserting an icon before them.
|
||||
|
||||
#+begin_src css :tangle arcology/static/arcology/css/app.css :mkdirp yes
|
||||
.task.task-DONE::before {
|
||||
content: '☑️ ';
|
||||
}
|
||||
|
||||
.task.task-NEXT::before {
|
||||
content: '🆕 ';
|
||||
}
|
||||
|
||||
.task.task-INPROGRESS::before {
|
||||
content: '⏳ ';
|
||||
}
|
||||
.task.task-DONE::before {content: '☑️ ';}
|
||||
.task.task-NEXT::before {content: '🆕 ';}
|
||||
.task.task-INPROGRESS::before {content: '🔜 ';}
|
||||
.task.task-WAITING::before {content: '⌚ ';}
|
||||
.task.task-CANCELLED::before {content: '☒ ';}
|
||||
#+end_src
|
||||
|
||||
This will display the header arguments to =org-babel= source blocks: You're staring right at one!
|
||||
|
@ -872,6 +871,7 @@ pre.src {
|
|||
font-style: normal;
|
||||
overflow: scroll;
|
||||
margin-top: 0;
|
||||
tab-size: 3ch;
|
||||
|
||||
padding-top: 1em;
|
||||
padding-left: 0.5em;
|
||||
|
|
|
@ -163,7 +163,7 @@ class Page(EMOM('page'), models.Model):
|
|||
)
|
||||
site = Site.from_route(route_key)
|
||||
root_heading = f.heading_set.filter(level=0)[0]
|
||||
title = root_heading.title
|
||||
title = root_heading.title or ""
|
||||
return cls.objects.get_or_create(
|
||||
file=f,
|
||||
route_key=route_key,
|
||||
|
@ -287,6 +287,7 @@ class FeedEntry(EMOM('feed_entry'), models.Model):
|
|||
v = pdqs.first().value
|
||||
pubdate = arrow.get(v, "YYYY-MM-DD ddd H:mm").format(arrow.FORMAT_RFC3339)
|
||||
title = heading.title
|
||||
|
||||
rets += [cls.objects.get_or_create(
|
||||
heading=heading,
|
||||
feed=feed,
|
||||
|
|
|
@ -48,7 +48,6 @@ INSTALLED_APPS = [
|
|||
"generators", # [[id:arroyo/django/generators][The Arroyo Generators]]
|
||||
"syncthonk", # [[id:20231218T183551.765340][Arcology watchsync Command]]
|
||||
"sitemap", # [[id:20240226T132507.817450][The Arcology's Site Maps and Discovery Mechanisms]]
|
||||
"localapi", # [[id:20240313T153901.656967][A Localhost API for the Arcology]]
|
||||
|
||||
"django_htmx",
|
||||
"django_prometheus",
|
||||
|
@ -59,6 +58,8 @@ INSTALLED_APPS = [
|
|||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
]
|
||||
if ARCOLOGY_ENVIRONMENT != "production":
|
||||
INSTALLED_APPS = ["localapi"] + INSTALLED_APPS
|
||||
# The Arcology is Modular:1 ends here
|
||||
|
||||
# [[file:../../configuration.org::*Internationalization][Internationalization:1]]
|
||||
|
|
|
@ -58,17 +58,11 @@ section.sidebar > div.backlinks {
|
|||
/* Org Page-specific CSS Stylings:4 ends here */
|
||||
|
||||
/* [[file:../../../../arcology.org::*Org Page-specific CSS Stylings][Org Page-specific CSS Stylings:5]] */
|
||||
.task.task-DONE::before {
|
||||
content: '☑️ ';
|
||||
}
|
||||
|
||||
.task.task-NEXT::before {
|
||||
content: '🆕 ';
|
||||
}
|
||||
|
||||
.task.task-INPROGRESS::before {
|
||||
content: '⏳ ';
|
||||
}
|
||||
.task.task-DONE::before {content: '☑️ ';}
|
||||
.task.task-NEXT::before {content: '🆕 ';}
|
||||
.task.task-INPROGRESS::before {content: '🔜 ';}
|
||||
.task.task-WAITING::before {content: '⌚ ';}
|
||||
.task.task-CANCELLED::before {content: '☒ ';}
|
||||
/* Org Page-specific CSS Stylings:5 ends here */
|
||||
|
||||
/* [[file:../../../../arcology.org::*Org Page-specific CSS Stylings][Org Page-specific CSS Stylings:6]] */
|
||||
|
@ -85,6 +79,7 @@ pre.src {
|
|||
font-style: normal;
|
||||
overflow: scroll;
|
||||
margin-top: 0;
|
||||
tab-size: 3ch;
|
||||
|
||||
padding-top: 1em;
|
||||
padding-left: 0.5em;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
# [[file:../arcology.org::*The Web Server][The Web Server:1]]
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path, include
|
||||
from django.conf import settings
|
||||
|
||||
from arcology import views
|
||||
|
||||
|
@ -13,9 +14,12 @@ urlpatterns = [
|
|||
path("feeds.json", views.feed_list, name="feed-list"),
|
||||
path("", include("django_prometheus.urls")),
|
||||
path("", include("sitemap.urls")),
|
||||
path("api/v1/", include("localapi.urls")),
|
||||
# ensure these ones are last because they're greedy!
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-]+\.xml)", views.feed, name="feed"),
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-]+)", views.org_page, name="org-page"),
|
||||
re_path("(?P<key>[0-9a-zA-Z/_\-\.]+)", views.org_page, name="org-page"),
|
||||
]
|
||||
if settings.ARCOLOGY_ENVIRONMENT != "production":
|
||||
urlpatterns = [
|
||||
path("api/v1/", include("localapi.urls")),
|
||||
] + urlpatterns
|
||||
# The Web Server:1 ends here
|
||||
|
|
|
@ -174,9 +174,9 @@ The NixOS module which defines =services.arcology-ng= is in [[id:20240213T124300
|
|||
|
||||
** NEXT the package import needs to be much better than this.
|
||||
|
||||
* Deploy manifests for [[id:cce/home-manager][a Dynamic Home Manager Configuration]]
|
||||
* Deploy manifests for [[id:cce/home-manager][a Dynamic Home Manager Configuration]] of [[id:20240320T123253.558216][LocalAPI Deployment]]
|
||||
|
||||
This deploys [[id:20240313T153901.656967][A Localhost API for the Arcology]] to any Home Manager user using =systemd= User Units:
|
||||
This deploys [[id:20240313T153901.656967][A Localhost API for the Arcology]] to any Home Manager user using =systemd= User Units. See [[id:20240320T123253.558216][LocalAPI Deployment Options]] for the list of options that can be changed and how to use it:
|
||||
|
||||
#+begin_src nix :tangle ~/arroyo-nix/hm/arcology-localapi.nix
|
||||
{ pkgs, ... }:
|
||||
|
@ -186,17 +186,18 @@ let
|
|||
arcology = pkgs.callPackage /home/rrix/org/arcology-django/default.nix { inherit arroyo_rs; };
|
||||
in {
|
||||
imports = [ ./arcology-localapi-mod.nix ];
|
||||
home.packages = [ arcology ];
|
||||
|
||||
services.arcology2 = {
|
||||
enable = true;
|
||||
packages.arcology = arcology;
|
||||
folderId = "p1kld-oxnwd";
|
||||
logLevel = "DEBUG";
|
||||
environmentFile = "/home/rrix/sync/private-files/.arcology-env";
|
||||
};
|
||||
}
|
||||
#+end_src
|
||||
|
||||
** NEXT address the =callPackages= here...
|
||||
|
||||
* Service Configuration
|
||||
:PROPERTIES:
|
||||
|
@ -286,7 +287,6 @@ basically, each org file in this repository, and maybe one or two of your own, a
|
|||
| "generators" | [[id:arroyo/django/generators][The Arroyo Generators]] |
|
||||
| "syncthonk" | [[id:20231218T183551.765340][Arcology watchsync Command]] |
|
||||
| "sitemap" | [[id:20240226T132507.817450][The Arcology's Site Maps and Discovery Mechanisms]] |
|
||||
| "localapi" | [[id:20240313T153901.656967][A Localhost API for the Arcology]] |
|
||||
|
||||
#+BEGIN_SRC python :tangle arcology/settings/__init__.py :noweb yes
|
||||
# Application definition
|
||||
|
@ -301,6 +301,8 @@ INSTALLED_APPS = [
|
|||
"django.contrib.messages",
|
||||
"django.contrib.staticfiles",
|
||||
]
|
||||
if ARCOLOGY_ENVIRONMENT != "production":
|
||||
INSTALLED_APPS = ["localapi"] + INSTALLED_APPS
|
||||
#+END_SRC
|
||||
|
||||
** Internationalization
|
||||
|
|
|
@ -50,7 +50,21 @@ let
|
|||
|
||||
pyenv = pkgs.python3.withPackages(pp: [cfg.packages.arcology]);
|
||||
|
||||
wrapperScript = pkgs.writeScriptBin "arcology" ''
|
||||
set -eEuo pipefail
|
||||
|
||||
${lib.concatStrings (lib.mapAttrsToList (name: value: "export ${name}=\${${name}:-${value}}\n") env)}
|
||||
|
||||
source ${cfg.environmentFile}
|
||||
export ARCOLOGY_SYNCTHING_KEY
|
||||
export ARCOLOGY_LOCALAPI_BEARER_TOKEN
|
||||
|
||||
exec ${cfg.packages.arcology}/bin/arcology "$@"
|
||||
'';
|
||||
|
||||
svcConfig = {
|
||||
environment.systemPackages = [ wrapperScript ];
|
||||
|
||||
system.activationScripts.arcology-collectfiles.text = ''
|
||||
echo "Setting up Arcology static files"
|
||||
ARCOLOGY_STATIC_ROOT=${cfg.staticRoot} ${cfg.packages.arcology}/bin/arcology collectstatic --no-input -c -v0
|
||||
|
@ -116,6 +130,7 @@ let
|
|||
serverAliases = tail cfg.domains;
|
||||
locations."/".proxyPass = "http://${cfg.address}:${toString cfg.port}";
|
||||
locations."/".extraConfig = ''
|
||||
limit_req zone=ip_based burst=10;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
|
@ -124,6 +139,10 @@ let
|
|||
'';
|
||||
locations."/static/".alias = cfg.staticRoot;
|
||||
};
|
||||
services.prometheus.scrapeConfigs = [{
|
||||
job_name = "arcology";
|
||||
static_configs = [{ targets = ["localhost:${toString config.services.arcology-ng.port}"]; }];
|
||||
}];
|
||||
};
|
||||
|
||||
userConfig = {
|
||||
|
|
|
@ -6,11 +6,11 @@
|
|||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1710377466,
|
||||
"narHash": "sha256-7p5/TWrVgK5/DeQRKT7TgbASAzTT4ctxyvK4rkIvrf8=",
|
||||
"lastModified": 1713548093,
|
||||
"narHash": "sha256-CCHmAlANZzEWD/5fJyDkvVH2H54wln54qmLUn0PgiVQ=",
|
||||
"ref": "refs/heads/main",
|
||||
"rev": "0571f3369c7ee5b36ea3acd2eba1d8dd6de6115f",
|
||||
"revCount": 162,
|
||||
"rev": "320c4d7f4ebb58162980d5384d5dadcd8d3ba02e",
|
||||
"revCount": 168,
|
||||
"type": "git",
|
||||
"url": "https://code.rix.si/rrix/arroyo"
|
||||
},
|
||||
|
|
300
localapi.org
300
localapi.org
|
@ -6,7 +6,6 @@
|
|||
|
||||
#+ARCOLOGY_KEY: arcology/localapi
|
||||
#+ARROYO_EMACS_MODULE: arcology-localapi-commands
|
||||
#+AUTO_TANGLE: t
|
||||
|
||||
By moving the database out of EmacSQL and in to Django, I have hobbled some of the Arcology's [[id:knowledge_base][Knowledge Management]] [[id:e79d4cdc-082f-4b1d-ae08-d979802f09ee][Living Systems]] and meta-cognitive skills. =arroyo-db-query= no longer existing means that things like the [[id:20230526T105534.711282][Direnv arroyo-db integration]] stopped working, and the helpers in [[id:cce/my_nixos_configuration][My NixOS configuration]] and elsewhere rely on the stringly-typed [[id:20231217T154938.132553][Arcology generate Command]] and a =nix run= invocation to function.
|
||||
|
||||
|
@ -14,12 +13,14 @@ I think it's worth building a small HTTP JSON API which can serve some of the co
|
|||
|
||||
Here's how to use this from Emacs Lisp:
|
||||
|
||||
- =(arcology-localapi-call method path)= is an API helper which given an API path will fetch the data and return a deserialized JSON structure
|
||||
- =(arcology-fetch-localapi-bearer-token)= fetches the bearer token file shared with the =localapi= deployment.
|
||||
- =(arcology-localapi-call method path)= is an API helper which given an API path will fetch the data with authorization and return a deserialized JSON structure
|
||||
- these interactive commands will fetch a URL, putting it on your kill ring or clipboard if you call it with =M-x= or equivalent:
|
||||
- =(arcology-key-to-url page-key &optional heading-id)= will take an =ARCOLOGY_KEY= and return a URL
|
||||
- =(arcology-file-to-url file-path &optional heading-id)= will do the same with a file path from =(buffer-file-name)= or so.
|
||||
- =(arcology-url-at-point)= gets the org-id of the heading your cursor is in if it has one, and makes a URL that links directly to that.
|
||||
- =(arcology-read-url)= pops up a list of all the org-roam headings, and returns a URL to it.
|
||||
- =(arcology-key-to-url page-key &optional heading-id)= will take an =ARCOLOGY_KEY= and return a URL
|
||||
- =(arcology-file-to-url file-path &optional heading-id)= will do the same with a file path from =(buffer-file-name)= or so.
|
||||
- =(arcology-url-at-point)= gets the org-id of the heading your cursor is in if it has one, and makes a URL that links directly to that.
|
||||
- =(arcology-read-url)= pops up a list of all the org-roam headings, and returns a URL to it.
|
||||
- =(arcology-api-generator)= calls in to [[id:arroyo/django/generators][The Arroyo Generators]] and returns the string of files they generate
|
||||
|
||||
* Server view and Arroyo Emacs lisp scaffolding
|
||||
|
||||
|
@ -30,15 +31,20 @@ The API will be simple, with a bearer token provided by a local state file:
|
|||
#+begin_src emacs-lisp :tangle ~/org/cce/arcology-localapi-commands.el
|
||||
(use-package plz)
|
||||
|
||||
(defvar arcology-localapi-bearer-token nil)
|
||||
(defun arcology-fetch-localapi-bearer-token ()
|
||||
(interactive)
|
||||
(customize-save-variable
|
||||
'arcology-localapi-bearer-token
|
||||
(or (getenv "ARCOLOGY_LOCALAPI_BEARER_TOKEN")
|
||||
(thread-last
|
||||
". ~/sync/private-files/.arcology-env && echo $ARCOLOGY_LOCALAPI_BEARER_TOKEN"
|
||||
(shell-command-to-string)
|
||||
(s-chop-suffix "\n" )))))
|
||||
(let ((tok (or arcology-localapi-bearer-token
|
||||
(getenv "ARCOLOGY_LOCALAPI_BEARER_TOKEN")
|
||||
(thread-last
|
||||
"~/sync/private-files/.arcology-env"
|
||||
(format ". %s && echo $ARCOLOGY_LOCALAPI_BEARER_TOKEN")
|
||||
(shell-command-to-string)
|
||||
(s-chop-suffix "\n" )))))
|
||||
(when (or (called-interactively-p)
|
||||
(not arcology-localapi-bearer-token))
|
||||
(customize-save-variable 'arcology-localapi-bearer-token tok))
|
||||
tok))
|
||||
|
||||
(defcustom arcology-api-base "http://127.0.0.1:29543/api/v1"
|
||||
"localapi base url")
|
||||
|
@ -46,7 +52,7 @@ The API will be simple, with a bearer token provided by a local state file:
|
|||
(defun arcology-localapi-call (method path &rest plz-args)
|
||||
(let ((plz-args (or plz-args '(:as json-read))))
|
||||
(apply 'plz method (format "%s%s" arcology-api-base path)
|
||||
:headers `(("Authorization" . ,(format "Bearer %s" arcology-localapi-bearer-token)))
|
||||
:headers `(("Authorization" . ,(format "Bearer %s" (arcology-fetch-localapi-bearer-token))))
|
||||
plz-args)))
|
||||
#+end_src
|
||||
|
||||
|
@ -62,6 +68,7 @@ urlpatterns = [
|
|||
path("", views.index),
|
||||
path("generate/<slug:module>/<slug:role>", views.generate, name="keyword_by_key"),
|
||||
path("generate/<slug:module>", views.generate, name="keyword_by_key"),
|
||||
path("file/by-tag/<slug:key>", views.files_by_tag, name="files_by_tag"),
|
||||
path("keywords/<slug:key>", views.keyword_by_key, name="keyword_by_key"),
|
||||
re_path("keywords/(?P<key>[0-9a-zA-Z_-]+)/(?P<value>[0-9a-zA-Z/_\-]+)", views.keyword_by_key_value, name="keyword_by_key"),
|
||||
re_path("page/(?P<route_key>[0-9a-zA-Z/_\-]+)", views.page_metadata, name="page_metadata"),
|
||||
|
@ -182,11 +189,19 @@ And some really simple APIs to get information about =Page= and =File= objects o
|
|||
import arcology.models
|
||||
|
||||
def _json_page(page):
|
||||
if page is None:
|
||||
return page
|
||||
return dict(
|
||||
title=page.title,
|
||||
url=page.to_url(),
|
||||
site=page.site.title,
|
||||
)
|
||||
title=page.title,
|
||||
url=page.to_url(),
|
||||
site=page.site.title,
|
||||
keywords={
|
||||
kw.keyword: kw.value
|
||||
for kw in page.collect_keywords().all()
|
||||
},
|
||||
tags=list(set([tag.tag for tag in page.collect_tags()])),
|
||||
references=[reference.ref for reference in page.collect_references()],
|
||||
)
|
||||
#+end_src
|
||||
|
||||
=GET http://127.0.0.1:8000/api/v1/page/arcology/localapi= or this Emacs Lisp command:
|
||||
|
@ -224,14 +239,19 @@ def page_metadata(request, route_key):
|
|||
=GET http://127.0.0.1:8000/api/v1/file/arcology-django/localapi.org= and the Emacs Lisp command which will do the same:
|
||||
|
||||
#+begin_src emacs-lisp :tangle ~/org/cce/arcology-localapi-commands.el
|
||||
(defun arcology-file-metadata (file-path)
|
||||
(thread-last
|
||||
(file-relative-name file-path org-roam-directory)
|
||||
(format "/file/%s")
|
||||
(arcology-localapi-call 'get)
|
||||
(alist-get 'file)))
|
||||
|
||||
(defun arcology-file-to-url (file-path &optional heading-id)
|
||||
(interactive "fKey: \nsHeading ID: ")
|
||||
(let ((url (concat
|
||||
(thread-last
|
||||
(file-relative-name file-path org-roam-directory)
|
||||
(format "http://127.0.0.1:8000/api/v1/file/%s")
|
||||
(arcology-localapi-call 'get)
|
||||
(alist-get 'file)
|
||||
file-path
|
||||
(arcology-file-metadata)
|
||||
(alist-get 'page)
|
||||
(alist-get 'url))
|
||||
(when heading-id (format "#%s" heading-id)))))
|
||||
|
@ -247,18 +267,56 @@ from django.conf import settings
|
|||
@authenticated
|
||||
def file_metadata(request, file_path):
|
||||
final_path = settings.ARCOLOGY_BASE_DIR.joinpath(file_path)
|
||||
print(f"1 {final_path}")
|
||||
the_file = roam.models.File.objects.get(path=final_path)
|
||||
print(f"2 {the_file}")
|
||||
page = the_file.page_set.first()
|
||||
print(f"3 {page}")
|
||||
root_heading = the_file.heading_set.filter(level=0).first()
|
||||
print(f"4 {root_heading}")
|
||||
return JsonResponse(dict(
|
||||
state="ok :)",
|
||||
file_path=file_path,
|
||||
file=dict(
|
||||
path=the_file.path,
|
||||
root_heading=dict(
|
||||
title=root_heading.title,
|
||||
id=root_heading.node_id,
|
||||
),
|
||||
page=_json_page(page),
|
||||
),
|
||||
))
|
||||
#+end_src
|
||||
|
||||
* Tag search
|
||||
|
||||
** Get all files by tag
|
||||
|
||||
#+begin_src python :tangle localapi/views.py
|
||||
@authenticated
|
||||
def files_by_tag(request, key):
|
||||
# final_path = settings.ARCOLOGY_BASE_DIR.joinpath(file_path)
|
||||
the_tags = roam.models.Tag.objects.filter(tag=key).all()
|
||||
return JsonResponse(dict(
|
||||
state="ok :)",
|
||||
tag=key,
|
||||
files=list(set([
|
||||
tag.heading.path.path
|
||||
for tag in the_tags
|
||||
]))
|
||||
))
|
||||
#+end_src
|
||||
|
||||
#+begin_src emacs-lisp :tangle ~/org/cce/arcology-localapi-commands.el
|
||||
(defun arcology-files-by-tag (tag)
|
||||
(thread-last
|
||||
tag
|
||||
(format "/file/by-tag/%s")
|
||||
(arcology-localapi-call 'get)
|
||||
(alist-get 'files)
|
||||
(mapcar #'identity)))
|
||||
#+end_src
|
||||
|
||||
* Some more ELisp helpers
|
||||
:PROPERTIES:
|
||||
:ID: 20240313T212950.461285
|
||||
|
@ -286,7 +344,9 @@ A command which will get the URL for a heading you can select using your =comple
|
|||
(defun arcology-read-url ()
|
||||
(interactive)
|
||||
(let* ((node (org-roam-node-read))
|
||||
(url (arcology-file-to-url (org-roam-node-file node)
|
||||
(node-path (file-relative-name (org-roam-node-file node)
|
||||
org-roam-directory))
|
||||
(url (arcology-file-to-url node-path
|
||||
(and (> (org-outline-level) 0)
|
||||
(org-id-get)))))
|
||||
(when (called-interactively-p)
|
||||
|
@ -348,35 +408,84 @@ def generate(request, module, role=None):
|
|||
|
||||
this is all full of jank. having to pass to a temporary file because the generate commands are locked away in a django management command is silly, i should refactor all of this. the branch is silly, too. But it's very easy now to get my literate programming helpers to quickly generate the code imports and whatnot as soon as the local server is running.
|
||||
|
||||
* INPROGRESS [[id:cce/home-manager][home-manager]] deployment
|
||||
* DONE Deploying the Arcology as a Local API via [[id:cce/home-manager][home-manager]]
|
||||
:LOGBOOK:
|
||||
- State "DONE" from "INPROGRESS" [2024-03-20 Wed 12:37]
|
||||
- State "INPROGRESS" from "NEXT" [2024-03-18 Mon 16:08]
|
||||
:END:
|
||||
|
||||
This thing needs to run as a =systemd= User Unit for the local tangles and whatnot to work. Bootstrapping this will be Fun, we'll just end up needing a bootstrap script to put the =systemd= user units in place for future deployments...
|
||||
|
||||
This is just the configurable module; the configuration itself is in the [[id:arcology/django/config][Arcology Project Configuration]] page like the Wobserver module.
|
||||
|
||||
#+ARROYO_HOME_MODULE: hm/arcology-localapi.nix
|
||||
#+ARROYO_SYSTEM_ROLE: endpoint
|
||||
: #+ARROYO_SYSTEM_ROLE: droid # need to make sure there is a wrapper script to launch this
|
||||
#+ARROYO_SYSTEM_ROLE: droid
|
||||
|
||||
#+begin_src nix :tangle ~/arroyo-nix/hm/arcology-localapi-mod.nix
|
||||
This thing needs to run as a =systemd= User Unit for the local tangles and whatnot to work. Bootstrapping this will be Fun, we'll just end up needing a bootstrap script to put the =systemd= user units in place for future deployments...
|
||||
|
||||
This is just the configurable module; the configuration itself is in the [[id:arcology/django/config][Arcology Project Configuration]] page like the Wobserver module. Here are the options:
|
||||
|
||||
** LocalAPI Deployment Options
|
||||
:PROPERTIES:
|
||||
:ID: 20240320T123253.558216
|
||||
:END:
|
||||
|
||||
#+NAME: hm-options
|
||||
| name | type | default | mdDoc |
|
||||
|-------------------+--------------------------------------+-----------------------+----------------------------------------------------------------------------------|
|
||||
| packages.arcology | package | | Arcology package to use |
|
||||
| address | str | "localhost" | Web interface listening address |
|
||||
| port | port | 29543 | Web interface listening port |
|
||||
| orgDir | str | "~/org" | Directory containing the org-mode documents. Provide r/o access. |
|
||||
| folderId | str | | Syncthing folder ID containing the org files |
|
||||
| cacheDir | str | "~/.cache/arcology2" | Location to cache HTML files and the like. |
|
||||
| dataDir | str | "~/.config/arcology2" | Location to store the arcology metadata store. |
|
||||
| workerCount | number | 4 | gunicorn worker count; they recommend 2-4 workers per core. |
|
||||
| environmentFile | str | "${cfg.dataDir}/env" | A file containing =ARCOLOGY_SYNCTHING_KEY= and =ARCOLOGY_LOCALAPI_BEARER_TOKEN=. |
|
||||
| logLevel | enum ["ERROR" "WARN" "INFO" "DEBUG"] | "INFO" | Django root logger level |
|
||||
|
||||
That table does some really gross code generation:
|
||||
|
||||
#+NAME: generate-hm-options
|
||||
#+begin_src emacs-lisp :var tbl=hm-options :results drawer
|
||||
(thread-last
|
||||
tbl
|
||||
(-map (pcase-lambda (`(,name ,type ,default ,doc))
|
||||
(let ((default-str
|
||||
(cond ((numberp default)
|
||||
(prin1-to-string default))
|
||||
((and (stringp default)
|
||||
(string-empty-p default))
|
||||
nil)
|
||||
(t (prin1-to-string default)))))
|
||||
(concat
|
||||
name " = mkOption {\n"
|
||||
" type = with types; " type ";\n"
|
||||
(if default-str
|
||||
(concat " default = " default-str ";\n")
|
||||
"")
|
||||
(if doc
|
||||
(concat " description = mdDoc ''" doc "'';\n")
|
||||
"\n")
|
||||
"};\n"))))
|
||||
(s-join "\n"))
|
||||
#+end_src
|
||||
|
||||
** Home Manager Service Definition
|
||||
|
||||
This does some code injection using the table above. It's configured from [[id:arcology/django/config][the Project Configuration]], but basically it sets up a few wrapper scripts that configure the process environment and put the webserver and the =watchsync= commands as SystemD User units as well as providing an =arcologyctl= command in =PATH=. Eventually it would be nice to pull the Syncthing key from the local =config.xml= but maybe that's another management command...
|
||||
|
||||
#+begin_src nix :tangle ~/arroyo-nix/hm/arcology-localapi-mod.nix :noweb yes
|
||||
{ pkgs, config, lib, ... }:
|
||||
|
||||
let
|
||||
cfg = config.services.arcology2;
|
||||
arcology = cfg.packages.arcology;
|
||||
|
||||
env = [
|
||||
"ARCOLOGY_BASE_DIR=${cfg.orgDir}"
|
||||
"ARCOLOGY_DB_PATH=${cfg.dataDir}/db.sqlite3"
|
||||
"ARCOLOGY_LOG_LEVEL=${cfg.logLevel}"
|
||||
"ARCOLOGY_CACHE_PATH=${cfg.cacheDir}"
|
||||
"GUNICORN_CMD_ARGS='--bind=${cfg.address}:${toString cfg.port} -w ${toString cfg.workerCount}'"
|
||||
];
|
||||
|
||||
pyenv = pkgs.python3.withPackages(pp: [arcology]);
|
||||
env = {
|
||||
ARCOLOGY_ENVIRONMENT="development";
|
||||
ARCOLOGY_BASE_DIR="${cfg.orgDir}";
|
||||
ARCOLOGY_DB_PATH="${cfg.dataDir}/db.sqlite3";
|
||||
ARCOLOGY_LOG_LEVEL="${cfg.logLevel}";
|
||||
ARCOLOGY_CACHE_PATH="${cfg.cacheDir}";
|
||||
};
|
||||
|
||||
preStartScript = pkgs.writeScriptBin "arcology-watchsync-prestart" ''
|
||||
mkdir -p ${cfg.dataDir}
|
||||
|
@ -384,95 +493,34 @@ let
|
|||
${arcology}/bin/arcology seed || true
|
||||
'';
|
||||
watchsyncScript = pkgs.writeScriptBin "arcology-watchsync" ''
|
||||
${arcology}/bin/arcology watchsync -f ${cfg.folderId}
|
||||
exec ${arcology}/bin/arcology watchsync -f ${cfg.folderId}
|
||||
'';
|
||||
localapiScript = pkgs.writeScriptBin "arcology-localapi" ''
|
||||
${pyenv}/bin/python -m gunicorn arcology.wsgi
|
||||
exec ${arcology}/bin/arcology runserver --noreload ${toString cfg.address}:${toString cfg.port}
|
||||
'';
|
||||
wrapperScript = pkgs.writeScriptBin "arcology" ''
|
||||
set -eEuo pipefail
|
||||
|
||||
${lib.concatStrings (lib.mapAttrsToList (name: value: "export ${name}=\${${name}:-${value}}\n") env)}
|
||||
|
||||
source ${cfg.environmentFile}
|
||||
export ARCOLOGY_SYNCTHING_KEY
|
||||
export ARCOLOGY_LOCALAPI_BEARER_TOKEN
|
||||
|
||||
exec ${arcology}/bin/arcology "$@"
|
||||
'';
|
||||
in {
|
||||
options.services.arcology2 = with lib; {
|
||||
enable = mkEnableOption (mdDoc "Arcology Local API");
|
||||
|
||||
packages.arcology = mkOption {
|
||||
type = types.package;
|
||||
description = mdDoc ''
|
||||
'';
|
||||
};
|
||||
|
||||
address = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
description = lib.mdDoc "Web interface address.";
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 29543;
|
||||
description = lib.mdDoc "Web interface port.";
|
||||
};
|
||||
|
||||
workerCount = mkOption {
|
||||
type = types.number;
|
||||
default = 4;
|
||||
description = lib.mdDoc "gunicorn worker count; they recommend 2-4 workers per core.";
|
||||
};
|
||||
|
||||
logLevel = mkOption {
|
||||
type = types.enum ["ERROR" "WARN" "INFO" "DEBUG"];
|
||||
default = "INFO";
|
||||
description = mdDoc ''
|
||||
Set the Django root logging level
|
||||
'';
|
||||
};
|
||||
|
||||
orgDir = mkOption {
|
||||
type = types.str;
|
||||
default = "~/org";
|
||||
description = mdDoc ''
|
||||
Directory containing the org-mode documents.
|
||||
Arcology needs read-only access to this directory.
|
||||
'';
|
||||
};
|
||||
|
||||
environmentFile = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.dataDir}/env";
|
||||
description = mdDoc ''
|
||||
A file containing environment variables you may not want to put in the nix store.
|
||||
|
||||
For example, you could put a syncthing key
|
||||
and a bearer token for the Local API in there:
|
||||
|
||||
ARCOLOGY_SYNCTHING_KEY=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;
|
||||
ARCOLOGY_LOCALAPI_BEARER_TOKEN=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA;
|
||||
'';
|
||||
};
|
||||
|
||||
folderId = mkOption {
|
||||
type = types.str;
|
||||
description = mdDoc ''
|
||||
Syncthing folder ID containing the org files.
|
||||
'';
|
||||
};
|
||||
|
||||
cacheDir = mkOption {
|
||||
type = types.str;
|
||||
default = "~/.cache/arcology2/";
|
||||
description = mdDoc ''
|
||||
Location to cache HTML files and the like.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "~/.config/arcology2/";
|
||||
description = mdDoc ''
|
||||
Location to store the arcology metadata store.
|
||||
'';
|
||||
};
|
||||
<<generate-hm-options()>>
|
||||
};
|
||||
|
||||
config = {
|
||||
home.packages = [
|
||||
wrapperScript
|
||||
];
|
||||
|
||||
systemd.user.services = {
|
||||
arcology-watchsync = {
|
||||
Unit.Description = "Dynamically update the Arcology database.";
|
||||
|
@ -481,7 +529,10 @@ in {
|
|||
ExecStart = "${pkgs.stdenv.shell} ${watchsyncScript}/bin/arcology-watchsync";
|
||||
|
||||
Restart = "on-failure";
|
||||
Environment = env;
|
||||
RestartSec=5;
|
||||
RestartSteps=10;
|
||||
RestartMaxDelaySec="1min";
|
||||
Environment = (lib.mapAttrsToList (name: value: "${name}=${value}") env);
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
};
|
||||
Install.WantedBy = ["default.target"];
|
||||
|
@ -493,7 +544,10 @@ in {
|
|||
ExecStart = "${pkgs.stdenv.shell} ${localapiScript}/bin/arcology-localapi";
|
||||
|
||||
Restart = "on-failure";
|
||||
Environment = env;
|
||||
RestartSec=5;
|
||||
RestartSteps=10;
|
||||
RestartMaxDelaySec="1min";
|
||||
Environment = (lib.mapAttrsToList (name: value: "${name}=${value}") env);
|
||||
EnvironmentFile = cfg.environmentFile;
|
||||
};
|
||||
Install.WantedBy = ["default.target"];
|
||||
|
@ -507,3 +561,9 @@ in {
|
|||
** NEXT is the arcology wobserver deployment better as a systemd user unit?
|
||||
|
||||
still need nixos module for the vhosts etc, but... hm.
|
||||
|
||||
running as my main user *would* make a lot of the environment stuff easier to deal with, i guess
|
||||
|
||||
how does a multi-user wobserver setup work?
|
||||
|
||||
** NEXT i think the RestartMaxDelay and whatnot are incorrectly specified...
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# [[file:../localapi.org::*Local API Auth Middleware][Local API Auth Middleware:1]]
|
||||
import re
|
||||
from django.conf import settings
|
||||
from django.http import HttpRequest, JsonResponse
|
||||
|
@ -22,3 +23,4 @@ def authenticated(func):
|
|||
return JsonResponse(dict(state="no :("), status=401)
|
||||
return func(*args, **kwargs)
|
||||
return wrapper
|
||||
# Local API Auth Middleware:1 ends here
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# [[file:../localapi.org::*Server view and Arroyo Emacs lisp scaffolding][Server view and Arroyo Emacs lisp scaffolding:2]]
|
||||
from django.contrib import admin
|
||||
from django.urls import path, re_path, include
|
||||
|
||||
|
@ -7,8 +8,10 @@ urlpatterns = [
|
|||
path("", views.index),
|
||||
path("generate/<slug:module>/<slug:role>", views.generate, name="keyword_by_key"),
|
||||
path("generate/<slug:module>", views.generate, name="keyword_by_key"),
|
||||
path("file/by-tag/<slug:key>", views.files_by_tag, name="files_by_tag"),
|
||||
path("keywords/<slug:key>", views.keyword_by_key, name="keyword_by_key"),
|
||||
re_path("keywords/(?P<key>[0-9a-zA-Z_-]+)/(?P<value>[0-9a-zA-Z/_\-]+)", views.keyword_by_key_value, name="keyword_by_key"),
|
||||
re_path("page/(?P<route_key>[0-9a-zA-Z/_\-]+)", views.page_metadata, name="page_metadata"),
|
||||
re_path("file/(?P<file_path>[0-9a-zA-Z/_\-\.]+)", views.file_metadata, name="file_metadata"),
|
||||
]
|
||||
# Server view and Arroyo Emacs lisp scaffolding:2 ends here
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# [[file:../localapi.org::*Server view and Arroyo Emacs lisp scaffolding][Server view and Arroyo Emacs lisp scaffolding:3]]
|
||||
from django.http import HttpRequest, HttpResponse, HttpResponseNotFound, Http404, JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
|
||||
|
@ -9,11 +10,15 @@ from prometheus_client import Counter, Histogram
|
|||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
# Server view and Arroyo Emacs lisp scaffolding:3 ends here
|
||||
|
||||
# [[file:../localapi.org::*Server view and Arroyo Emacs lisp scaffolding][Server view and Arroyo Emacs lisp scaffolding:4]]
|
||||
@authenticated
|
||||
def index(request):
|
||||
return JsonResponse(dict(state="ok :)"))
|
||||
# Server view and Arroyo Emacs lisp scaffolding:4 ends here
|
||||
|
||||
# [[file:../localapi.org::*Keyword metadata][Keyword metadata:1]]
|
||||
import roam.models
|
||||
|
||||
def _json_keywords(keywords):
|
||||
|
@ -21,7 +26,9 @@ def _json_keywords(keywords):
|
|||
dict(path=kw.path.path, keyword=kw.keyword, value=kw.value)
|
||||
for kw in keywords
|
||||
]
|
||||
# Keyword metadata:1 ends here
|
||||
|
||||
# [[file:../localapi.org::*/keywords/{key}][/keywords/{key}:1]]
|
||||
@authenticated
|
||||
def keyword_by_key(request, key):
|
||||
keywords = roam.models.Keyword.objects.filter(keyword=key).all()
|
||||
|
@ -30,7 +37,9 @@ def keyword_by_key(request, key):
|
|||
key=key,
|
||||
keywords=_json_keywords(keywords),
|
||||
))
|
||||
# /keywords/{key}:1 ends here
|
||||
|
||||
# [[file:../localapi.org::*/keywords/{key}/{value}][/keywords/{key}/{value}:1]]
|
||||
@authenticated
|
||||
def keyword_by_key_value(request, key, value):
|
||||
keywords = roam.models.Keyword.objects.filter(keyword=key, value=value).all()
|
||||
|
@ -40,16 +49,28 @@ def keyword_by_key_value(request, key, value):
|
|||
value=value,
|
||||
keywords=_json_keywords(keywords),
|
||||
))
|
||||
# /keywords/{key}/{value}:1 ends here
|
||||
|
||||
# [[file:../localapi.org::*Page and File metadata][Page and File metadata:1]]
|
||||
import arcology.models
|
||||
|
||||
def _json_page(page):
|
||||
if page is None:
|
||||
return page
|
||||
return dict(
|
||||
title=page.title,
|
||||
url=page.to_url(),
|
||||
site=page.site.title,
|
||||
)
|
||||
title=page.title,
|
||||
url=page.to_url(),
|
||||
site=page.site.title,
|
||||
keywords={
|
||||
kw.keyword: kw.value
|
||||
for kw in page.collect_keywords().all()
|
||||
},
|
||||
tags=list(set([tag.tag for tag in page.collect_tags()])),
|
||||
references=[reference.ref for reference in page.collect_references()],
|
||||
)
|
||||
# Page and File metadata:1 ends here
|
||||
|
||||
# [[file:../localapi.org::*Page and File metadata][Page and File metadata:3]]
|
||||
@authenticated
|
||||
def page_metadata(request, route_key):
|
||||
page = arcology.models.Page.objects.get(route_key=route_key)
|
||||
|
@ -61,23 +82,51 @@ def page_metadata(request, route_key):
|
|||
page=_json_page(page),
|
||||
keywords=_json_keywords(keywords)
|
||||
))
|
||||
# Page and File metadata:3 ends here
|
||||
|
||||
# [[file:../localapi.org::*Page and File metadata][Page and File metadata:5]]
|
||||
from django.conf import settings
|
||||
|
||||
@authenticated
|
||||
def file_metadata(request, file_path):
|
||||
final_path = settings.ARCOLOGY_BASE_DIR.joinpath(file_path)
|
||||
print(f"1 {final_path}")
|
||||
the_file = roam.models.File.objects.get(path=final_path)
|
||||
print(f"2 {the_file}")
|
||||
page = the_file.page_set.first()
|
||||
print(f"3 {page}")
|
||||
root_heading = the_file.heading_set.filter(level=0).first()
|
||||
print(f"4 {root_heading}")
|
||||
return JsonResponse(dict(
|
||||
state="ok :)",
|
||||
file_path=file_path,
|
||||
file=dict(
|
||||
path=the_file.path,
|
||||
root_heading=dict(
|
||||
title=root_heading.title,
|
||||
id=root_heading.node_id,
|
||||
),
|
||||
page=_json_page(page),
|
||||
),
|
||||
))
|
||||
# Page and File metadata:5 ends here
|
||||
|
||||
# [[file:../localapi.org::*Get all files by tag][Get all files by tag:1]]
|
||||
@authenticated
|
||||
def files_by_tag(request, key):
|
||||
# final_path = settings.ARCOLOGY_BASE_DIR.joinpath(file_path)
|
||||
the_tags = roam.models.Tag.objects.filter(tag=key).all()
|
||||
return JsonResponse(dict(
|
||||
state="ok :)",
|
||||
tag=key,
|
||||
files=list(set([
|
||||
tag.heading.path.path
|
||||
for tag in the_tags
|
||||
]))
|
||||
))
|
||||
# Get all files by tag:1 ends here
|
||||
|
||||
# [[file:../localapi.org::*\[\[id:20231217T154938.132553\]\[Arcology generate Command\]\]][[[id:20231217T154938.132553][Arcology generate Command]]:3]]
|
||||
import tempfile
|
||||
from django.core.management import call_command
|
||||
|
||||
|
@ -93,3 +142,4 @@ def generate(request, module, role=None):
|
|||
f.seek(0)
|
||||
|
||||
return HttpResponse(f)
|
||||
# [[id:20231217T154938.132553][Arcology generate Command]]:3 ends here
|
||||
|
|
Loading…
Reference in New Issue