Skip to content
Merged
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "lastActiveAt" TIMESTAMP(3);
3 changes: 3 additions & 0 deletions packages/db/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,9 @@ model User {
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// Last time the user performed an authenticated action.
lastActiveAt DateTime?
}

enum AccountPermissionSyncJobStatus {
Expand Down
35 changes: 34 additions & 1 deletion packages/web/src/ee/features/lighthouse/servicePing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,19 @@ export const syncWithLighthouse = async (orgId: number) => {
where: { orgId },
});

const [userCount, repoCount] = await Promise.all([
const now = Date.now();
const DAY_MS = 24 * 60 * 60 * 1000;
const dauCutoff = new Date(now - 1 * DAY_MS);
const wauCutoff = new Date(now - 7 * DAY_MS);
const mauCutoff = new Date(now - 30 * DAY_MS);

const [
userCount,
repoCount,
dauCount,
wauCount,
mauCount,
] = await Promise.all([
__unsafePrisma.userToOrg.count({
where: {
orgId,
Expand All @@ -29,6 +41,24 @@ export const syncWithLighthouse = async (orgId: number) => {
orgId,
},
}),
__unsafePrisma.user.count({
where: {
orgs: { some: { orgId } },
lastActiveAt: { gte: dauCutoff },
},
}),
__unsafePrisma.user.count({
where: {
orgs: { some: { orgId } },
lastActiveAt: { gte: wauCutoff },
},
}),
__unsafePrisma.user.count({
where: {
orgs: { some: { orgId } },
lastActiveAt: { gte: mauCutoff },
},
}),
]);

const activationCode = license?.activationCode
Expand All @@ -40,6 +70,9 @@ export const syncWithLighthouse = async (orgId: number) => {
version: SOURCEBOT_VERSION,
userCount,
repoCount,
dauCount,
wauCount,
mauCount,
deploymentType: inferDeploymentType(),
isTelemetryEnabled: env.SOURCEBOT_TELEMETRY_DISABLED === 'false',
...(activationCode && { activationCode }),
Expand Down
7 changes: 5 additions & 2 deletions packages/web/src/ee/features/lighthouse/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { z } from "zod";
export const servicePingRequestSchema = z.object({
installId: z.string(),
version: z.string(),
userCount: z.number(),
repoCount: z.number(),
userCount: z.number().int().nonnegative(),
repoCount: z.number().int().nonnegative(),
dauCount: z.number().int().nonnegative(),
wauCount: z.number().int().nonnegative(),
mauCount: z.number().int().nonnegative(),
deploymentType: z.string(),
isTelemetryEnabled: z.boolean(),
activationCode: z.string().optional(),
Expand Down
24 changes: 24 additions & 0 deletions packages/web/src/middleware/withAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { ErrorCode } from "../lib/errorCodes";
import { getOrgMetadata, isServiceError } from "../lib/utils";
import { hasEntitlement, isAnonymousAccessAvailable } from "@/lib/entitlements";

const LAST_ACTIVE_AT_THRESHOLD_MS = 5 * 60 * 1000;

type RequiredAuthContext = {
user: UserWithAccounts;
role: OrgRole;
Expand Down Expand Up @@ -107,12 +109,34 @@ export const getAuthContext = async (): Promise<OptionalAuthContext | ServiceErr

const prisma = __unsafePrisma.$extends(await userScopedPrismaClientExtension(user)) as PrismaClient;

if (user) {
updateUserLastActiveAt(user);
}

if (user && role) {
return { user, org, role, prisma };
}
return { user, org, prisma };
};

const updateUserLastActiveAt = (user: UserWithAccounts) => {
const now = Date.now();
if (
user.lastActiveAt &&
(now - user.lastActiveAt.getTime()) < LAST_ACTIVE_AT_THRESHOLD_MS
) {
return;
}

// Fired without a await to avoid blocking.
void __unsafePrisma.user
.update({
where: { id: user.id },
data: { lastActiveAt: new Date(now) },
})
.catch(() => { /* updaing the lastActiveAt is best effort. */ });
};

type AuthSource = 'session' | 'oauth' | 'api_key';

export const getAuthenticatedUser = async (): Promise<{ user: UserWithAccounts, source: AuthSource } | undefined> => {
Expand Down
Loading