Compare commits
No commits in common. "main" and "v0.2.0" have entirely different histories.
17 changed files with 172 additions and 507 deletions
|
|
@ -1,3 +1,21 @@
|
|||
# name: Deploy eolas-api
|
||||
# on:
|
||||
# push:
|
||||
# branches: [main]
|
||||
# jobs:
|
||||
# deploy:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v3
|
||||
# - run: |
|
||||
# echo "${{ secrets.VPS_DEPLOY_USER_SSH_KEY }}" > /tmp/ssh_key
|
||||
# chmod 600 /tmp/ssh_key
|
||||
# ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{ vars.VPS_DEPLOY_USER }} "bash -c 'cd /var/www/eolas-api && rm -rf * .[^.]*'"
|
||||
# scp -i /tmp/ssh_key -o StrictHostKeyChecking=no -r ./* ${{ vars.VPS_DEPLOY_USER }}:/var/www/eolas-api/
|
||||
# rm /tmp/ssh_key
|
||||
#
|
||||
#
|
||||
|
||||
name: Deploy eolas-api
|
||||
on:
|
||||
push:
|
||||
|
|
@ -10,41 +28,75 @@ jobs:
|
|||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Deploy to VPS
|
||||
- name: Determine version bump and update package.json
|
||||
id: version
|
||||
run: |
|
||||
echo "⚡ INFO Installing rsync"
|
||||
apt-get update && apt-get install -y rsync
|
||||
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||
echo "Previous version: $latest_tag"
|
||||
|
||||
echo "⚡ INFO Retrieving SSH key for deploy user"
|
||||
commit_msg=$(git log -1 --pretty=%B)
|
||||
|
||||
version=${latest_tag#v}
|
||||
IFS='.' read -r major minor patch <<< "$version"
|
||||
|
||||
if echo "$commit_msg" | grep -qE "^major(\(.*\))?:|BREAKING CHANGE:"; then
|
||||
major=$((major + 1))
|
||||
minor=0
|
||||
patch=0
|
||||
elif echo "$commit_msg" | grep -qE "^feat(\(.*\))?:"; then
|
||||
minor=$((minor + 1))
|
||||
patch=0
|
||||
elif echo "$commit_msg" | grep -qE "^fix(\(.*\))?:"; then
|
||||
patch=$((patch + 1))
|
||||
else
|
||||
echo "No version bump needed"
|
||||
echo "new_tag=" >> $GITHUB_OUTPUT
|
||||
exit 0
|
||||
fi
|
||||
|
||||
new_tag="v${major}.${minor}.${patch}"
|
||||
new_version="${major}.${minor}.${patch}"
|
||||
echo "New version: $new_tag"
|
||||
echo "new_tag=$new_tag" >> $GITHUB_OUTPUT
|
||||
echo "new_version=$new_version" >> $GITHUB_OUTPUT
|
||||
|
||||
# Update package.json
|
||||
sed -i "s/\"version\": \".*\"/\"version\": \"$new_version\"/" package.json
|
||||
|
||||
- name: Commit version change
|
||||
if: steps.version.outputs.new_tag != ''
|
||||
run: |
|
||||
git config user.name "forgejo-actions[bot]"
|
||||
git config user.email "forgejo-actions[bot]@noreply"
|
||||
git add package.json
|
||||
git commit --amend --no-edit
|
||||
git push -f origin main
|
||||
|
||||
- name: Create and push tag
|
||||
if: steps.version.outputs.new_tag != ''
|
||||
run: |
|
||||
git tag ${{ steps.version.outputs.new_tag }}
|
||||
git push origin ${{ steps.version.outputs.new_tag }}
|
||||
|
||||
- name: Create Forgejo Release
|
||||
if: steps.version.outputs.new_tag != ''
|
||||
run: |
|
||||
# Get the commit message for release body
|
||||
commit_msg=$(git log -1 --pretty=%B | jq -Rs .)
|
||||
|
||||
curl -X POST \
|
||||
"${{ github.server_url }}/api/v1/repos/${{ github.repository }}/releases" \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"tag_name\": \"${{ steps.version.outputs.new_tag }}\",
|
||||
\"name\": \"${{ steps.version.outputs.new_tag }}\",
|
||||
\"body\": $commit_msg
|
||||
}"
|
||||
|
||||
- run: |
|
||||
echo "${{ secrets.VPS_DEPLOY_USER_SSH_KEY }}" > /tmp/ssh_key
|
||||
chmod 600 /tmp/ssh_key
|
||||
|
||||
echo "⚡ INFO Stopping service on VPS"
|
||||
ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{vars.VPS_DEPLOY_USER}} sudo /usr/bin/systemctl stop eolas-api.service
|
||||
|
||||
echo "⚡ INFO Copy updated sourcefile files via rsync"
|
||||
rsync -avz --delete --inplace --exclude='.env' --exclude=".git" -e "ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no" ./ ${{ vars.VPS_DEPLOY_USER }}:/var/www/eolas-api/
|
||||
|
||||
echo "⚡ INFO Run npm install on VPS"
|
||||
ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{vars.VPS_DEPLOY_USER}} "bash -l -c 'source ~/.nvm/nvm.sh && cd /var/www/eolas-api && npm install --omit=dev'"
|
||||
|
||||
echo "⚡ INFO Restarting service"
|
||||
ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{vars.VPS_DEPLOY_USER}} "bash -l -c 'sudo /usr/bin/systemctl daemon-reload && sudo /usr/bin/systemctl start eolas-api.service'"
|
||||
|
||||
ssh -i /tmp/ssh_key -o StrictHostKeyChecking=no ${{ vars.VPS_DEPLOY_USER }} "bash -c 'cd /var/www/eolas-api && rm -rf * .[^.]*'"
|
||||
scp -i /tmp/ssh_key -o StrictHostKeyChecking=no -r ./* ${{ vars.VPS_DEPLOY_USER }}:/var/www/eolas-api/
|
||||
rm /tmp/ssh_key
|
||||
|
||||
- name: Notify success
|
||||
if: success()
|
||||
run: |
|
||||
curl -u thomas:${{ secrets.NTFY_PASSWORD }} \
|
||||
-H "Tags: Forgejo Runner" \
|
||||
-d "🟩 eolas-api successfully deployed" \
|
||||
https://ntfy.systemsobscure.net/eolas
|
||||
|
||||
- name: Notify failure
|
||||
if: failure()
|
||||
run: |
|
||||
curl -u thomas:${{ secrets.NTFY_PASSWORD }} \
|
||||
-H "Tags: Forgejo Runner" \
|
||||
-d "🟥 An error occurred deploying eolas-api. See Forgejo Action logs." \
|
||||
https://ntfy.systemsobscure.net/eolas
|
||||
|
|
|
|||
214
README.md
214
README.md
|
|
@ -1,207 +1,9 @@
|
|||
# eolas-api
|
||||
TBC
|
||||
|
||||
API written in NodeJS that queries my Zettelkasten, Eolas.
|
||||
|
||||
It is a constituent part of my knowledge management system comprising [eolas](https://forgejo.systemsobscure.net/thomasabishop/eolas),
|
||||
[eolas-db](https://forgejo.systemsobscure.net/thomasabishop/eolas-db), and [eolas-app](https://forgejo.systemsobscure.net/thomasabishop/eolas-app).
|
||||
|
||||
The application reads from an SQLite database managed via [eolas-db](https://forgejo.systemsobscure.net/thomasabishop/eolas-db).
|
||||
|
||||
## Local development
|
||||
|
||||
```sh
|
||||
npm install
|
||||
npm run start
|
||||
```
|
||||
|
||||
This will start the local server at `http://localhost:4000`. It will look for
|
||||
stub database at `/data/eolas.db`.
|
||||
|
||||
## Deployment
|
||||
|
||||
The API is deployed to my remote VPS, residing at `/var/www/eolas-api`. The
|
||||
database that it reads from is located at `/data/sqlite/eolas/eolas.db`.
|
||||
|
||||
Deployment is automated via [Forgejo action](https://forgejo.systemsobscure.net/thomasabishop/eolas-api/src/branch/main/.forgejo/workflows/deploy.yaml). Deployment actions are always executed by the `deploy` user on the VPS.
|
||||
|
||||
The action script transfers the source files to the VPS and installs necessary
|
||||
packages. It also restarts the `eolas-api` systemd service on the VPS.
|
||||
|
||||
### `systemd` service
|
||||
|
||||
On the VPS, `eolas-api` runs as a `systemd` service. See the
|
||||
[unit file](./systemd/eolas-api.service) for details.
|
||||
|
||||
## API
|
||||
|
||||
### Search
|
||||
|
||||
```
|
||||
GET /search/<search_term>
|
||||
```
|
||||
|
||||
```json
|
||||
|
||||
{
|
||||
"count": 2,
|
||||
"data": [
|
||||
{
|
||||
"entry": "Test_values_in_Bash",
|
||||
"excerpt": "# <mark>Test</mark> values in Bash\n\n`<mark>test</mark>` is a built-in command that is used to compare values or determine whether\nsomething is the case.\n\nWe..."
|
||||
},
|
||||
{
|
||||
"entry": "Testing_Python_code",
|
||||
"excerpt": "...for a module called `lorem`, it will detzect the unit <mark>test</mark>\n files `lorem_<mark>test</mark>.py` and `<mark>test</mark>_lorem.py`.\n- In order to detect tests..."
|
||||
},
|
||||
```
|
||||
|
||||
### Diagnostics
|
||||
|
||||
#### Get broken links
|
||||
|
||||
```
|
||||
GET /diagnostics/broken-links
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 1,
|
||||
"data": [
|
||||
{
|
||||
"source_entry_title": "Reducing_fractions",
|
||||
"broken_link_title": "Equivalent%20fractions"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Entries
|
||||
|
||||
#### Get all entries
|
||||
|
||||
Return all entries. Optionally limit by length and/or date.
|
||||
|
||||
```
|
||||
GET /entries?limit=2&sort=date
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 5,
|
||||
"data": [
|
||||
{
|
||||
"title": "SSH",
|
||||
"last_modified": "2025-07-10 14:26:04"
|
||||
},
|
||||
{
|
||||
"title": "List_largest_files_bash",
|
||||
"last_modified": "2025-07-07 16:49:12"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get specific entry
|
||||
|
||||
```
|
||||
GET /entries/Memory_versus_processor
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"title": "Memory_versus_processor",
|
||||
"last_modified": "2024-10-18 19:17:01",
|
||||
"size": 270,
|
||||
"body": "# Memory versus processor\n\n Would a more powerful processor with average or reduced memory capacity..."
|
||||
}
|
||||
```
|
||||
|
||||
#### Get backlinks for an entry
|
||||
|
||||
Defaults to alphabetic list.
|
||||
|
||||
```
|
||||
GET /entries/backlinks/The_kernel
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 3,
|
||||
"data": ["Boot_process", "Containerization", "CPU_architecture"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get outlinks for an entry
|
||||
|
||||
Defaults to alphabetic list.
|
||||
|
||||
```
|
||||
GET /entries/outlinks/The_kernel
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 3,
|
||||
"data": ["Basic_model_of_the_operating_system", "Processes", "User_Space"]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get entries associated with a specified tag
|
||||
|
||||
Optionally sort chronologically.
|
||||
|
||||
```
|
||||
GET /entries/tag/memory?sort=date
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 3,
|
||||
"data": [
|
||||
{
|
||||
"entry_title": "Memory_addresses"
|
||||
},
|
||||
{
|
||||
"entry_title": "Call_stack"
|
||||
},
|
||||
{
|
||||
"entry_title": "The_memory_hierarchy"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Tags
|
||||
|
||||
#### Get all tags
|
||||
|
||||
Sorted alphabetically.
|
||||
|
||||
```
|
||||
GET /tags
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 119,
|
||||
"data": ["algebra", "algorithms", "analogue", "android", "..."]
|
||||
}
|
||||
```
|
||||
|
||||
#### Get tags for specified entry
|
||||
|
||||
```
|
||||
GET /tags/The_kernel
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"count": 4,
|
||||
"data": [
|
||||
"computer-architecture",
|
||||
"memory",
|
||||
"operating-systems",
|
||||
"systems-programming"
|
||||
]
|
||||
}
|
||||
```
|
||||
| HTTP Method | Path | Parameter | Returns |
|
||||
| ----------- | --------------- | ------------- | -------------------------------- |
|
||||
| GET | `entry` | `entry_title` | Body text, tags, title |
|
||||
| GET | `tag` | `tag_name` | List of entries for supplied tag |
|
||||
| GET | `refs` | `entry_title` | List of linked entries |
|
||||
| GET | `full_metadata` | `entry_title` | Tags, links, last modified |
|
||||
| GET | `graph` | `null` | Full network graph |
|
||||
|
|
|
|||
58
package-lock.json
generated
58
package-lock.json
generated
|
|
@ -10,8 +10,7 @@
|
|||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"morgan": "^1.10.1"
|
||||
"express": "^4.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9.20.0",
|
||||
|
|
@ -393,24 +392,6 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"safe-buffer": "5.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth/node_modules/safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
|
|
@ -1499,34 +1480,6 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/morgan": {
|
||||
"version": "1.10.1",
|
||||
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
|
||||
"integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"basic-auth": "~2.0.1",
|
||||
"debug": "2.6.9",
|
||||
"depd": "~2.0.0",
|
||||
"on-finished": "~2.3.0",
|
||||
"on-headers": "~1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/morgan/node_modules/on-finished": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
|
||||
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ee-first": "1.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
|
|
@ -1582,15 +1535,6 @@
|
|||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/on-headers": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/optionator": {
|
||||
"version": "0.9.4",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "eolas-api",
|
||||
"version": "0.0.0",
|
||||
"version": "0.2.0",
|
||||
"description": "API for querying eolas-db, my Zettelkasten database",
|
||||
"license": "ISC",
|
||||
"author": "Thomas Bishop",
|
||||
|
|
@ -8,14 +8,12 @@
|
|||
"imports": {},
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "NODE_OPTIONS='--experimental-sqlite' node --watch --env-file=.env src/index.js",
|
||||
"start": "NODE_OPTIONS='--experimental-sqlite' node --env-file=.env src/index.js",
|
||||
"start": "NODE_OPTIONS='--experimental-sqlite' node --watch --env-file=.env src/index.js",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"express": "^4.21.2",
|
||||
"morgan": "^1.10.1"
|
||||
"express": "^4.21.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^9.20.0",
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
export default class DiagnosticsController {
|
||||
diagnosticsService
|
||||
|
||||
constructor(diagnosticsService) {
|
||||
this.diagnosticsService = diagnosticsService
|
||||
}
|
||||
|
||||
getBrokenLinks = (req, res) => {
|
||||
const brokenLinks = this.diagnosticsService.getBrokenLinks()
|
||||
if (!brokenLinks) {
|
||||
return res.status(404).json({
|
||||
message: `Broken link list could not be retrieved`,
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(brokenLinks)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
export default class SearchController {
|
||||
searchService
|
||||
|
||||
constructor(searchService) {
|
||||
this.searchService = searchService
|
||||
}
|
||||
|
||||
search = (req, res) => {
|
||||
const query = req.params.query
|
||||
const results = this.searchService.search(query)
|
||||
if (!results) {
|
||||
return res.status(404).json({
|
||||
message: `No matches for query ${query}`,
|
||||
})
|
||||
}
|
||||
|
||||
return res.json(results)
|
||||
}
|
||||
}
|
||||
16
src/index.js
16
src/index.js
|
|
@ -1,30 +1,18 @@
|
|||
import express from "express"
|
||||
import entries from "./routes/entries.js"
|
||||
import tags from "./routes/tags.js"
|
||||
import search from "./routes/search.js"
|
||||
import diagnostics from "./routes/diagnostics.js"
|
||||
import cors from "cors"
|
||||
import { validateApiKey } from "./middlewear/auth.js"
|
||||
import morgan from "morgan"
|
||||
|
||||
const app = express()
|
||||
|
||||
const port = process.env.PORT || 4000
|
||||
const port = process.env.PORT || 3000
|
||||
|
||||
app.use(morgan("short"))
|
||||
app.use(cors())
|
||||
app.use(express.json())
|
||||
app.use("/", validateApiKey)
|
||||
app.use("/entries", entries)
|
||||
app.use("/tags", tags)
|
||||
app.use("/search", search)
|
||||
app.use("/diagnostics", diagnostics)
|
||||
|
||||
app.listen(port, () => {
|
||||
console.info(`TB-INFO eolas-api running on NodeJS ${process.version}`)
|
||||
console.info(`TB-INFO eolas-api server running at http://localhost:${port}`)
|
||||
})
|
||||
|
||||
app.get("/health", (req, res) => {
|
||||
res.status(200).json({ status: "ok" })
|
||||
console.info(`INFO eolas-api server running at http://localhost:${port}`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import express from "express"
|
||||
import DiagnosticsService from "../services/DiagnosticsService.js"
|
||||
import DiagnosticsController from "../controllers/DiagnosticsController.js"
|
||||
import database from "../db/connection.js"
|
||||
|
||||
const router = express.Router()
|
||||
const diagnosticsService = new DiagnosticsService(database)
|
||||
const diagnosticsContoller = new DiagnosticsController(diagnosticsService)
|
||||
|
||||
router.get("/broken-links", diagnosticsContoller.getBrokenLinks)
|
||||
export default router
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import express from "express"
|
||||
import SearchService from "../services/SearchService.js"
|
||||
import SearchController from "../controllers/SearchController.js"
|
||||
import database from "../db/connection.js"
|
||||
|
||||
const router = express.Router()
|
||||
const searchService = new SearchService(database)
|
||||
const searchController = new SearchController(searchService)
|
||||
|
||||
router.get("/:query", searchController.search)
|
||||
|
||||
export default router
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import { GET_BROKEN_LINKS } from "../sql/diagnostics.js"
|
||||
|
||||
export default class DiagnosticsService {
|
||||
database
|
||||
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
|
||||
getBrokenLinks = () => {
|
||||
const brokenLinks = this.database.prepare(GET_BROKEN_LINKS).all()
|
||||
return {
|
||||
count: brokenLinks.length,
|
||||
data: brokenLinks,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,73 +1,68 @@
|
|||
import {
|
||||
GET_ALL_ENTRIES,
|
||||
GET_ENTRY,
|
||||
GET_ENTRIES_FOR_TAG,
|
||||
GET_BACKLINKS_FOR_ENTRY,
|
||||
GET_OUTLINKS_FOR_ENTRY,
|
||||
} from "../sql/entries.js"
|
||||
import { GET_ALL_ENTRIES, GET_ENTRY, GET_ENTRIES_FOR_TAG, GET_BACKLINKS_FOR_ENTRY, GET_OUTLINKS_FOR_ENTRY } from "../sql/entries.js"
|
||||
|
||||
export default class EntriesService {
|
||||
database
|
||||
database
|
||||
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
|
||||
getEntry = (title) => {
|
||||
return this.database.prepare(GET_ENTRY).get(title)
|
||||
}
|
||||
getEntry = (title) => {
|
||||
return this.database.prepare(GET_ENTRY).get(title)
|
||||
}
|
||||
|
||||
getAllEntries = (sort, limit) => {
|
||||
const entries = this.database.prepare(GET_ALL_ENTRIES).all()
|
||||
getAllEntries = (sort, limit) => {
|
||||
const entries = this.database.prepare(GET_ALL_ENTRIES).all()
|
||||
|
||||
const sorted =
|
||||
sort === "date" ? this._sortByDate(entries) : this._sortByTitle(entries, "title")
|
||||
const sorted =
|
||||
sort === "date" ? this._sortByDate(entries) : this._sortByTitle(entries, "title")
|
||||
|
||||
const sliced = sorted.slice(0, Number(limit) || -1)
|
||||
const sliced = sorted.slice(0, Number(limit) || -1)
|
||||
|
||||
return {
|
||||
count: sliced.length,
|
||||
data: sliced,
|
||||
}
|
||||
}
|
||||
return {
|
||||
count: sliced.length,
|
||||
data: sliced,
|
||||
}
|
||||
}
|
||||
|
||||
getEntriesForTag = (tag, sort) => {
|
||||
const entries = this.database.prepare(GET_ENTRIES_FOR_TAG).all(tag)
|
||||
return {
|
||||
count: entries.length,
|
||||
data:
|
||||
sort === "date"
|
||||
? this._sortByDate(entries)
|
||||
: this._sortByTitle(entries, "entry_title"),
|
||||
}
|
||||
}
|
||||
getEntriesForTag = (tag, sort) => {
|
||||
const entries = this.database.prepare(GET_ENTRIES_FOR_TAG).all(tag)
|
||||
return {
|
||||
count: entries.length,
|
||||
data:
|
||||
sort === "date"
|
||||
? this._sortByDate(entries)
|
||||
: this._sortByTitle(entries, "entry_title"),
|
||||
}
|
||||
}
|
||||
|
||||
getBacklinksForEntry = (title) => {
|
||||
const backlinks = this.database.prepare(GET_BACKLINKS_FOR_ENTRY).all(title)
|
||||
const sorted = this._sortByTitle(backlinks, "source_entry_title")
|
||||
const list = sorted.flatMap((i) => i.source_entry_title)
|
||||
return {
|
||||
count: backlinks.length,
|
||||
data: list,
|
||||
}
|
||||
}
|
||||
getBacklinksForEntry = (title) => {
|
||||
const backlinks = this.database.prepare(GET_BACKLINKS_FOR_ENTRY).all(title)
|
||||
const sorted = this._sortByTitle(backlinks, "source_entry_title")
|
||||
const list = sorted.flatMap((i) => i.source_entry_title)
|
||||
return {
|
||||
count: backlinks.length,
|
||||
data: list
|
||||
}
|
||||
}
|
||||
|
||||
getOutlinksForEntry = (title) => {
|
||||
const outlinks = this.database.prepare(GET_OUTLINKS_FOR_ENTRY).all(title)
|
||||
const sorted = this._sortByTitle(outlinks, "target_entry_title")
|
||||
const list = sorted.flatMap((i) => i.target_entry_title)
|
||||
return {
|
||||
count: outlinks.length,
|
||||
data: list,
|
||||
}
|
||||
}
|
||||
getOutlinksForEntry = (title) => {
|
||||
const outlinks = this.database.prepare(GET_OUTLINKS_FOR_ENTRY).all(title)
|
||||
const sorted = this._sortByTitle(outlinks, "target_entry_title")
|
||||
const list = sorted.flatMap((i) => i.target_entry_title)
|
||||
return {
|
||||
count: outlinks.length,
|
||||
data: list
|
||||
}
|
||||
|
||||
_sortByTitle = (entries, fieldName) => {
|
||||
return entries.sort((a, b) => a[fieldName].localeCompare(b[fieldName]))
|
||||
}
|
||||
}
|
||||
|
||||
_sortByDate = (entries, fieldName = "last_modified") => {
|
||||
const sorted = entries.sort((a, b) => new Date(b[fieldName]) - new Date(a[fieldName]))
|
||||
return sorted
|
||||
}
|
||||
_sortByTitle = (entries, fieldName) => {
|
||||
return entries.sort((a, b) => a[fieldName].localeCompare(b[fieldName]))
|
||||
}
|
||||
|
||||
_sortByDate = (entries, fieldName = "last_modified") => {
|
||||
const sorted = entries.sort((a, b) => new Date(b[fieldName]) - new Date(a[fieldName]))
|
||||
return sorted
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +0,0 @@
|
|||
import { SEARCH } from "../sql/search.js"
|
||||
|
||||
export default class SearchService {
|
||||
database
|
||||
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
|
||||
search = (query) => {
|
||||
const results = this.database.prepare(SEARCH).all(query.trim())
|
||||
return {
|
||||
count: results.length,
|
||||
data: results,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,27 +1,27 @@
|
|||
import { GET_ALL_TAGS, GET_TAGS_FOR_ENTRY } from "../sql/tags.js"
|
||||
|
||||
export default class TagsService {
|
||||
database
|
||||
database
|
||||
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
constructor(database) {
|
||||
this.database = database
|
||||
}
|
||||
|
||||
getAllTags = () => {
|
||||
const tags = this.database.prepare(GET_ALL_TAGS).all()
|
||||
const sorted = this._sortTags(tags, "name")
|
||||
const list = sorted.flatMap((tag) => tag.name)
|
||||
return { count: tags.length, data: list }
|
||||
}
|
||||
getAllTags = () => {
|
||||
const tags = this.database.prepare(GET_ALL_TAGS).all()
|
||||
const sorted = this._sortTags(tags, "name")
|
||||
const list = sorted.flatMap((tag) => tag.name)
|
||||
return { count: tags.length, data: list }
|
||||
}
|
||||
|
||||
getTagsForEntry = (entryTitle) => {
|
||||
const tags = this.database.prepare(GET_TAGS_FOR_ENTRY).all(entryTitle)
|
||||
const sorted = this._sortTags(tags, "tag_name")
|
||||
const list = sorted.flatMap((tag) => tag.tag_name)
|
||||
return { count: tags.length, data: list }
|
||||
}
|
||||
getTagsForEntry = (entryTitle) => {
|
||||
const tags = this.database.prepare(GET_TAGS_FOR_ENTRY).all(entryTitle)
|
||||
const sorted = this._sortTags(tags, "tag_name")
|
||||
const list = sorted.flatMap((tag) => tag.tag_name)
|
||||
return { count: tags.length, data: list }
|
||||
}
|
||||
|
||||
_sortTags = (tags, fieldName) => {
|
||||
return tags.sort((a, b) => a[fieldName].localeCompare(b[fieldName]))
|
||||
}
|
||||
_sortTags = (tags, fieldName) => {
|
||||
return tags.sort((a, b) => a[fieldName].localeCompare(b[fieldName]))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
const GET_BROKEN_LINKS = `SELECT * from broken_links`
|
||||
|
||||
export { GET_BROKEN_LINKS }
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
const SEARCH = `SELECT title as entry, snippet(entries_fts, 1, '<mark>', '</mark>', '...', 24) as excerpt FROM entries_fts WHERE entries_fts MATCH ? ORDER BY rank LIMIT 25`
|
||||
|
||||
export { SEARCH }
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
[Unit]
|
||||
Description=eolas-api
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=deploy
|
||||
WorkingDirectory=/var/www/eolas-api
|
||||
Environment="NODE_OPTIONS=--experimental-sqlite"
|
||||
ExecStart=/home/deploy/.nvm/versions/node/v24.10.0/bin/node --env-file=.env /var/www/eolas-api/src/index.js
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
0
test.txt
Normal file
0
test.txt
Normal file
Loading…
Add table
Reference in a new issue