Skip to content
This repository was archived by the owner on Nov 4, 2023. It is now read-only.
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
2 changes: 2 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
import type * as _shared from "../_shared";
import type * as auth from "../auth";
import type * as projects from "../projects";
import type * as services from "../services";

/**
* A utility for referencing Convex functions in your app's API.
Expand All @@ -30,6 +31,7 @@ declare const fullApi: ApiFromModules<{
_shared: typeof _shared;
auth: typeof auth;
projects: typeof projects;
services: typeof services;
}>;
export declare const api: FilterApi<
typeof fullApi,
Expand Down
19 changes: 19 additions & 0 deletions convex/_shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,22 @@ export async function isAuthenticated(auth: Auth) {

return identity;
}

/**
* Checks if the error is a convex error then gets the message from it.
* @param error Any error from a try catch block.
* @returns
*/
export function buildTypedErrorMessage (error: unknown): string {
const errorMessage =
error instanceof IConvexError
? (error.data as { message: string }).message
: 'Unexpected error occurred';

return errorMessage;
}

export type Result = {
Ok: boolean;
Err?: IConvexError;
}
191 changes: 104 additions & 87 deletions convex/auth.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { v } from "convex/values";
import { QueryCtx, mutation, query } from "./_generated/server";
import { IConvexError, isAuthenticated } from "./_shared";
import { v } from 'convex/values';
import { QueryCtx, mutation, query } from './_generated/server';
import { IConvexError, isAuthenticated } from './_shared';

/**
* Stores an authenticated user in the database. This runs whenever the auth state is changed on the client side (by clerk).
Expand All @@ -9,97 +9,113 @@ import { IConvexError, isAuthenticated } from "./_shared";
* If they due exist, all clerk metadata is updated and the rest of the user data is saved as is.
*/
export const store = mutation({
args: {},
handler: async (ctx) => {
const identity = await isAuthenticated(ctx.auth);
args: {},
handler: async (ctx) => {
const identity = await isAuthenticated(ctx.auth);

if (!identity.email) {
throw new IConvexError({
code: "Unauthorized",
message: "No email found for identity",
});
}
if (!identity.email) {
throw new IConvexError({
code: 'Unauthorized',
message: 'No email found for identity',
});
}

if (!identity.name) {
throw new IConvexError({
code: "Unauthorized",
message: "No name found for identity",
});
}
if (!identity.name) {
throw new IConvexError({
code: 'Unauthorized',
message: 'No name found for identity',
});
}

const user = await getUserWithId(ctx, identity.subject);
const user = await getUserWithId(ctx);

if (user !== null) {
if (
user.username !== identity.nickname ||
identity.preferredUsername ||
identity.name ||
identity.familyName ||
user.email !== identity.email
) {
const newUser = {
...user,
tokenIdentifier: identity.tokenIdentifier,
issuer: identity.issuer,
user_id: identity.subject,
username: identity.nickname || identity.name,
email: identity.email,
emailVerified: identity.emailVerified ?? false,
};
if (user !== null) {
if (
user.username !== identity.nickname ||
identity.preferredUsername ||
identity.name ||
identity.familyName ||
user.email !== identity.email
) {
const newUser = {
...user,
tokenIdentifier: identity.tokenIdentifier,
issuer: identity.issuer,
user_id: identity.subject,
username: identity.nickname || identity.name,
email: identity.email,
emailVerified: identity.emailVerified ?? false,
};

await ctx.db.patch(user._id, newUser).catch((err) => {
console.error(err);
throw new IConvexError({
code: "DatabaseError",
message: err.message,
severity: "High",
where: "convex/auth.ts",
});
});
}
return user._id;
}
await ctx.db.patch(user._id, newUser).catch((err) => {
console.error(err);
throw new IConvexError({
code: 'DatabaseError',
message: err.message,
severity: 'High',
where: 'convex/auth.ts',
});
});
}
return user._id;
}

// If it's a new identity, create a new `User`.
return await ctx.db.insert("auth", {
tokenIdentifier: identity.tokenIdentifier,
issuer: identity.issuer,
username: identity.nickname || identity.name,
user_id: identity.subject,
email: identity.email,
emailVerified: identity.emailVerified ?? false,
role: "User",
tombstoned: false,
projects: [],
joined_projects: [],
});
},
// If it's a new identity, create a new `User`.
return await ctx.db.insert('auth', {
tokenIdentifier: identity.tokenIdentifier,
issuer: identity.issuer,
username: identity.nickname || identity.name,
user_id: identity.subject,
email: identity.email,
emailVerified: identity.emailVerified ?? false,
role: 'User',
tombstoned: false,
projects: [],
joined_projects: [],
});
},
});

/**
* Gets a user by there clerk user_id
* @param user_id The clerk user_id
*/
export const get = query({
args: {
user_id: v.string(),
},
handler: async (ctx, { user_id }) => {
return await getUserWithId(ctx, user_id);
},
args: {
user_id: v.string(),
},
handler: async (ctx, { user_id }) => {
return await ctx.db
.query('auth')
.withIndex('by_user_id', (q) => q.eq('user_id', user_id))
.filter((q) => q.eq(q.field('tombstoned'), false))
.unique()
.catch((err) => {
console.error(err);
return null;
});
},
});

/**
* Gets a user by there clerk user_id
* Gets the current authenticated user by there identity subject (user_id)
* @param ctx The Convex Query Context
* @param user_id The clerk user_id
* @returns The user or null if not found
*/
export async function getUserWithId(ctx: QueryCtx, user_id: string) {
return await ctx.db
.query("auth")
.withIndex("by_user_id", (q) => q.eq("user_id", user_id))
.unique()
.catch((err) => {
console.error(err);
return null;
});
export async function getUserWithId(ctx: QueryCtx) {
const identity = await ctx.auth.getUserIdentity();

if (!identity) return null;

return await ctx.db
.query('auth')
.withIndex('by_user_id', (q) => q.eq('user_id', identity.subject))
.filter((q) => q.eq(q.field("tombstoned"), false))
.unique()
.catch((err) => {
console.error(err);
return null;
});
}

/**
Expand All @@ -109,12 +125,13 @@ export async function getUserWithId(ctx: QueryCtx, user_id: string) {
* @returns
*/
export async function getUserWithEmail(ctx: QueryCtx, email: string) {
return await ctx.db
.query("auth")
.filter((q) => q.eq(q.field("email"), email))
.unique()
.catch((err) => {
console.error(err);
return null;
});
return await ctx.db
.query('auth')
.filter((q) => q.eq(q.field('email'), email))
.filter((q) => q.eq(q.field('tombstoned'), false))
.unique()
.catch((err) => {
console.error(err);
return null;
});
}
Loading