Update all dependencies

This takes part of the work being done in #96 that was reverted but still useful.

Note Tailwind and Fresh weren't upgraded because there's no security vulnerability in either, and I have found the new versions to be worse in performance. Thos will likely stay at those fixed versions going forward.
This commit is contained in:
Bruno Bernardino
2025-09-27 19:39:09 +01:00
parent ba2103afa9
commit 6734e9557b
45 changed files with 11027 additions and 127 deletions

2
.dvmrc
View File

@@ -1 +1 @@
2.4.5 2.5.2

View File

@@ -22,7 +22,7 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v5
- name: Log in to the Container registry - name: Log in to the Container registry
uses: docker/login-action@v3 uses: docker/login-action@v3

View File

@@ -10,7 +10,7 @@ jobs:
deploy: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- name: Configure SSH - name: Configure SSH
run: | run: |
mkdir -p ~/.ssh/ mkdir -p ~/.ssh/

View File

@@ -6,7 +6,7 @@ jobs:
test: test:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: denoland/setup-deno@v2 - uses: denoland/setup-deno@v2
with: with:
deno-version-file: .dvmrc deno-version-file: .dvmrc

View File

@@ -1,4 +1,4 @@
FROM denoland/deno:ubuntu-2.4.5 FROM denoland/deno:ubuntu-2.5.2
EXPOSE 8000 EXPOSE 8000
@@ -9,8 +9,6 @@ WORKDIR /app
# These steps will be re-run upon each file change in your working directory: # These steps will be re-run upon each file change in your working directory:
ADD . /app ADD . /app
RUN rm -fr node_modules _fresh
# Build fresh # Build fresh
RUN deno task build RUN deno task build

View File

@@ -1,3 +1,5 @@
SHELL := /bin/bash
.PHONY: start .PHONY: start
start: start:
deno task start deno task start
@@ -19,10 +21,6 @@ build:
migrate-db: migrate-db:
deno run --allow-net --allow-read --allow-env migrate-db.ts deno run --allow-net --allow-read --allow-env migrate-db.ts
.PHONY: crons/cleanup
crons/cleanup:
deno run --allow-net --allow-read --allow-env crons/cleanup.ts
.PHONY: exec-db .PHONY: exec-db
exec-db: exec-db:
docker exec -it -u postgres $(shell basename $(CURDIR))-postgresql-1 psql docker exec -it -u postgres $(shell basename $(CURDIR))-postgresql-1 psql

View File

@@ -3,7 +3,7 @@ import { Config, PartialDeep } from './lib/types.ts';
/** Check the Config type for all the possible options and instructions. */ /** Check the Config type for all the possible options and instructions. */
const config: PartialDeep<Config> = { const config: PartialDeep<Config> = {
auth: { auth: {
baseUrl: 'http://localhost:8000', // The base URL of the application you use to access the app, i.e. "http://localhost:8000" or "https://cloud.example.com" (SSO redirect, if enabled, will be this + /oidc/callback, so "https://cloud.example.com/oidc/callback") baseUrl: 'http://localhost:8000', // The base URL of the application you use to access the app, i.e. "http://localhost:8000" or "https://cloud.example.com" (note authentication won't work without https:// except for localhost; SSO redirect, if enabled, will be this + /oidc/callback, so "https://cloud.example.com/oidc/callback")
allowSignups: false, // If true, anyone can sign up for an account. Note that it's always possible to sign up for the first user, and they will be an admin allowSignups: false, // If true, anyone can sign up for an account. Note that it's always possible to sign up for the first user, and they will be an admin
enableEmailVerification: false, // If true, email verification will be required for signups (using SMTP settings below) enableEmailVerification: false, // If true, email verification will be required for signups (using SMTP settings below)
enableForeverSignup: true, // If true, all signups become active for 100 years enableForeverSignup: true, // If true, all signups become active for 100 years

View File

@@ -191,7 +191,6 @@ export default function ExpenseModal(
onChange={(event) => { onChange={(event) => {
newExpenseBudget.value = event.currentTarget.value; newExpenseBudget.value = event.currentTarget.value;
}} }}
placeholder='Misc'
> >
{sortedBudgetNames.map((budget) => ( {sortedBudgetNames.map((budget) => (
<option value={budget} selected={newExpenseBudget.value === budget}>{budget}</option> <option value={budget} selected={newExpenseBudget.value === budget}>{budget}</option>

View File

@@ -1,6 +1,6 @@
import { useSignal } from '@preact/signals'; import { useSignal } from '@preact/signals';
import { useEffect, useRef } from 'preact/hooks'; import { useEffect, useRef } from 'preact/hooks';
import { Chart } from 'chart.js'; import { Chart } from 'chart.js/auto';
import { formatNumber } from '/lib/utils/misc.ts'; import { formatNumber } from '/lib/utils/misc.ts';
import { Budget, SupportedCurrencySymbol } from '/lib/types.ts'; import { Budget, SupportedCurrencySymbol } from '/lib/types.ts';

View File

@@ -1,4 +1,4 @@
import { join } from 'std/path/join.ts'; import { join } from '@std/path';
import { Directory, DirectoryFile } from '/lib/types.ts'; import { Directory, DirectoryFile } from '/lib/types.ts';
import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts'; import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';

View File

@@ -1,7 +1,7 @@
import { Directory, DirectoryFile } from '/lib/types.ts'; import { Directory, DirectoryFile } from '/lib/types.ts';
import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts'; import { humanFileSize, TRASH_PATH } from '/lib/utils/files.ts';
interface ListFilesProps { interface ListPhotosProps {
directories: Directory[]; directories: Directory[];
files: DirectoryFile[]; files: DirectoryFile[];
onClickOpenRenameDirectory?: (parentPath: string, name: string) => void; onClickOpenRenameDirectory?: (parentPath: string, name: string) => void;
@@ -13,7 +13,7 @@ interface ListFilesProps {
isShowingNotes?: boolean; isShowingNotes?: boolean;
} }
export default function ListFiles( export default function ListPhotos(
{ {
directories, directories,
files, files,
@@ -24,7 +24,7 @@ export default function ListFiles(
onClickDeleteDirectory, onClickDeleteDirectory,
onClickDeleteFile, onClickDeleteFile,
isShowingNotes, isShowingNotes,
}: ListFilesProps, }: ListPhotosProps,
) { ) {
const dateFormatOptions: Intl.DateTimeFormatOptions = { const dateFormatOptions: Intl.DateTimeFormatOptions = {
year: 'numeric', year: 'numeric',

View File

@@ -1,4 +1,4 @@
import { Cron } from 'https://deno.land/x/croner@8.1.2/dist/croner.js'; import { Cron } from '@hexagon/croner';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';
import { cleanupSessions } from './sessions.ts'; import { cleanupSessions } from './sessions.ts';

View File

@@ -1,13 +1,12 @@
{ {
"lock": false, "lock": false,
"tasks": { "tasks": {
"check": "deno fmt --check && deno lint && deno check **/*.ts && deno check **/*.tsx", "check": "deno fmt --check && deno lint && deno check .",
"cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -", "cli": "echo \"import '\\$fresh/src/dev/cli.ts'\" | deno run --unstable -A -",
"manifest": "deno task cli manifest $(pwd)", "manifest": "deno task cli manifest $(pwd)",
"start": "deno run -A --watch=static/,routes/,lib/,components/,islands/ dev.ts", "start": "deno run -A --watch=static/,routes/,lib/,components/,islands/ dev.ts",
"build": "deno run -A dev.ts build", "build": "deno run -A dev.ts build",
"preview": "deno run -A main.ts", "preview": "deno run -A main.ts",
"update": "deno run -A -r https://fresh.deno.dev/update .",
"test": "deno test -A --check" "test": "deno test -A --check"
}, },
"fmt": { "fmt": {
@@ -16,13 +15,14 @@
"indentWidth": 2, "indentWidth": 2,
"singleQuote": true, "singleQuote": true,
"proseWrap": "preserve", "proseWrap": "preserve",
"exclude": ["README.md"] "exclude": ["README.md", "lib/models/dav.js"]
}, },
"lint": { "lint": {
"rules": { "rules": {
"tags": ["fresh", "recommended"], "tags": ["fresh", "recommended"],
"exclude": ["no-explicit-any", "no-empty-interface", "no-window", "no-unused-vars"] "exclude": ["no-explicit-any", "no-empty-interface", "no-window", "no-unused-vars"]
} },
"exclude": ["lib/models/dav.js"]
}, },
"exclude": ["./_fresh/*", "./node_modules/*", "**/_fresh/*"], "exclude": ["./_fresh/*", "./node_modules/*", "**/_fresh/*"],
"compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" }, "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "preact" },
@@ -30,28 +30,36 @@
"imports": { "imports": {
"/": "./", "/": "./",
"./": "./", "./": "./",
"xml": "https://deno.land/x/xml@2.1.3/mod.ts",
"mrmime": "https://deno.land/x/mrmime@v2.0.0/mod.ts",
"fresh/": "https://deno.land/x/fresh@1.7.3/", "fresh/": "https://deno.land/x/fresh@1.7.3/",
"$fresh/": "https://deno.land/x/fresh@1.7.3/", "$fresh/": "https://deno.land/x/fresh@1.7.3/",
"std/": "https://deno.land/std@0.224.0/",
"$std/": "https://deno.land/std@0.224.0/", "postgres": "jsr:@db/postgres@0.19.5",
"postgres": "https://deno.land/x/postgres@v0.19.3/mod.ts", "@b-fuze/deno-dom": "jsr:@b-fuze/deno-dom@0.1.56",
"preact": "https://esm.sh/preact@10.23.2", "@fresh/plugin-vite": "jsr:@fresh/plugin-vite@1.0.4",
"preact/": "https://esm.sh/preact@10.23.2/", "@hexagon/croner": "jsr:@hexagon/croner@9.1.0",
"@preact/signals": "https://esm.sh/*@preact/signals@1.3.0", "@mikaelporttila/rss": "jsr:@mikaelporttila/rss@1.1.3",
"@preact/signals-core": "https://esm.sh/*@preact/signals-core@1.8.0", "@libs/qrcode": "jsr:@libs/qrcode@3.0.0",
"chart.js": "https://esm.sh/chart.js@4.4.9/auto", "@libs/xml": "jsr:@libs/xml@7.0.2",
"otpauth": "https://esm.sh/otpauth@9.4.0", "@simplewebauthn/server": "jsr:@simplewebauthn/server@13.2.1",
"qrcode": "https://esm.sh/qrcode@1.5.4", "@simplewebauthn/browser": "jsr:@simplewebauthn/browser@13.2.0",
"openid-client": "https://esm.sh/openid-client@6.6.3", "@std/assert": "jsr:@std/assert@1.0.14",
"@simplewebauthn/server": "jsr:@simplewebauthn/server@13.1.2", "@std/dotenv": "jsr:@std/dotenv@0.225.5",
"@simplewebauthn/server/helpers": "jsr:@simplewebauthn/server@13.1.2/helpers", "@std/encoding": "jsr:@std/encoding@1.0.10",
"@simplewebauthn/browser": "jsr:@simplewebauthn/browser@13.1.2", "@std/http": "jsr:@std/http@1.0.20",
"@std/path": "jsr:@std/path@1.1.2",
"chart.js": "npm:chart.js@4.5.0",
"mrmime": "npm:mrmime@2.0.1",
"nodemailer": "npm:nodemailer@7.0.6",
"openid-client": "npm:openid-client@6.8.0",
"otpauth": "npm:otpauth@9.4.1",
"preact": "npm:preact@10.27.2",
"sharp": "npm:sharp@0.34.4",
"tailwindcss": "npm:tailwindcss@3.4.17", "tailwindcss": "npm:tailwindcss@3.4.17",
"tailwindcss/": "npm:/tailwindcss@3.4.17/", "tailwindcss/": "npm:/tailwindcss@3.4.17/",
"tailwindcss/plugin": "npm:/tailwindcss@3.4.17/plugin.js", "tailwindcss/plugin": "npm:/tailwindcss@3.4.17/plugin.js",
"nodemailer": "npm:nodemailer@7.0.5", "vite": "npm:vite@7.1.7",
"tsdav": "https://raw.githubusercontent.com/sunsama/tsdav/cc1c5a09b64c87bbee7e5f171cfcb6748e99469e/dist/tsdav.js" "@preact/signals": "npm:@preact/signals@2.3.1"
} }
} }

2
dev.ts
View File

@@ -3,6 +3,6 @@
import dev from 'fresh/dev.ts'; import dev from 'fresh/dev.ts';
import config from './fresh.config.ts'; import config from './fresh.config.ts';
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
await dev(import.meta.url, './main.ts', config); await dev(import.meta.url, './main.ts', config);

View File

@@ -18,7 +18,7 @@ services:
# NOTE: If you don't want to use the CardDav/CalDav servers, you can comment/remove this service. # NOTE: If you don't want to use the CardDav/CalDav servers, you can comment/remove this service.
radicale: radicale:
image: tomsquest/docker-radicale:3.5.4.0 image: tomsquest/docker-radicale:3.5.6.0
ports: ports:
- 5232:5232 - 5232:5232
init: true init: true

View File

@@ -1,6 +1,6 @@
services: services:
website: website:
image: ghcr.io/bewcloud/bewcloud:v2.5.3 image: ghcr.io/bewcloud/bewcloud:v2.6.0
restart: always restart: always
ports: ports:
- 127.0.0.1:8000:8000 - 127.0.0.1:8000:8000
@@ -32,7 +32,7 @@ services:
# NOTE: If you don't want to use the CardDav/CalDav servers, you can comment/remove this service. # NOTE: If you don't want to use the CardDav/CalDav servers, you can comment/remove this service.
radicale: radicale:
image: tomsquest/docker-radicale:3.5.4.0 image: tomsquest/docker-radicale:3.5.6.0
# NOTE: uncomment below only if you need to connect to the CardDav/CalDav servers from outside the container # NOTE: uncomment below only if you need to connect to the CardDav/CalDav servers from outside the container
# ports: # ports:
# - 127.0.0.1:5232:5232 # - 127.0.0.1:5232:5232

View File

@@ -391,8 +391,8 @@ export default function MultiFactorAuthSettings({ methods }: MultiFactorAuthSett
<p class='mb-4'> <p class='mb-4'>
1. Scan this QR code with your authenticator app (Aegis Authenticator, Google Authenticator, etc.): 1. Scan this QR code with your authenticator app (Aegis Authenticator, Google Authenticator, etc.):
</p> </p>
<section class='flex justify-center mb-4'> <section class='flex justify-center mb-4 max-w-sm mx-auto'>
<img src={setupData.value.qrCodeUrl} alt='TOTP QR Code' class='border' /> <img src={setupData.value.qrCodeUrl} alt='TOTP QR Code' class='border-8 border-white' />
</section> </section>
<p class='text-sm text-gray-400 mb-4'> <p class='text-sm text-gray-400 mb-4'>
Or manually enter this secret:{' '} Or manually enter this secret:{' '}

View File

@@ -1,7 +1,6 @@
import { decodeBase64Url, encodeBase64Url } from 'std/encoding/base64url.ts'; import { decodeBase64, decodeBase64Url, encodeBase64Url } from '@std/encoding';
import { decodeBase64 } from 'std/encoding/base64.ts'; import { Cookie, getCookies, setCookie } from '@std/http';
import { Cookie, getCookies, setCookie } from 'std/http/cookie.ts'; import '@std/dotenv/load';
import 'std/dotenv/load.ts';
import { generateHash, isRunningLocally } from './utils/misc.ts'; import { generateHash, isRunningLocally } from './utils/misc.ts';
import { User, UserSession } from './types.ts'; import { User, UserSession } from './types.ts';

View File

@@ -1,5 +1,5 @@
import { DOMParser, initParser } from 'https://deno.land/x/deno_dom@v0.1.45/deno-dom-wasm-noinit.ts'; import { DOMParser, initParser } from '@b-fuze/deno-dom/wasm-noinit';
import { Feed, parseFeed } from 'https://deno.land/x/rss@1.0.0/mod.ts'; import { Feed, parseFeed } from '@mikaelporttila/rss';
import { fetchUrl, fetchUrlAsGooglebot, fetchUrlWithProxy, fetchUrlWithRetries } from './utils/misc.ts'; import { fetchUrl, fetchUrlAsGooglebot, fetchUrlWithProxy, fetchUrlWithRetries } from './utils/misc.ts';
import { NewsFeed, NewsFeedCrawlType, NewsFeedType } from './types.ts'; import { NewsFeed, NewsFeedCrawlType, NewsFeedType } from './types.ts';

View File

@@ -124,7 +124,7 @@ function generateInputHtml(
if (type === 'select') { if (type === 'select') {
return ( return (
<select class='mt-1 input-field' id={`field_${name}`} name={name} type={type} {...additionalAttributes}> <select class='mt-1 input-field' id={`field_${name}`} name={name} {...additionalAttributes}>
{options?.map((option) => ( {options?.map((option) => (
<option <option
value={option.value} value={option.value}

View File

@@ -1,5 +1,5 @@
import { Client } from 'postgres'; import { Client } from 'postgres';
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
const POSTGRESQL_HOST = Deno.env.get('POSTGRESQL_HOST') || ''; const POSTGRESQL_HOST = Deno.env.get('POSTGRESQL_HOST') || '';
const POSTGRESQL_USER = Deno.env.get('POSTGRESQL_USER') || ''; const POSTGRESQL_USER = Deno.env.get('POSTGRESQL_USER') || '';

View File

@@ -1,5 +1,4 @@
import { createDAVClient } from 'tsdav'; import { createDAVClient } from '/lib/models/dav.js';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';
import { getColorAsHex, parseVCalendar } from '/lib/utils/calendar.ts'; import { getColorAsHex, parseVCalendar } from '/lib/utils/calendar.ts';
import { concurrentPromises } from '/lib/utils/misc.ts'; import { concurrentPromises } from '/lib/utils/misc.ts';
@@ -146,7 +145,7 @@ export class CalendarModel {
displayName: string, displayName: string,
color?: string, color?: string,
): Promise<void> { ): Promise<void> {
// Make "manual" request (https://www.rfc-editor.org/rfc/rfc4791.html#page-20) because tsdav doesn't have PROPPATCH // Make "manual" request (https://www.rfc-editor.org/rfc/rfc4791.html#page-20) because the dav client doesn't have PROPPATCH
const xmlBody = `<?xml version="1.0" encoding="utf-8"?> const xmlBody = `<?xml version="1.0" encoding="utf-8"?>
<d:proppatch xmlns:d="DAV:" xmlns:a="http://apple.com/ns/ical/"> <d:proppatch xmlns:d="DAV:" xmlns:a="http://apple.com/ns/ical/">
<d:set> <d:set>
@@ -195,15 +194,25 @@ export class CalendarEventModel {
}, },
}; };
const davCalendarEvents: DAVObject[] = await client.fetchCalendarObjects(fetchOptions);
if (dateRange) { if (dateRange) {
fetchOptions.timeRange = { fetchOptions.timeRange = {
start: dateRange.start.toISOString(), start: dateRange.start.toISOString(),
end: dateRange.end.toISOString(), end: dateRange.end.toISOString(),
}; };
fetchOptions.expand = true; fetchOptions.expand = true;
}
const davCalendarEvents: DAVObject[] = await client.fetchCalendarObjects(fetchOptions); // Sometimes the expand option doesn't return anything, so we we fetch with and without it, when queried for a date range
const davCalendarEventsWithExpansion = await client.fetchCalendarObjects(fetchOptions);
for (const davCalendarEvent of davCalendarEventsWithExpansion) {
// Only add the events that are not already in the list
if (!davCalendarEvents.some((davCalendarEvent) => davCalendarEvent.url === davCalendarEvent.url)) {
davCalendarEvents.push(davCalendarEvent);
}
}
}
const calendarEvents: CalendarEvent[] = []; const calendarEvents: CalendarEvent[] = [];

View File

@@ -1,5 +1,4 @@
import { createDAVClient } from 'tsdav'; import { createDAVClient } from '/lib/models/dav.js';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';
import { parseVCard } from '/lib/utils/contacts.ts'; import { parseVCard } from '/lib/utils/contacts.ts';

10891
lib/models/dav.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
import nodemailer from 'nodemailer'; import nodemailer from 'nodemailer';
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
import { escapeHtml } from '/lib/utils/misc.ts'; import { escapeHtml } from '/lib/utils/misc.ts';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';

View File

@@ -1,7 +1,6 @@
import { join } from 'std/path/join.ts'; import { join, resolve } from '@std/path';
import { resolve } from 'std/path/resolve.ts';
import { lookup } from 'mrmime'; import { lookup } from 'mrmime';
import { Cookie, getCookies, setCookie } from 'std/http/cookie.ts'; import { Cookie, getCookies, setCookie } from '@std/http';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';
import { Directory, DirectoryFile, FileShare } from '/lib/types.ts'; import { Directory, DirectoryFile, FileShare } from '/lib/types.ts';

View File

@@ -1,4 +1,4 @@
import { Cookie, getCookies, setCookie } from 'std/http/cookie.ts'; import { Cookie, getCookies, setCookie } from '@std/http';
import { MultiFactorAuthMethod, User } from '/lib/types.ts'; import { MultiFactorAuthMethod, User } from '/lib/types.ts';
import { import {

View File

@@ -1,7 +1,6 @@
import { Secret, TOTP } from 'otpauth'; import { Secret, TOTP } from 'otpauth';
import QRCode from 'qrcode'; import { qrcode } from '@libs/qrcode';
import { encodeBase32 } from 'std/encoding/base32.ts'; import { decodeBase64, encodeBase32, encodeBase64 } from '@std/encoding';
import { decodeBase64, encodeBase64 } from 'std/encoding/base64.ts';
import { MultiFactorAuthMethod } from '/lib/types.ts'; import { MultiFactorAuthMethod } from '/lib/types.ts';
import { MFA_KEY, MFA_SALT } from '/lib/auth.ts'; import { MFA_KEY, MFA_SALT } from '/lib/auth.ts';
@@ -133,7 +132,8 @@ export class TOTPModel {
private static async generateQRCodeDataURL(secret: string, issuer: string, accountName: string): Promise<string> { private static async generateQRCodeDataURL(secret: string, issuer: string, accountName: string): Promise<string> {
const totp = this.createTOTP(secret, issuer, accountName); const totp = this.createTOTP(secret, issuer, accountName);
const uri = totp.toString(); const uri = totp.toString();
return await QRCode.toDataURL(uri); const svgString = await qrcode(uri, { output: 'svg', border: 0 });
return `data:image/svg+xml;base64,${encodeBase64(svgString)}`;
} }
private static verifyTOTPToken(secret: string, token: string, window = 1): boolean { private static verifyTOTPToken(secret: string, token: string, window = 1): boolean {

View File

@@ -1,4 +1,4 @@
import { Feed } from 'https://deno.land/x/rss@1.0.0/mod.ts'; import { Feed } from '@mikaelporttila/rss';
import Database, { sql } from '/lib/interfaces/database.ts'; import Database, { sql } from '/lib/interfaces/database.ts';
import Locker from '/lib/interfaces/locker.ts'; import Locker from '/lib/interfaces/locker.ts';
@@ -136,7 +136,7 @@ export class FeedModel {
feedArticle.id; feedArticle.id;
// Fix relative URLs in the feeds // Fix relative URLs in the feeds
if (url.startsWith('/')) { if (url!.startsWith('/')) {
const feedUrl = new URL(newsFeed.feed_url); const feedUrl = new URL(newsFeed.feed_url);
url = `${feedUrl.origin}${url}`; url = `${feedUrl.origin}${url}`;
} }

View File

@@ -1,6 +1,6 @@
import { decodeBase64Url } from 'std/encoding/base64url.ts'; import { decodeBase64Url } from '@std/encoding';
import * as openIdClient from 'openid-client'; import * as openIdClient from 'openid-client';
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
import { createSessionResponse, dataToText } from '/lib/auth.ts'; import { createSessionResponse, dataToText } from '/lib/auth.ts';
import { UserModel } from '/lib/models/user.ts'; import { UserModel } from '/lib/models/user.ts';

View File

@@ -156,7 +156,7 @@ export type OptionalApp = 'news' | 'notes' | 'photos' | 'expenses' | 'contacts'
export interface Config { export interface Config {
auth: { auth: {
/** The base URL of the application you use to access the app, i.e. "http://localhost:8000" or "https://cloud.example.com" */ /** The base URL of the application you use to access the app, i.e. "http://localhost:8000" or "https://cloud.example.com" (note authentication won't work without https:// except for localhost; SSO redirect, if enabled, will be this + /oidc/callback, so "https://cloud.example.com/oidc/callback") */
baseUrl: string; baseUrl: string;
/** If true, anyone can sign up for an account. Note that it's always possible to sign up for the first user, and they will be an admin */ /** If true, anyone can sign up for an account. Note that it's always possible to sign up for the first user, and they will be an admin */
allowSignups: boolean; allowSignups: boolean;

View File

@@ -1,5 +1,4 @@
import { assertEquals } from 'std/assert/assert_equals.ts'; import { assertEquals, assertMatch } from '@std/assert';
import { assertMatch } from 'std/assert/assert_match.ts';
import { Calendar, CalendarEvent } from '/lib/models/calendar.ts'; import { Calendar, CalendarEvent } from '/lib/models/calendar.ts';
import { import {

View File

@@ -1,5 +1,4 @@
import { assertEquals } from 'std/assert/assert_equals.ts'; import { assertEquals, assertMatch } from '@std/assert';
import { assertMatch } from 'std/assert/assert_match.ts';
import { generateVCard, getIdFromVCard, parseVCard, splitTextIntoVCards, updateVCard } from './contacts.ts'; import { generateVCard, getIdFromVCard, parseVCard, splitTextIntoVCards, updateVCard } from './contacts.ts';

View File

@@ -1,4 +1,5 @@
import { assertEquals } from 'std/assert/assert_equals.ts'; import { assertEquals } from '@std/assert';
import { humanFileSize } from './files.ts'; import { humanFileSize } from './files.ts';
Deno.test('that humanFileSize works', () => { Deno.test('that humanFileSize works', () => {

View File

@@ -1,4 +1,4 @@
import { assertEquals } from 'std/assert/assert_equals.ts'; import { assertEquals } from '@std/assert';
import { SupportedCurrencySymbol } from '/lib/types.ts'; import { SupportedCurrencySymbol } from '/lib/types.ts';
import { import {

View File

@@ -1,4 +1,4 @@
import { join } from 'std/path/join.ts'; import { join } from '@std/path';
import { lookup } from 'mrmime'; import { lookup } from 'mrmime';
export function getProperDestinationPath(url: string) { export function getProperDestinationPath(url: string) {

View File

@@ -4,7 +4,7 @@
/// <reference lib="dom.asynciterable" /> /// <reference lib="dom.asynciterable" />
/// <reference lib="deno.ns" /> /// <reference lib="deno.ns" />
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
import { start } from 'fresh/server.ts'; import { start } from 'fresh/server.ts';
import manifest from './fresh.gen.ts'; import manifest from './fresh.gen.ts';

View File

@@ -1,5 +1,4 @@
import { assert } from 'std/assert/assert.ts'; import { assertEquals } from '@std/assert';
import { assertEquals } from 'std/assert/assert_equals.ts';
import { createHandler, ServeHandlerInfo } from 'fresh/server.ts'; import { createHandler, ServeHandlerInfo } from 'fresh/server.ts';
import manifest from './fresh.gen.ts'; import manifest from './fresh.gen.ts';
@@ -21,14 +20,14 @@ Deno.test('Basic routes', async (testContext) => {
await testContext.step('#2 GET /login', async () => { await testContext.step('#2 GET /login', async () => {
const response = await handler(new Request('http://127.0.0.1/login'), CONN_INFO); const response = await handler(new Request('http://127.0.0.1/login'), CONN_INFO);
const text = await response.text(); const text = await response.text();
assert(text.includes('bewCloud')); assertEquals(text.includes('bewCloud'), true);
assertEquals(response.status, 200); assertEquals(response.status, 200);
}); });
await testContext.step('#3 GET /blah', async () => { await testContext.step('#3 GET /blah', async () => {
const response = await handler(new Request('http://127.0.0.1/blah'), CONN_INFO); const response = await handler(new Request('http://127.0.0.1/blah'), CONN_INFO);
const text = await response.text(); const text = await response.text();
assert(text.includes('404 - Page not found')); assertEquals(text.includes('404 - Page not found'), true);
assertEquals(response.status, 404); assertEquals(response.status, 404);
}); });
@@ -41,7 +40,7 @@ Deno.test('Basic routes', async (testContext) => {
}); });
const response = await handler(request, CONN_INFO); const response = await handler(request, CONN_INFO);
const text = await response.text(); const text = await response.text();
assert(text.includes('Error: Password is too short')); assertEquals(text.includes('Error: Password is too short'), true);
assertEquals(response.status, 200); assertEquals(response.status, 200);
}); });
}); });

View File

@@ -1,4 +1,4 @@
import 'std/dotenv/load.ts'; import '@std/dotenv/load';
import Database, { sql } from '/lib/interfaces/database.ts'; import Database, { sql } from '/lib/interfaces/database.ts';

View File

@@ -1,11 +1,6 @@
import { Head } from 'fresh/runtime.ts'; import { Head } from 'fresh/runtime.ts';
import { PageProps } from 'fresh/server.ts';
import { FreshContextState } from '/lib/types.ts'; export default function Error404() {
interface Data {}
export default function Error404({ state }: PageProps<Data, FreshContextState>) {
return ( return (
<> <>
<Head> <Head>
@@ -13,20 +8,6 @@ export default function Error404({ state }: PageProps<Data, FreshContextState>)
</Head> </Head>
<main> <main>
<section class='max-w-screen-md mx-auto flex flex-col items-center justify-center'> <section class='max-w-screen-md mx-auto flex flex-col items-center justify-center'>
{!state.user
? (
<>
<img
class='my-6'
src='/images/logo-white.svg'
width='250'
height='50'
alt='the bewCloud logo: a stylized logo'
/>
<h1>404 - Page not found</h1>
</>
)
: null}
<p class='my-4'> <p class='my-4'>
The page you were looking for doesn't exist. The page you were looking for doesn't exist.
</p> </p>

View File

@@ -1,6 +1,6 @@
import { Handler, RouteConfig } from 'fresh/server.ts'; import { Handler, RouteConfig } from 'fresh/server.ts';
import { join } from 'std/path/join.ts'; import { join } from '@std/path';
import { parse, stringify } from 'xml'; import { parse, stringify } from '@libs/xml';
import { FreshContextState } from '/lib/types.ts'; import { FreshContextState } from '/lib/types.ts';
import { AppConfig } from '/lib/config.ts'; import { AppConfig } from '/lib/config.ts';
@@ -59,7 +59,7 @@ export const handler: Handler<Data, FreshContextState> = async (request, context
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });
} }
return new Response(fileResult.contents!, { return new Response(fileResult.contents! as BodyInit, {
status: 200, status: 200,
headers: { headers: {
'cache-control': 'no-cache, no-store, must-revalidate', 'cache-control': 'no-cache, no-store, must-revalidate',

View File

@@ -1,5 +1,5 @@
import { Handlers, PageProps } from 'fresh/server.ts'; import { Handlers, PageProps } from 'fresh/server.ts';
import { join } from 'std/path/join.ts'; import { join } from '@std/path';
import { Directory, DirectoryFile, FreshContextState } from '/lib/types.ts'; import { Directory, DirectoryFile, FreshContextState } from '/lib/types.ts';
import { import {

View File

@@ -1,5 +1,5 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { join } from 'std/path/join.ts'; import { join } from '@std/path';
import { FreshContextState } from '/lib/types.ts'; import { FreshContextState } from '/lib/types.ts';
import { ensureFileSharePathIsValidAndSecurelyAccessible, FileModel, FileShareModel } from '/lib/models/files.ts'; import { ensureFileSharePathIsValidAndSecurelyAccessible, FileModel, FileShareModel } from '/lib/models/files.ts';
@@ -70,7 +70,7 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });
} }
return new Response(fileResult.contents!, { return new Response(fileResult.contents! as BodyInit, {
status: 200, status: 200,
headers: { headers: {
'cache-control': 'no-cache, no-store, must-revalidate', 'cache-control': 'no-cache, no-store, must-revalidate',

View File

@@ -37,7 +37,7 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Not Found', { status: 404 }); return new Response('Not Found', { status: 404 });
} }
return new Response(fileResult.contents!, { return new Response(fileResult.contents! as BodyInit, {
status: 200, status: 200,
headers: { headers: {
'cache-control': 'no-cache, no-store, must-revalidate', 'cache-control': 'no-cache, no-store, must-revalidate',

View File

@@ -1,5 +1,5 @@
import { Handlers } from 'fresh/server.ts'; import { Handlers } from 'fresh/server.ts';
import { resize } from 'https://deno.land/x/deno_image@0.0.4/mod.ts'; import sharp from 'sharp';
import { FreshContextState } from '/lib/types.ts'; import { FreshContextState } from '/lib/types.ts';
import { FileModel } from '/lib/models/files.ts'; import { FileModel } from '/lib/models/files.ts';
@@ -53,15 +53,36 @@ export const handler: Handlers<Data, FreshContextState> = {
return new Response('Bad Request', { status: 400 }); return new Response('Bad Request', { status: 400 });
} }
const resizedImageContents = await resize(fileResult.contents!, { width, height, aspectRatio: true }); try {
const image = sharp(fileResult.contents! as unknown as ArrayBuffer).resize({
width,
height,
fit: 'cover',
background: { r: 0, g: 0, b: 0, alpha: 0 },
}).png();
return new Response(resizedImageContents, { const resizedImageContents = await image.toBuffer();
status: 200,
headers: { return new Response(Uint8Array.from(resizedImageContents), {
'cache-control': `max-age=${604_800}`, // Tell browsers to cache for 1 week (60 * 60 * 24 * 7 = 604_800) status: 200,
'content-type': fileResult.contentType!, headers: {
'content-length': resizedImageContents.byteLength.toString(), 'cache-control': `max-age=${604_800}`, // Tell browsers to cache for 1 week (60 * 60 * 24 * 7 = 604_800)
}, 'content-type': fileResult.contentType!,
}); 'content-length': resizedImageContents.byteLength.toString(),
},
});
} catch (error) {
console.error(error);
// Serve original if we can't make a thumbnail
return new Response(fileResult.contents! as BodyInit, {
status: 200,
headers: {
'cache-control': `max-age=${604_800}`, // Tell browsers to cache for 1 week (60 * 60 * 24 * 7 = 604_800)
'content-type': fileResult.contentType!,
'content-length': fileResult.contents!.byteLength.toString(),
},
});
}
}, },
}; };