Skip to content

Commit

Permalink
add /add-media endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
brookback committed May 9, 2024
1 parent be88484 commit e7ab959
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 26 deletions.
12 changes: 11 additions & 1 deletion api/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Router, RouterRequest } from './router.ts';
import { checkAuth, Client } from './auth.ts';
import { Services } from './services/index.ts';
import { pipe } from './pipe.ts';
import { addBook, finishBook, getCurrentBooks, postNote } from './routes/index.ts';
import { addBook, addMedia, finishBook, getCurrentBooks, postNote } from './routes/index.ts';
import { urlForBook } from '../src/_includes/permalinks.ts';
import { setCurrentTrack, setCurrentTrackFromSpotifyUrl } from './routes/index.ts';
import { addLink } from './routes/link.ts';
Expand Down Expand Up @@ -118,6 +118,16 @@ export function createApp(services: Services) {
}),
);

router.route(
'POST',
'/add-media',
pipe(authHandler, async (req) => {
await addMedia(services, await req.json());

return new Response('Media appended');
}),
);

return router;
}

Expand Down
102 changes: 84 additions & 18 deletions api/model/note.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { FileHost } from '../services/index.ts';
import { join } from 'std/path/mod.ts';
import * as Yaml from 'std/yaml/mod.ts';
import { test as hasFrontMatter } from 'std/front_matter/mod.ts';
import * as fm from 'std/front_matter/any.ts';
import { formatFileName, formatISO } from '../date.ts';

export interface Note {
Expand All @@ -27,28 +29,16 @@ export interface NoteInput {

const NOTES_PATH = 'src/notes';

/** What note is appended to for updates to what media I've been consuming during the past week/month/whatever. */
const CURRENT_NOTE = '_CURRENT.md';
const CURRENT_NOTE_TAG = 'recently';
const CURRENT_NOTE_HEADING = '## Uncategorised';

export const add = async (host: FileHost, input: NoteInput): Promise<[Note, string]> => {
// Will go into the frontmatter: "2024-03-15T10:24:35+01:00".
// For practical reasons, I store the "dumb" date in the `date` field and the timezone
// separately. I *could've* put them both as an ISO8601 string with timezone information
// appended à la Temporal, but I'm not sure my current or future site generators will
// play well with that, since `date` tend to be a spEcIal fIeLd.
const metaDate = formatISO(
input.zonedDateTime,
);
// Will be the filename: "2024-03-15-10-24-35"
const fileDate = formatFileName(input.zonedDateTime);

const note: Note = {
contents: input.contents,
fileName: `${fileDate}.md`,
meta: {
date: metaDate,
location: input.location,
timezone: input.zonedDateTime.timeZone.toString(),
tags: input.tags,
},
};
const note = noteOf(`${fileDate}.md`, input);

const filePath = await host.putFile(
addFrontMatter(note.contents, note.meta),
Expand All @@ -58,6 +48,82 @@ export const add = async (host: FileHost, input: NoteInput): Promise<[Note, stri
return [note, filePath];
};

export const appendToCurrent = async (host: FileHost, input: NoteInput): Promise<[Note, string]> => {
// Always append special tag:
input.tags = [...input.tags ?? [], CURRENT_NOTE_TAG];

const filePath = join(NOTES_PATH, CURRENT_NOTE);

const current = await host.getFile(filePath);

const contentsOf = (existing: string = ''): string => {
existing = existing.trim();

if (!existing.includes(CURRENT_NOTE_HEADING)) {
return `${existing}
${CURRENT_NOTE_HEADING}
- ${input.contents}`.trim();
}

return `${existing}\n- ${input.contents}`.trim();
};

// Add new
if (!current) {
const note = noteOf(CURRENT_NOTE, input);
const contents = contentsOf();

const filePath = await host.putFile(
addFrontMatter(contents, note.meta),
join(NOTES_PATH, note.fileName),
);

return [note, filePath];
} else {
// Append
const existingBody = (() => {
if (hasFrontMatter(current)) {
return fm.extract<Meta>(current).body;
}

return current;
})();

const newContents = contentsOf(existingBody);

const note = noteOf(CURRENT_NOTE, input);

const filePath = await host.putFile(
addFrontMatter(newContents, note.meta),
join(NOTES_PATH, note.fileName),
);

return [note, filePath];
}
};

const noteOf = (fileName: string, input: NoteInput): Note => {
return {
contents: input.contents,
fileName,
meta: {
// Will go into the frontmatter: "2024-03-15T10:24:35+01:00".
// For practical reasons, I store the "dumb" date in the `date` field and the timezone
// separately. I *could've* put them both as an ISO8601 string with timezone information
// appended à la Temporal, but I'm not sure my current or future site generators will
// play well with that, since `date` tend to be a spEcIal fIeLd.
date: formatISO(
input.zonedDateTime,
),
location: input.location,
timezone: input.zonedDateTime.timeZoneId.toString(),
tags: input.tags,
},
};
};

const addFrontMatter = <T extends Record<string, unknown>>(
contents: string,
fm: T,
Expand Down
2 changes: 1 addition & 1 deletion api/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { postNote } from './note.ts';
export { addMedia, postNote } from './note.ts';
export { addBook, finishBook, getCurrentBooks } from './book.ts';
export { setCurrentTrack, setCurrentTrackFromSpotifyUrl } from './track.ts';
6 changes: 6 additions & 0 deletions api/routes/note.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@ export const postNote = async (services: Services, json: any) => {
return new URL(notePermalinkOf(note.fileName), getConfig('ROOT_URL'));
};

export const addMedia = async (services: Services, json: any) => {
const input = inputOf(json); // throws on validation errors

await Notes.appendToCurrent(services.fileHost, input);
};

function assert<T>(v: any, type: string, err: () => Error): asserts v is T {
if (typeof v != type) {
throw err();
Expand Down
11 changes: 10 additions & 1 deletion api/services/local.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@ export const createLocal = (): FileHost => {
},
getFile: async (filePath) => {
filePath = join(Deno.cwd(), filePath);
return await Deno.readTextFile(filePath);

try {
return await Deno.readTextFile(filePath);
} catch (ex) {
if (!(ex instanceof Deno.errors.NotFound)) {
throw ex;
}

return null;
}
},
};
};
17 changes: 17 additions & 0 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions src/notes/2024-05-06-23-15-14.md → src/notes/_CURRENT.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
---
date: '2024-05-06T23:15:14+02:00'
location: <where>
timezone: Europe/Stockholm
draft: true
date: '2024-05-09T20:21:00+02:00'
tags:
- recently
---

## Watched

- _Poor Things_ (Disney+)
Expand Down
88 changes: 88 additions & 0 deletions test/add-media.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { assertEquals } from 'std/assert/mod.ts';
import { assertSpyCall, assertSpyCalls, spy } from 'std/testing/mock.ts';
import { createApp } from '../api/app.ts';
import { mock } from './_mock.ts';
import 'temporal-polyfill/global';

const BASE_URL = 'http://localhost:8000';

Deno.test('API /add-media creates if not exists ok', async () => {
const { services } = mock();

services.fileHost.getFile = spy(() => Promise.resolve(null));

const router = createApp(services);
const date = Temporal.ZonedDateTime.from('2024-03-07T08:27:35[Asia/Bangkok]');
const res = await router.run(
new Request(new URL('/add-media', BASE_URL), {
method: 'POST',
headers: {
Authorization: 'API-Token aaa',
ContentType: 'application/json',
},
body: JSON.stringify({
contents: 'Some movie I watched',
date: date,
tags: 'bar,baz',
}),
}),
);

assertEquals(res.status, 200);

assertSpyCalls(services.fileHost.putFile, 1);
assertSpyCall(services.fileHost.putFile, 0, {
args: [
`---\ndate: '2024-03-07T08:27:35+07:00'\ntimezone: Asia/Bangkok\ntags:\n - bar\n - baz\n - recently\n---\n## Uncategorised\n\n- Some movie I watched\n\n`,
'src/notes/_CURRENT.md',
],
});
});

Deno.test('API /add-media appends if exists ok', async () => {
const { services } = mock();

services.fileHost.getFile = spy(() => Promise.resolve(`---
date: '2024-03-07T08:27:35+07:00'
timezone: Asia/Bangkok
tags:
- bar
- baz
- recently
---
## Movies
- The Godfather
## Uncategorised
- A music track
`));

const router = createApp(services);
const date = Temporal.ZonedDateTime.from('2024-03-07T08:27:35[Asia/Bangkok]');
const res = await router.run(
new Request(new URL('/add-media', BASE_URL), {
method: 'POST',
headers: {
Authorization: 'API-Token aaa',
ContentType: 'application/json',
},
body: JSON.stringify({
contents: 'Some movie I watched',
date: date,
tags: 'bar,baz',
}),
}),
);

assertEquals(res.status, 200);

assertSpyCalls(services.fileHost.putFile, 1);
assertSpyCall(services.fileHost.putFile, 0, {
args: [
`---\ndate: '2024-03-07T08:27:35+07:00'\ntimezone: Asia/Bangkok\ntags:\n - bar\n - baz\n - recently\n---\n## Movies\n\n- The Godfather\n\n## Uncategorised\n\n- A music track\n- Some movie I watched\n\n`,
'src/notes/_CURRENT.md',
],
});
});

0 comments on commit e7ab959

Please sign in to comment.