scheduling functions now works
This commit is contained in:
32
packages/backend/convex/_generated/api.d.ts
vendored
32
packages/backend/convex/_generated/api.d.ts
vendored
@@ -12,16 +12,16 @@ import type {
|
||||
ApiFromModules,
|
||||
FilterApi,
|
||||
FunctionReference,
|
||||
} from 'convex/server';
|
||||
import type * as auth from '../auth.js';
|
||||
import type * as crons from '../crons.js';
|
||||
import type * as custom_auth_index from '../custom/auth/index.js';
|
||||
import type * as custom_auth_password_validate from '../custom/auth/password/validate.js';
|
||||
import type * as custom_auth_providers_entra from '../custom/auth/providers/entra.js';
|
||||
import type * as custom_auth_providers_password from '../custom/auth/providers/password.js';
|
||||
import type * as files from '../files.js';
|
||||
import type * as http from '../http.js';
|
||||
import type * as statuses from '../statuses.js';
|
||||
} from "convex/server";
|
||||
import type * as auth from "../auth.js";
|
||||
import type * as crons from "../crons.js";
|
||||
import type * as custom_auth_index from "../custom/auth/index.js";
|
||||
import type * as custom_auth_password_validate from "../custom/auth/password/validate.js";
|
||||
import type * as custom_auth_providers_entra from "../custom/auth/providers/entra.js";
|
||||
import type * as custom_auth_providers_password from "../custom/auth/providers/password.js";
|
||||
import type * as files from "../files.js";
|
||||
import type * as http from "../http.js";
|
||||
import type * as statuses from "../statuses.js";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
@@ -34,19 +34,19 @@ import type * as statuses from '../statuses.js';
|
||||
declare const fullApi: ApiFromModules<{
|
||||
auth: typeof auth;
|
||||
crons: typeof crons;
|
||||
'custom/auth/index': typeof custom_auth_index;
|
||||
'custom/auth/password/validate': typeof custom_auth_password_validate;
|
||||
'custom/auth/providers/entra': typeof custom_auth_providers_entra;
|
||||
'custom/auth/providers/password': typeof custom_auth_providers_password;
|
||||
"custom/auth/index": typeof custom_auth_index;
|
||||
"custom/auth/password/validate": typeof custom_auth_password_validate;
|
||||
"custom/auth/providers/entra": typeof custom_auth_providers_entra;
|
||||
"custom/auth/providers/password": typeof custom_auth_providers_password;
|
||||
files: typeof files;
|
||||
http: typeof http;
|
||||
statuses: typeof statuses;
|
||||
}>;
|
||||
export declare const api: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, 'public'>
|
||||
FunctionReference<any, "public">
|
||||
>;
|
||||
export declare const internal: FilterApi<
|
||||
typeof fullApi,
|
||||
FunctionReference<any, 'internal'>
|
||||
FunctionReference<any, "internal">
|
||||
>;
|
||||
|
@@ -8,7 +8,7 @@
|
||||
* @module
|
||||
*/
|
||||
|
||||
import { anyApi } from 'convex/server';
|
||||
import { anyApi } from "convex/server";
|
||||
|
||||
/**
|
||||
* A utility for referencing Convex functions in your app's API.
|
||||
|
@@ -13,9 +13,9 @@ import type {
|
||||
DocumentByName,
|
||||
TableNamesInDataModel,
|
||||
SystemTableNames,
|
||||
} from 'convex/server';
|
||||
import type { GenericId } from 'convex/values';
|
||||
import schema from '../schema.js';
|
||||
} from "convex/server";
|
||||
import type { GenericId } from "convex/values";
|
||||
import schema from "../schema.js";
|
||||
|
||||
/**
|
||||
* The names of all of your Convex tables.
|
||||
|
16
packages/backend/convex/_generated/server.d.ts
vendored
16
packages/backend/convex/_generated/server.d.ts
vendored
@@ -18,8 +18,8 @@ import {
|
||||
GenericQueryCtx,
|
||||
GenericDatabaseReader,
|
||||
GenericDatabaseWriter,
|
||||
} from 'convex/server';
|
||||
import type { DataModel } from './dataModel.js';
|
||||
} from "convex/server";
|
||||
import type { DataModel } from "./dataModel.js";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
@@ -29,7 +29,7 @@ import type { DataModel } from './dataModel.js';
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const query: QueryBuilder<DataModel, 'public'>;
|
||||
export declare const query: QueryBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a query that is only accessible from other Convex functions (but not from the client).
|
||||
@@ -39,7 +39,7 @@ export declare const query: QueryBuilder<DataModel, 'public'>;
|
||||
* @param func - The query function. It receives a {@link QueryCtx} as its first argument.
|
||||
* @returns The wrapped query. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalQuery: QueryBuilder<DataModel, 'internal'>;
|
||||
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define a mutation in this Convex app's public API.
|
||||
@@ -49,7 +49,7 @@ export declare const internalQuery: QueryBuilder<DataModel, 'internal'>;
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const mutation: MutationBuilder<DataModel, 'public'>;
|
||||
export declare const mutation: MutationBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define a mutation that is only accessible from other Convex functions (but not from the client).
|
||||
@@ -59,7 +59,7 @@ export declare const mutation: MutationBuilder<DataModel, 'public'>;
|
||||
* @param func - The mutation function. It receives a {@link MutationCtx} as its first argument.
|
||||
* @returns The wrapped mutation. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalMutation: MutationBuilder<DataModel, 'internal'>;
|
||||
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an action in this Convex app's public API.
|
||||
@@ -72,7 +72,7 @@ export declare const internalMutation: MutationBuilder<DataModel, 'internal'>;
|
||||
* @param func - The action. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped action. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const action: ActionBuilder<DataModel, 'public'>;
|
||||
export declare const action: ActionBuilder<DataModel, "public">;
|
||||
|
||||
/**
|
||||
* Define an action that is only accessible from other Convex functions (but not from the client).
|
||||
@@ -80,7 +80,7 @@ export declare const action: ActionBuilder<DataModel, 'public'>;
|
||||
* @param func - The function. It receives an {@link ActionCtx} as its first argument.
|
||||
* @returns The wrapped function. Include this as an `export` to name it and make it accessible.
|
||||
*/
|
||||
export declare const internalAction: ActionBuilder<DataModel, 'internal'>;
|
||||
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
||||
|
||||
/**
|
||||
* Define an HTTP action.
|
||||
|
@@ -16,7 +16,7 @@ import {
|
||||
internalActionGeneric,
|
||||
internalMutationGeneric,
|
||||
internalQueryGeneric,
|
||||
} from 'convex/server';
|
||||
} from "convex/server";
|
||||
|
||||
/**
|
||||
* Define a query in this Convex app's public API.
|
||||
|
@@ -12,7 +12,6 @@ import type { Doc, Id } from './_generated/dataModel';
|
||||
import { paginationOptsValidator } from 'convex/server';
|
||||
|
||||
type RWCtx = MutationCtx | QueryCtx;
|
||||
|
||||
type StatusRow = {
|
||||
user: {
|
||||
id: Id<'users'>;
|
||||
@@ -34,28 +33,24 @@ type Paginated<T> = {
|
||||
continueCursor: string | null;
|
||||
};
|
||||
|
||||
// CHANGED: typed helpers
|
||||
const ensureUser = async (ctx: RWCtx, userId: Id<'users'>) => {
|
||||
const user = await ctx.db.get(userId);
|
||||
if (!user) throw new ConvexError('User not found.');
|
||||
return user;
|
||||
};
|
||||
|
||||
const latestStatusForOwner = async (ctx: RWCtx, ownerId: Id<'users'>) => {
|
||||
const [latest] = await ctx.db
|
||||
.query('statuses')
|
||||
.withIndex('by_user_updatedAt', (q) => q.eq('userId', ownerId))
|
||||
.order('desc')
|
||||
.take(1);
|
||||
return latest as Doc<'statuses'> | null;
|
||||
const getName = (u: Doc<'users'>): string | null =>
|
||||
'name' in u && typeof u.name === 'string' ? u.name : null;
|
||||
|
||||
const getEmail = (u: Doc<'users'>): string | null =>
|
||||
'email' in u && typeof u.email === 'string' ? u.email : null;
|
||||
|
||||
const getImageId = (u: Doc<'users'>): Id<'_storage'> | null => {
|
||||
if (!('image' in u)) return null;
|
||||
const img = (u as { image?: unknown }).image as string | undefined;
|
||||
return img && img.length > 0 ? (img as Id<'_storage'>) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new status for a single user.
|
||||
* - Defaults userId to the caller.
|
||||
* - updatedBy defaults to the caller.
|
||||
* - Updates the user's currentStatusId pointer.
|
||||
*/
|
||||
export const create = mutation({
|
||||
args: {
|
||||
message: v.string(),
|
||||
@@ -64,12 +59,14 @@ export const create = mutation({
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const authUserId = await getAuthUserId(ctx);
|
||||
if (!authUserId) throw new ConvexError('Not authenticated.');
|
||||
if (!args.userId && !authUserId) {
|
||||
throw new ConvexError('Not authenticated.');
|
||||
}
|
||||
|
||||
const userId = args.userId ?? authUserId!;
|
||||
const updatedBy = args.updatedBy ?? authUserId!;
|
||||
|
||||
const userId = args.userId ?? authUserId;
|
||||
await ensureUser(ctx, userId);
|
||||
|
||||
const updatedBy = args.updatedBy ?? authUserId;
|
||||
await ensureUser(ctx, updatedBy);
|
||||
|
||||
const message = args.message.trim();
|
||||
@@ -85,16 +82,10 @@ export const create = mutation({
|
||||
});
|
||||
|
||||
await ctx.db.patch(userId, { currentStatusId: statusId });
|
||||
|
||||
return { statusId };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Bulk create the same status for many users.
|
||||
* - updatedBy defaults to the caller.
|
||||
* - Updates each user's currentStatusId pointer.
|
||||
*/
|
||||
export const bulkCreate = mutation({
|
||||
args: {
|
||||
message: v.string(),
|
||||
@@ -118,91 +109,94 @@ export const bulkCreate = mutation({
|
||||
const statusIds: Id<'statuses'>[] = [];
|
||||
const now = Date.now();
|
||||
|
||||
// Sequential to keep load predictable; switch to Promise.all
|
||||
// if your ownerIds lists are small and bounded.
|
||||
for (const userId of args.userIds) {
|
||||
await ensureUser(ctx, userId);
|
||||
|
||||
const statusId = await ctx.db.insert('statuses', {
|
||||
message,
|
||||
userId,
|
||||
updatedBy,
|
||||
updatedAt: now,
|
||||
});
|
||||
|
||||
await ctx.db.patch(userId, { currentStatusId: statusId });
|
||||
statusIds.push(statusId);
|
||||
}
|
||||
|
||||
return { statusIds };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Update all statuses for all users.
|
||||
*/
|
||||
// Update all users - simplified
|
||||
export const updateAllStatuses = mutation({
|
||||
args: { message: v.string() },
|
||||
handler: async (ctx, args) => {
|
||||
const userIds = await ctx.runQuery(api.auth.getAllUserIds);
|
||||
const updatedAt = Date.now();
|
||||
const users = await ctx.db.query('users').collect();
|
||||
const message = args.message.trim();
|
||||
if (message.length === 0) {
|
||||
throw new ConvexError('Message cannot be empty.');
|
||||
}
|
||||
|
||||
const statusIds: Id<'statuses'>[] = [];
|
||||
for (const userId of userIds) {
|
||||
await ensureUser(ctx, userId);
|
||||
const now = Date.now();
|
||||
|
||||
for (const user of users) {
|
||||
const statusId = await ctx.db.insert('statuses', {
|
||||
message: args.message,
|
||||
userId,
|
||||
updatedAt,
|
||||
message,
|
||||
userId: user._id,
|
||||
updatedAt: now,
|
||||
});
|
||||
await ctx.db.patch(userId, { currentStatusId: statusId });
|
||||
await ctx.db.patch(user._id, { currentStatusId: statusId });
|
||||
statusIds.push(statusId);
|
||||
}
|
||||
|
||||
return { statusIds };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Current status for a specific user.
|
||||
* - Uses users.currentStatusId if present,
|
||||
* otherwise falls back to latest by index.
|
||||
*/
|
||||
// Lunch status with automatic return - this should be an action
|
||||
export const createLunchStatus = mutation({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const authUserId = await getAuthUserId(ctx);
|
||||
if (!authUserId) throw new ConvexError('Not authenticated.');
|
||||
// Create lunch status
|
||||
await ctx.runMutation(api.statuses.create, {
|
||||
message: 'At lunch',
|
||||
userId: authUserId
|
||||
});
|
||||
const oneHour = 60 * 60 * 1000;
|
||||
console.log('Scheduling return to desk after 1 hour');
|
||||
await ctx.scheduler.runAfter(oneHour, api.statuses.create, {
|
||||
message: 'At desk',
|
||||
userId: authUserId
|
||||
});
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
export const getCurrentForUser = query({
|
||||
args: { userId: v.id('users') },
|
||||
handler: async (ctx, { userId }) => {
|
||||
const user = await ensureUser(ctx, userId);
|
||||
|
||||
if (user.currentStatusId) {
|
||||
const status = await ctx.db.get(user.currentStatusId);
|
||||
if (status) return status;
|
||||
}
|
||||
const [latest] = await ctx.db
|
||||
.query('statuses')
|
||||
.withIndex('by_user_updatedAt', (q) => q.eq('userId', userId))
|
||||
.order('desc')
|
||||
.take(1);
|
||||
|
||||
return await latestStatusForOwner(ctx, userId);
|
||||
return latest ?? null;
|
||||
},
|
||||
});
|
||||
|
||||
const getName = (u: Doc<'users'>): string | null =>
|
||||
'name' in u && typeof u.name === 'string' ? u.name : null;
|
||||
const getEmail = (u: Doc<'users'>): string | null =>
|
||||
'email' in u && typeof u.email === 'string' ? u.email : null;
|
||||
|
||||
const getImageId = (u: Doc<'users'>): Id<'_storage'> | null => {
|
||||
if (!('image' in u)) return null;
|
||||
const img = (u as { image?: unknown }).image as string | undefined;
|
||||
return img && img.length > 0 ? (img as Id<'_storage'>) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Current statuses for all users.
|
||||
* - Reads each user's currentStatusId pointer.
|
||||
* - Falls back to latest-by-index if pointer is missing.
|
||||
*/
|
||||
export const getCurrentForAll = query({
|
||||
args: {},
|
||||
handler: async (ctx): Promise<StatusRow[]> => {
|
||||
const users = await ctx.db.query('users').collect();
|
||||
|
||||
return await Promise.all(
|
||||
users.map(async (u) => {
|
||||
// Resolve user's current or latest status
|
||||
let curStatus: Doc<'statuses'> | null = null;
|
||||
if ('currentStatusId' in u && u.currentStatusId) {
|
||||
curStatus = await ctx.db.get(u.currentStatusId);
|
||||
@@ -215,30 +209,26 @@ export const getCurrentForAll = query({
|
||||
.take(1);
|
||||
curStatus = latest ?? null;
|
||||
}
|
||||
|
||||
// User display + URL
|
||||
const userImageId = getImageId(u);
|
||||
const userImageUrl = userImageId
|
||||
? await ctx.storage.getUrl(userImageId)
|
||||
: null;
|
||||
|
||||
// Updated by (if different) + URL
|
||||
let updatedByUser: StatusRow['user'] | null = null;
|
||||
if (curStatus && curStatus.updatedBy && curStatus.updatedBy !== u._id) {
|
||||
const updater = await ctx.db.get(curStatus.updatedBy);
|
||||
if (!updater) throw new ConvexError('Updater not found.');
|
||||
const updaterImageId = getImageId(updater);
|
||||
const updaterImageUrl = updaterImageId
|
||||
? await ctx.storage.getUrl(updaterImageId)
|
||||
: null;
|
||||
updatedByUser = {
|
||||
id: updater._id,
|
||||
email: getEmail(updater),
|
||||
name: getName(updater),
|
||||
imageUrl: updaterImageUrl,
|
||||
};
|
||||
if (updater) {
|
||||
const updaterImageId = getImageId(updater);
|
||||
const updaterImageUrl = updaterImageId
|
||||
? await ctx.storage.getUrl(updaterImageId)
|
||||
: null;
|
||||
updatedByUser = {
|
||||
id: updater._id,
|
||||
email: getEmail(updater),
|
||||
name: getName(updater),
|
||||
imageUrl: updaterImageUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const status: StatusRow['status'] = curStatus
|
||||
? {
|
||||
id: curStatus._id,
|
||||
@@ -247,7 +237,6 @@ export const getCurrentForAll = query({
|
||||
updatedBy: updatedByUser,
|
||||
}
|
||||
: null;
|
||||
|
||||
return {
|
||||
user: {
|
||||
id: u._id,
|
||||
@@ -262,19 +251,13 @@ export const getCurrentForAll = query({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Paginated history for all users or for a specific user.
|
||||
*/
|
||||
// Paginated history
|
||||
export const listHistory = query({
|
||||
args: {
|
||||
userId: v.optional(v.id('users')),
|
||||
paginationOpts: paginationOptsValidator,
|
||||
},
|
||||
handler: async (
|
||||
ctx,
|
||||
{ userId, paginationOpts },
|
||||
): Promise<Paginated<StatusRow>> => {
|
||||
// Query statuses newest-first, optionally filtered by user
|
||||
handler: async (ctx, { userId, paginationOpts }): Promise<Paginated<StatusRow>> => {
|
||||
const result = userId
|
||||
? await ctx.db
|
||||
.query('statuses')
|
||||
@@ -282,21 +265,15 @@ export const listHistory = query({
|
||||
.order('desc')
|
||||
.paginate(paginationOpts)
|
||||
: await ctx.db.query('statuses').order('desc').paginate(paginationOpts);
|
||||
|
||||
// Cache user display objects to avoid refetching repeatedly
|
||||
const displayCache = new Map<string, StatusRow['user']>();
|
||||
|
||||
const getDisplay = async (uid: Id<'users'>): Promise<StatusRow['user']> => {
|
||||
const key = uid as unknown as string;
|
||||
const cached = displayCache.get(key);
|
||||
if (cached) return cached;
|
||||
|
||||
const user = await ctx.db.get(uid);
|
||||
if (!user) throw new ConvexError('User not found.');
|
||||
|
||||
const imgId = getImageId(user);
|
||||
const imgUrl = imgId ? await ctx.storage.getUrl(imgId) : null;
|
||||
|
||||
const display: StatusRow['user'] = {
|
||||
id: user._id,
|
||||
email: getEmail(user),
|
||||
@@ -306,7 +283,6 @@ export const listHistory = query({
|
||||
displayCache.set(key, display);
|
||||
return display;
|
||||
};
|
||||
|
||||
const statuses: StatusRow[] = [];
|
||||
for (const s of result.page) {
|
||||
const owner = await getDisplay(s.userId);
|
||||
@@ -314,7 +290,6 @@ export const listHistory = query({
|
||||
s.updatedBy && s.updatedBy !== s.userId
|
||||
? await getDisplay(s.updatedBy)
|
||||
: null;
|
||||
|
||||
statuses.push({
|
||||
user: owner,
|
||||
status: {
|
||||
@@ -325,12 +300,8 @@ export const listHistory = query({
|
||||
},
|
||||
});
|
||||
}
|
||||
const page = statuses.sort(
|
||||
(a, b) => (b.status?.updatedAt ?? 0) - (a.status?.updatedAt ?? 0),
|
||||
);
|
||||
|
||||
return {
|
||||
page,
|
||||
page: statuses,
|
||||
isDone: result.isDone,
|
||||
continueCursor: result.continueCursor,
|
||||
};
|
||||
|
Reference in New Issue
Block a user