Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ ADMIN_EMAIL=

USER_DATA_TURNSTILE_SECRET_KEY=
PUBLIC_USER_DATA_TURNSTILE_SITEKEY=

ORG_REQUEST_TURNSTILE_SECRET_KEY=
PUBLIC_ORG_REQUEST_TURNSTILE_SITEKEY=
```

> **Note:** Contact [@sillsdev/scriptoria-developers](https://github.com/orgs/sillsdev/teams/scriptoria-developers/members) for help obtaining the secret values.
Expand Down
3 changes: 2 additions & 1 deletion src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { checkInviteErrors } from '$lib/organizationInvites';
import { localizeHref } from '$lib/paraglide/runtime';
import { RoleId } from '$lib/prisma';
import { DatabaseReads, DatabaseWrites } from '$lib/server/database';
import { stringifyError } from '$lib/utils';

declare module '@auth/sveltekit' {
interface Session {
Expand Down Expand Up @@ -332,7 +333,7 @@ export const populateSecurityInfo: Handle = async ({ event, resolve }) => {
// Suppress auth failures but log for debugging
trace.getActiveSpan()?.addEvent('API auth failed', {
'auth.hasToken': !!authToken,
'auth.validationError': e instanceof Error ? e.message : String(e)
'auth.validationError': stringifyError(e)
});
}
}
Expand Down
8 changes: 5 additions & 3 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ import { QueueConnected, getQueues } from '$lib/server/bullmq';
import { bullboardHandle } from '$lib/server/bullmq/BullBoard';
import { allWorkers } from '$lib/server/bullmq/BullMQ';
import { DatabaseConnected, DatabaseReads, DatabaseWrites } from '$lib/server/database';
import { stringifyError } from '$lib/utils';
import { logLocalDev } from '$lib/utils/server';

if (!building) {
// Start OTEL collector
Expand Down Expand Up @@ -190,15 +192,15 @@ export const handle: Handle = async ({ event, resolve }) => {
export const handleError: HandleServerError = ({ error, event, status }) => {
// Log the error with OTEL
OTEL.instance.logger.error('Error in handleError', {
error: error instanceof Error ? error.message : String(error),
error: stringifyError(error),
route: event.route.id,
method: event.request.method,
url: event.url.href
});
trace.getActiveSpan()?.recordException(error as Error);
trace.getActiveSpan()?.setStatus({
code: SpanStatusCode.ERROR, // Error
message: error instanceof Error ? error.message : String(error)
message: stringifyError(error)
});

if (status === 404) {
Expand All @@ -209,7 +211,7 @@ export const handleError: HandleServerError = ({ error, event, status }) => {
};
}

console.error('Error occurred:', error);
logLocalDev?.('Error occurred:', error);

return {
message: 'An unexpected error occurred. Please try again later.',
Expand Down
22 changes: 20 additions & 2 deletions src/lib/components/settings/SubmitButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,32 @@
import IconButton, { type IconButtonProps } from './IconButton.svelte';
import { Icons } from '$lib/icons';

interface Props extends IconButtonProps {
waiting?: boolean;
}

let {
class: classes,
icon = Icons.Save,
disabled = false,
key = 'common_save',
waiting = false,
children,
// eslint-disable-next-line svelte/valid-compile
...rest
}: IconButtonProps = $props();
}: Props = $props();
</script>

<IconButton type="submit" class={['btn-primary', classes]} {icon} {key} {disabled} {...rest} />
<IconButton
type="submit"
class={['btn-primary', classes]}
{icon}
{key}
disabled={disabled || waiting}
{...rest}
children={waiting ? loading : children}
/>

{#snippet loading()}
<span class="loading loading-spinner"></span>
{/snippet}
6 changes: 6 additions & 0 deletions src/lib/locales/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"common_or": "or",
"common_passThrough": "{value}",
"common_type": "Type",
"common_expires": "Expires",
"models_add": "Add {name}",
"models_edit": "Edit {name}",
"models_save": "Save {name}",
Expand Down Expand Up @@ -164,6 +165,9 @@
"invitations_orgSubmit": "Add Organization",
"invitations_requestOrgInvite": "Request Organization Invite",
"invitations_orgAdminEmail": "Organization Admin Email",
"invitations_ourUsers": "Our Users",
"invitations_verifyUser": "Before submitting a request, please verify that your organization is not already a user of Scriptoria.",
"invitations_verifyWebsite": "Scriptoria ran into an issue verifying that your website exists.",
"newOrganization_title": "Add organization",
"org_title": "Organizations",
"org_add": "Add Organization",
Expand Down Expand Up @@ -213,6 +217,8 @@
"org_accessToken": "Build Engine API Access Token",
"org_emptyBuildEngineURL": "A URL must be provided when not using the Default Build Engine",
"org_emptyAccessToken": "An API token must be provided when not using the Default Build Engine",
"org_visible": "Visible to Public",
"org_visibleWarning": "Warning: if your organization is visible to the public, all visitors of Scriptoria, including non-users, will be able to see your name, contact, and logo!",
"products_definition": "Useful files that result from a series of steps.",
"products_storeSelect": "Select a store for {name}",
"products_noStoresAvailable": "There are no stores available for the selected product. Please contact your organization administrator.",
Expand Down
6 changes: 6 additions & 0 deletions src/lib/locales/es-419.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"common_or": "o",
"common_passThrough": "{value}",
"common_type": "Tipo",
"common_expires": "Vence",
"models_add": "Añadir {name}",
"models_edit": "Editar {name}",
"models_save": "Guardar {name}",
Expand Down Expand Up @@ -164,6 +165,9 @@
"invitations_orgSubmit": "Agregar organización",
"invitations_requestOrgInvite": "Solicitar invitación de la organización",
"invitations_orgAdminEmail": "Correo electrónico del administrador",
"invitations_ourUsers": "Nuestras Usuarias",
Comment thread
FyreByrd marked this conversation as resolved.
"invitations_verifyUser": "Antes de enviar una solicitud, por favor verifique que su organización no sea ya usuaria de Scriptoria.",
"invitations_verifyWebsite": "Scriptoria tuvo problemas para verificar que tu sitio web existe.",
Comment thread
FyreByrd marked this conversation as resolved.
"newOrganization_title": "Agregar organización",
"org_title": "Organizaciones",
"org_add": "Añadir organización",
Expand Down Expand Up @@ -213,6 +217,8 @@
"org_accessToken": "Token de acceso del motor de construcción",
"org_emptyBuildEngineURL": "Se debe proporcionar una URL cuando no se utiliza el motor de compilación predeterminado",
"org_emptyAccessToken": "Se debe proporcionar un token de API cuando no se utiliza el motor de compilación predeterminado",
"org_visible": "Visible para el público",
"org_visibleWarning": "Advertencia: si su organización es visible para el público, todos los visitantes de Scriptoria —incluidas las personas que no son usuarias— podrán ver su nombre, datos de contacto y logotipo.",
"products_definition": "Archivos útiles que resultan de una serie de pasos.",
"products_storeSelect": "Seleccione una tienda para {name}",
"products_noStoresAvailable": "No hay tiendas disponibles para el producto seleccionado. Por favor, contacte con el administrador de su organización.",
Expand Down
6 changes: 6 additions & 0 deletions src/lib/locales/fr-FR.json
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@
"common_or": "o",
"common_passThrough": "{value}",
"common_type": "Type",
"common_expires": "Expires",
Comment thread
FyreByrd marked this conversation as resolved.
Comment thread
FyreByrd marked this conversation as resolved.
"models_add": "Add {name}",
"models_edit": "Edit {name}",
"models_save": "Save {name}",
Expand Down Expand Up @@ -164,6 +165,9 @@
"invitations_orgSubmit": "Add Organization",
"invitations_requestOrgInvite": "Request Organization Invite",
"invitations_orgAdminEmail": "Organization Admin Email",
"invitations_ourUsers": "Our Users",
"invitations_verifyUser": "Before submitting a request, please verify that your organization is not already a user of Scriptoria.",
"invitations_verifyWebsite": "Scriptoria ran into an issue verifying that your website exists.",
"newOrganization_title": "Add organization",
"org_title": "Organizations",
"org_add": "Add Organization",
Expand Down Expand Up @@ -213,6 +217,8 @@
"org_accessToken": "Build Engine API Access Token",
"org_emptyBuildEngineURL": "A URL must be provided when not using the Default Build Engine",
"org_emptyAccessToken": "An API token must be provided when not using the Default Build Engine",
"org_visible": "Visible to Public",
"org_visibleWarning": "Warning: if your organization is visible to the public, all visitors of Scriptoria, including non-users, will be able to see your name, contact, and logo!",
"products_definition": "Useful files that result from a series of steps.",
"products_storeSelect": "Select a store for {name}",
"products_noStoresAvailable": "There are no stores available for the selected product. Please contact your organization administrator.",
Expand Down
3 changes: 2 additions & 1 deletion src/lib/organizations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ export const infoSchema = v.object({
),
v.pipe(v.string(), v.email())
])
)
),
visibleToPublic: v.boolean()
Comment thread
FyreByrd marked this conversation as resolved.
});

export const infrastructureSchema = v.object({
Expand Down
6 changes: 3 additions & 3 deletions src/lib/otel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { LoggerProvider, SimpleLogRecordProcessor } from '@opentelemetry/sdk-log
import { PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
import { NodeSDK } from '@opentelemetry/sdk-node';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
import { inLocalDevelopment } from '$lib/utils/server';

class Logger {
constructor(
Expand Down Expand Up @@ -60,8 +61,7 @@ export default class OTEL {
private _logger: Logger;

private constructor() {
const isDev = process.env.NODE_ENV === 'development';
const endpoint = `http://${isDev ? 'localhost' : 'otel'}:6317`;
const endpoint = `http://${inLocalDevelopment ? 'localhost' : 'otel'}:6317`;

const resource = resourceFromAttributes({
[ATTR_SERVICE_NAME]: 'scriptoria',
Expand All @@ -79,7 +79,7 @@ export default class OTEL {
resource,
processors: [logProcessor]
}).getLogger('scriptoria-logger'),
isDev
inLocalDevelopment
);

this.sdk = new NodeSDK({
Expand Down
2 changes: 2 additions & 0 deletions src/lib/prisma/migrations/41_public_orgs/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Organizations" ADD COLUMN "VisibleToPublic" BOOLEAN NOT NULL DEFAULT false;
1 change: 1 addition & 0 deletions src/lib/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ model Organizations {
UseDefaultBuildEngine Boolean @default(true)
PublicByDefault Boolean @default(true)
ContactEmail String?
VisibleToPublic Boolean @default(false)
Groups Groups[]
OrganizationMembershipInvites OrganizationMembershipInvites[]
Users Users[]
Expand Down
3 changes: 2 additions & 1 deletion src/lib/projects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Session } from '@auth/sveltekit';
import type { Prisma } from '@prisma/client';
import * as v from 'valibot';
import { RoleId } from '$lib/prisma';
import { stringifyError } from '$lib/utils';
import { isAdminForOrg } from '$lib/utils/roles';
import { idSchema, langtagRegex, paginateSchema, requiredString } from '$lib/valibot';

Expand Down Expand Up @@ -165,7 +166,7 @@ export const importJSONSchema = v.pipe(
return JSON.parse(dataset.value || '{}');
} catch (e) {
addIssue({
message: e instanceof Error ? e.message : String(e),
message: stringifyError(e),
path: [
{
type: 'unknown',
Expand Down
3 changes: 2 additions & 1 deletion src/lib/server/build-engine-api/requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { DatabaseReads } from '../database/prisma';
import * as Types from './types';
import { env } from '$env/dynamic/private';
import { activeSystems } from '$lib/organizations/server';
import { stringifyError } from '$lib/utils';

const tracer = trace.getTracer('build-engine-api');

Expand Down Expand Up @@ -97,7 +98,7 @@ export async function request(resource: string, auth: Types.Auth, opts?: Types.R
name: '',
status: 500,
code: 500,
message: typeof e === 'string' ? e.toUpperCase() : e instanceof Error ? e.message : e,
message: stringifyError(e),
type: ''
} as Types.ErrorResponse
};
Expand Down
3 changes: 2 additions & 1 deletion src/lib/server/bullmq/BullWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getQueues, getWorkerConfig } from './queues';
import * as BullMQ from './types';
import { building } from '$app/environment';
import { SSEPageUpdates } from '$lib/projects/listener';
import { logLocalDev } from '$lib/utils/server';

const tracer = trace.getTracer('BullWorker');

Expand Down Expand Up @@ -41,7 +42,7 @@ export abstract class BullWorker<T extends BullMQ.Job> {
code: SpanStatusCode.ERROR, // Error
message: (error as Error).message
});
console.error(error);
logLocalDev?.(error);
throw error;
} finally {
span.end();
Expand Down
18 changes: 11 additions & 7 deletions src/lib/server/bullmq/queues.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,15 @@ import type {
} from './types';
import { QueueName } from './types';
import OTEL from '$lib/otel';
import { stringifyError } from '$lib/utils';
import { inLocalDevelopment, logLocalDev } from '$lib/utils/server';

class Connection {
private conn: Redis;
private connected: boolean;
constructor(isQueueConnection = false, keyPrefix?: string) {
this.conn = new Redis({
host: process.env.NODE_ENV === 'development' ? 'localhost' : process.env.VALKEY_HOST,
host: inLocalDevelopment ? 'localhost' : process.env.VALKEY_HOST,
maxRetriesPerRequest: isQueueConnection ? undefined : null,
keyPrefix
});
Expand All @@ -49,10 +51,10 @@ class Connection {
});
this.connected = false;
if (err.message.includes('ENOTFOUND')) {
console.error('Fatal Valkey connection', err);
logLocalDev?.('Fatal Valkey connection', err);
process.exit(1);
} else if (!err.message.includes('ECONNREFUSED')) {
console.error('Valkey connection error', err);
logLocalDev?.('Valkey connection error', err);
}
});
setInterval(() => {
Expand All @@ -64,8 +66,8 @@ class Connection {
})
.catch((err) => {
if (this.connected) {
console.error(err);
console.log('Valkey disconnected');
logLocalDev?.(err);
logLocalDev?.('Valkey disconnected');
this.connected = false;
OTEL.instance.logger.error('Valkey disconnected', {
error: err.message,
Expand Down Expand Up @@ -149,7 +151,8 @@ async function createJobRecord(job: Job<BaseJob>) {
}
if (!found) {
job.log('Error recovering transition. No job record created.');
console.error(`Error recovering transition ${job.data.transition}`);
logLocalDev?.(`Error recovering transition ${job.data.transition}`);
OTEL.instance.logger.error(`Error recovering transition ${job.data.transition}`);
return;
} else {
job.log(`Transition ${job.data.transition} not found. Replacing with ${found}`);
Expand All @@ -171,7 +174,8 @@ async function createJobRecord(job: Job<BaseJob>) {
}
} catch (e) {
job.log(`Error creating job records: ${e}`);
console.error(e);
logLocalDev?.(e);
OTEL.instance.logger.error(`Error creating job records`, { error: stringifyError(e) });
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/lib/server/database/Products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BullMQ, getQueues } from '../bullmq/index';
import { delete as deleteInstance } from './WorkflowInstances';
import prisma from './prisma';
import type { RequirePrimitive } from './utility';
import { logLocalDev } from '$lib/utils/server';
import { WorkflowState } from '$lib/workflowTypes';

export async function create(
Expand Down Expand Up @@ -290,9 +291,7 @@ async function validateProductBase(
'product.product-definition-allowed': productInOrg,
'product.project-type-allowed': projectTypeAllowed
};
if (process.env.NODE_ENV === 'development') {
console.log(log);
}
logLocalDev?.(log);
span.addEvent(msg, log);

span.recordException(new Error(msg));
Expand Down
3 changes: 2 additions & 1 deletion src/lib/server/database/prisma.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Prisma, PrismaClient } from '@prisma/client';
import { ReadonlyClient } from './ReadonlyPrisma';
import OTEL from '$lib/otel';
import { logLocalDev } from '$lib/utils/server';

// This is the home of all database operations through prisma
// It is used from both the node-server package (which runs tasks) and from the sveltekit
Expand Down Expand Up @@ -48,7 +49,7 @@ class ConnectionChecker {
error: e.message
});
this.connected = false;
console.log('Error checking database connection:', e);
logLocalDev?.('Error checking database connection:', e);
}
} else {
throw e;
Expand Down
5 changes: 2 additions & 3 deletions src/lib/server/email-service/EmailClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ import {
} from './EmailTemplates';
import { building } from '$app/environment';
import { RoleId } from '$lib/prisma';
import { inLocalDevelopment } from '$lib/utils/server';

const EMAIL_NAME =
process.env.ADMIN_NAME ??
'Scriptoria' + (process.env.NODE_ENV === 'development' ? ' Staging' : '');
const EMAIL_NAME = process.env.ADMIN_NAME ?? 'Scriptoria' + (inLocalDevelopment ? ' Staging' : '');
const ADMIN_EMAIL = process.env.ADMIN_EMAIL || '<no-email>';
let transporter: Transporter | null = null;
if (!building) {
Expand Down
Loading