mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
⭐ feat(www): Finish up on the onboarding (#210)
Merging this prematurely to make sure the team is on the same boat... like dang! We need to find a better way to do this. Plus it has become too big
This commit is contained in:
@@ -1,30 +1,17 @@
|
||||
export * from "drizzle-orm";
|
||||
import ws from 'ws';
|
||||
import { Resource } from "sst";
|
||||
import { drizzle as neonDrizzle, NeonDatabase } from "drizzle-orm/neon-serverless";
|
||||
// import { drizzle } from 'drizzle-orm/postgres-js';
|
||||
import { Pool, neonConfig } from "@neondatabase/serverless";
|
||||
import postgres from "postgres";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
|
||||
neonConfig.webSocketConstructor = ws;
|
||||
const client = postgres({
|
||||
idle_timeout: 30000,
|
||||
connect_timeout: 30000,
|
||||
host: Resource.Postgres.host,
|
||||
database: Resource.Postgres.database,
|
||||
user: Resource.Postgres.username,
|
||||
password: Resource.Postgres.password,
|
||||
port: Resource.Postgres.port,
|
||||
max: parseInt(process.env.POSTGRES_POOL_MAX || "1"),
|
||||
});
|
||||
|
||||
function addPoolerSuffix(original: string): string {
|
||||
const firstDotIndex = original.indexOf('.');
|
||||
if (firstDotIndex === -1) return original + '-pooler';
|
||||
return original.slice(0, firstDotIndex) + '-pooler' + original.slice(firstDotIndex);
|
||||
}
|
||||
|
||||
const dbHost = addPoolerSuffix(Resource.Database.host)
|
||||
|
||||
const client = new Pool({ connectionString: `postgres://${Resource.Database.user}:${Resource.Database.password}@${dbHost}/${Resource.Database.name}?sslmode=require` })
|
||||
|
||||
export const db = neonDrizzle(client, {
|
||||
logger:
|
||||
process.env.DRIZZLE_LOG === "true"
|
||||
? {
|
||||
logQuery(query, params) {
|
||||
console.log("query", query);
|
||||
console.log("params", params);
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
export const db = drizzle(client, {});
|
||||
@@ -4,14 +4,13 @@ import {
|
||||
PgTransactionConfig
|
||||
} from "drizzle-orm/pg-core";
|
||||
import {
|
||||
NeonQueryResultHKT
|
||||
// NeonHttpQueryResultHKT
|
||||
} from "drizzle-orm/neon-serverless";
|
||||
PostgresJsQueryResultHKT
|
||||
} from "drizzle-orm/postgres-js";
|
||||
import { ExtractTablesWithRelations } from "drizzle-orm";
|
||||
import { createContext } from "../context";
|
||||
|
||||
export type Transaction = PgTransaction<
|
||||
NeonQueryResultHKT,
|
||||
PostgresJsQueryResultHKT,
|
||||
Record<string, never>,
|
||||
ExtractTablesWithRelations<Record<string, never>>
|
||||
>;
|
||||
@@ -59,7 +58,6 @@ export async function createTransaction<T>(
|
||||
},
|
||||
);
|
||||
await Promise.all(effects.map((x) => x()));
|
||||
// await db.$client.end()
|
||||
return result as T;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,145 @@
|
||||
import { z } from "zod"
|
||||
|
||||
/**
|
||||
* Standard error response schema used for OpenAPI documentation
|
||||
*/
|
||||
export const ErrorResponse = z
|
||||
.object({
|
||||
type: z
|
||||
.enum([
|
||||
"validation",
|
||||
"authentication",
|
||||
"forbidden",
|
||||
"not_found",
|
||||
"already_exists",
|
||||
"rate_limit",
|
||||
"internal",
|
||||
])
|
||||
.openapi({
|
||||
description: "The error type category",
|
||||
examples: ["validation", "authentication"],
|
||||
}),
|
||||
code: z.string().openapi({
|
||||
description: "Machine-readable error code identifier",
|
||||
examples: ["invalid_parameter", "missing_required_field", "unauthorized"],
|
||||
}),
|
||||
message: z.string().openapi({
|
||||
description: "Human-readable error message",
|
||||
examples: ["The request was invalid", "Authentication required"],
|
||||
}),
|
||||
param: z
|
||||
.string()
|
||||
.optional()
|
||||
.openapi({
|
||||
description: "The parameter that caused the error (if applicable)",
|
||||
examples: ["email", "user_id", "team_id"],
|
||||
}),
|
||||
details: z.any().optional().openapi({
|
||||
description: "Additional error context information",
|
||||
}),
|
||||
})
|
||||
.openapi({ ref: "ErrorResponse" });
|
||||
|
||||
export type ErrorResponseType = z.infer<typeof ErrorResponse>;
|
||||
|
||||
/**
|
||||
* Standardized error codes for the API
|
||||
*/
|
||||
export const ErrorCodes = {
|
||||
// Validation errors (400)
|
||||
Validation: {
|
||||
MISSING_REQUIRED_FIELD: "missing_required_field",
|
||||
ALREADY_EXISTS: "resource_already_exists",
|
||||
TEAM_ALREADY_EXISTS: "team_already_exists",
|
||||
INVALID_PARAMETER: "invalid_parameter",
|
||||
INVALID_FORMAT: "invalid_format",
|
||||
INVALID_STATE: "invalid_state",
|
||||
IN_USE: "resource_in_use",
|
||||
},
|
||||
|
||||
// Authentication errors (401)
|
||||
Authentication: {
|
||||
UNAUTHORIZED: "unauthorized",
|
||||
INVALID_TOKEN: "invalid_token",
|
||||
EXPIRED_TOKEN: "expired_token",
|
||||
INVALID_CREDENTIALS: "invalid_credentials",
|
||||
},
|
||||
|
||||
// Permission errors (403)
|
||||
Permission: {
|
||||
FORBIDDEN: "forbidden",
|
||||
INSUFFICIENT_PERMISSIONS: "insufficient_permissions",
|
||||
ACCOUNT_RESTRICTED: "account_restricted",
|
||||
},
|
||||
|
||||
// Resource not found errors (404)
|
||||
NotFound: {
|
||||
RESOURCE_NOT_FOUND: "resource_not_found",
|
||||
},
|
||||
|
||||
// Rate limit errors (429)
|
||||
RateLimit: {
|
||||
TOO_MANY_REQUESTS: "too_many_requests",
|
||||
QUOTA_EXCEEDED: "quota_exceeded",
|
||||
},
|
||||
|
||||
// Server errors (500)
|
||||
Server: {
|
||||
INTERNAL_ERROR: "internal_error",
|
||||
SERVICE_UNAVAILABLE: "service_unavailable",
|
||||
DEPENDENCY_FAILURE: "dependency_failure",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Standard error that will be exposed to clients through API responses
|
||||
*/
|
||||
export class VisibleError extends Error {
|
||||
constructor(
|
||||
public code: string,
|
||||
public message: string,
|
||||
) {
|
||||
super(message);
|
||||
constructor(
|
||||
public type: ErrorResponseType["type"],
|
||||
public code: string,
|
||||
public message: string,
|
||||
public param?: string,
|
||||
public details?: any,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this error to an HTTP status code
|
||||
*/
|
||||
public statusCode(): number {
|
||||
switch (this.type) {
|
||||
case "validation":
|
||||
return 400;
|
||||
case "authentication":
|
||||
return 401;
|
||||
case "forbidden":
|
||||
return 403;
|
||||
case "not_found":
|
||||
return 404;
|
||||
case "already_exists":
|
||||
return 409;
|
||||
case "rate_limit":
|
||||
return 429;
|
||||
case "internal":
|
||||
return 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert this error to a standard response object
|
||||
*/
|
||||
public toResponse(): ErrorResponseType {
|
||||
const response: ErrorResponseType = {
|
||||
type: this.type,
|
||||
code: this.code,
|
||||
message: this.message,
|
||||
};
|
||||
|
||||
if (this.param) response.param = this.param;
|
||||
if (this.details) response.details = this.details;
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export module Examples {
|
||||
id: Id("team"),
|
||||
name: "John Does' Team",
|
||||
slug: "john_doe",
|
||||
planType: "BYOG" as const
|
||||
}
|
||||
|
||||
export const Member = {
|
||||
|
||||
@@ -66,15 +66,11 @@ export module Member {
|
||||
const id = input.id ?? createID("member");
|
||||
await tx.insert(memberTable).values({
|
||||
id,
|
||||
email: input.email,
|
||||
teamID: useTeam(),
|
||||
timeSeen: input.first ? sql`CURRENT_TIMESTAMP()` : null,
|
||||
}).onConflictDoUpdate({
|
||||
target: memberTable.id,
|
||||
set: {
|
||||
timeDeleted: null,
|
||||
}
|
||||
email: input.email,
|
||||
timeSeen: input.first ? sql`now()` : null,
|
||||
})
|
||||
|
||||
await afterTx(() =>
|
||||
async () => bus.publish(Resource.Bus, Events.Created, { memberID: id }),
|
||||
);
|
||||
@@ -87,7 +83,7 @@ export module Member {
|
||||
await tx
|
||||
.update(memberTable)
|
||||
.set({
|
||||
timeDeleted: sql`CURRENT_TIMESTAMP()`,
|
||||
timeDeleted: sql`now()`,
|
||||
})
|
||||
.where(and(eq(memberTable.id, input), eq(memberTable.teamID, useTeam())))
|
||||
.execute();
|
||||
|
||||
@@ -12,7 +12,7 @@ export const memberTable = pgTable(
|
||||
},
|
||||
(table) => [
|
||||
...teamIndexes(table),
|
||||
uniqueIndex("member_email").on(table.teamID, table.email),
|
||||
index("email_global").on(table.email),
|
||||
uniqueIndex("member_email").on(table.teamID, table.email),
|
||||
],
|
||||
);
|
||||
@@ -4,7 +4,7 @@ import { Resource } from "sst";
|
||||
import { eq, and } from "../drizzle";
|
||||
import { useTeam } from "../actor";
|
||||
import { createEvent } from "../event";
|
||||
import { polarTable, Standing } from "./polar.sql";
|
||||
// import { polarTable, Standing } from "./polar.sql.ts.test";
|
||||
import { Polar as PolarSdk } from "@polar-sh/sdk";
|
||||
import { useTransaction } from "../drizzle/transaction";
|
||||
|
||||
@@ -15,10 +15,10 @@ export module Polar {
|
||||
|
||||
export const Info = z.object({
|
||||
teamID: z.string(),
|
||||
customerID: z.string(),
|
||||
subscriptionID: z.string().nullable(),
|
||||
customerID: z.string(),
|
||||
subscriptionItemID: z.string().nullable(),
|
||||
standing: z.enum(Standing),
|
||||
// standing: z.enum(Standing),
|
||||
});
|
||||
|
||||
export type Info = z.infer<typeof Info>;
|
||||
@@ -53,16 +53,16 @@ export module Polar {
|
||||
),
|
||||
};
|
||||
|
||||
export function get() {
|
||||
return useTransaction(async (tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(polarTable)
|
||||
.where(eq(polarTable.teamID, useTeam()))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
);
|
||||
}
|
||||
// export function get() {
|
||||
// return useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .select()
|
||||
// .from(polarTable)
|
||||
// .where(eq(polarTable.teamID, useTeam()))
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// );
|
||||
// }
|
||||
|
||||
export const fromUserEmail = fn(z.string().min(1), async (email) => {
|
||||
try {
|
||||
@@ -81,89 +81,89 @@ export module Polar {
|
||||
}
|
||||
})
|
||||
|
||||
export const setCustomerID = fn(Info.shape.customerID, async (customerID) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.insert(polarTable)
|
||||
.values({
|
||||
teamID: useTeam(),
|
||||
customerID,
|
||||
standing: "new",
|
||||
})
|
||||
.execute(),
|
||||
),
|
||||
);
|
||||
// export const setCustomerID = fn(Info.shape.customerID, async (customerID) =>
|
||||
// useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .insert(polarTable)
|
||||
// .values({
|
||||
// teamID: useTeam(),
|
||||
// customerID,
|
||||
// standing: "new",
|
||||
// })
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
|
||||
export const setSubscription = fn(
|
||||
Info.pick({
|
||||
subscriptionID: true,
|
||||
subscriptionItemID: true,
|
||||
}),
|
||||
(input) =>
|
||||
useTransaction(async (tx) =>
|
||||
tx
|
||||
.update(polarTable)
|
||||
.set({
|
||||
subscriptionID: input.subscriptionID,
|
||||
subscriptionItemID: input.subscriptionItemID,
|
||||
})
|
||||
.where(eq(polarTable.teamID, useTeam()))
|
||||
.returning()
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
);
|
||||
// export const setSubscription = fn(
|
||||
// Info.pick({
|
||||
// subscriptionID: true,
|
||||
// subscriptionItemID: true,
|
||||
// }),
|
||||
// (input) =>
|
||||
// useTransaction(async (tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({
|
||||
// subscriptionID: input.subscriptionID,
|
||||
// subscriptionItemID: input.subscriptionItemID,
|
||||
// })
|
||||
// .where(eq(polarTable.teamID, useTeam()))
|
||||
// .returning()
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// ),
|
||||
// );
|
||||
|
||||
export const removeSubscription = fn(
|
||||
z.string().min(1),
|
||||
(stripeSubscriptionID) =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.update(polarTable)
|
||||
.set({
|
||||
subscriptionItemID: null,
|
||||
subscriptionID: null,
|
||||
})
|
||||
.where(and(eq(polarTable.subscriptionID, stripeSubscriptionID)))
|
||||
.execute(),
|
||||
),
|
||||
);
|
||||
// export const removeSubscription = fn(
|
||||
// z.string().min(1),
|
||||
// (stripeSubscriptionID) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({
|
||||
// subscriptionItemID: null,
|
||||
// subscriptionID: null,
|
||||
// })
|
||||
// .where(and(eq(polarTable.subscriptionID, stripeSubscriptionID)))
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
|
||||
export const setStanding = fn(
|
||||
Info.pick({
|
||||
subscriptionID: true,
|
||||
standing: true,
|
||||
}),
|
||||
(input) =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.update(polarTable)
|
||||
.set({ standing: input.standing })
|
||||
.where(and(eq(polarTable.subscriptionID, input.subscriptionID!)))
|
||||
.execute(),
|
||||
),
|
||||
);
|
||||
// export const setStanding = fn(
|
||||
// Info.pick({
|
||||
// subscriptionID: true,
|
||||
// standing: true,
|
||||
// }),
|
||||
// (input) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .update(polarTable)
|
||||
// .set({ standing: input.standing })
|
||||
// .where(and(eq(polarTable.subscriptionID, input.subscriptionID!)))
|
||||
// .execute(),
|
||||
// ),
|
||||
// );
|
||||
|
||||
export const fromCustomerID = fn(Info.shape.customerID, (customerID) =>
|
||||
useTransaction((tx) =>
|
||||
tx
|
||||
.select()
|
||||
.from(polarTable)
|
||||
.where(and(eq(polarTable.customerID, customerID)))
|
||||
.execute()
|
||||
.then((rows) => rows.map(serialize).at(0)),
|
||||
),
|
||||
);
|
||||
// export const fromCustomerID = fn(Info.shape.customerID, (customerID) =>
|
||||
// useTransaction((tx) =>
|
||||
// tx
|
||||
// .select()
|
||||
// .from(polarTable)
|
||||
// .where(and(eq(polarTable.customerID, customerID)))
|
||||
// .execute()
|
||||
// .then((rows) => rows.map(serialize).at(0)),
|
||||
// ),
|
||||
// );
|
||||
|
||||
function serialize(
|
||||
input: typeof polarTable.$inferSelect,
|
||||
): z.infer<typeof Info> {
|
||||
return {
|
||||
teamID: input.teamID,
|
||||
customerID: input.customerID,
|
||||
subscriptionID: input.subscriptionID,
|
||||
subscriptionItemID: input.subscriptionItemID,
|
||||
standing: input.standing,
|
||||
};
|
||||
}
|
||||
// function serialize(
|
||||
// input: typeof polarTable.$inferSelect,
|
||||
// ): z.infer<typeof Info> {
|
||||
// return {
|
||||
// teamID: input.teamID,
|
||||
// customerID: input.customerID,
|
||||
// subscriptionID: input.subscriptionID,
|
||||
// subscriptionItemID: input.subscriptionItemID,
|
||||
// standing: input.standing,
|
||||
// };
|
||||
// }
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import { timestamps, teamID } from "../drizzle/types";
|
||||
import { teamIndexes, teamTable } from "../team/team.sql";
|
||||
import { pgTable, text, varchar } from "drizzle-orm/pg-core";
|
||||
|
||||
// FIXME: This is causing errors while trying to db push
|
||||
export const Standing = ["new", "good", "overdue"] as const;
|
||||
|
||||
export const polarTable = pgTable(
|
||||
@@ -16,7 +17,7 @@ export const polarTable = pgTable(
|
||||
}),
|
||||
standing: text("standing", { enum: Standing }).notNull(),
|
||||
},
|
||||
(table) => ({
|
||||
(table) => [
|
||||
...teamIndexes(table),
|
||||
})
|
||||
]
|
||||
)
|
||||
@@ -2,14 +2,14 @@ import { z } from "zod";
|
||||
import { Resource } from "sst";
|
||||
import { bus } from "sst/aws/bus";
|
||||
import { Common } from "../common";
|
||||
import { createID, fn } from "../utils";
|
||||
import { Examples } from "../examples";
|
||||
import { teamTable } from "./team.sql";
|
||||
import { createEvent } from "../event";
|
||||
import { assertActor, withActor } from "../actor";
|
||||
import { createID, fn } from "../utils";
|
||||
import { and, eq, sql } from "../drizzle";
|
||||
import { PlanType, teamTable } from "./team.sql";
|
||||
import { assertActor, withActor } from "../actor";
|
||||
import { memberTable } from "../member/member.sql";
|
||||
import { HTTPException } from 'hono/http-exception';
|
||||
import { ErrorCodes, VisibleError } from "../error";
|
||||
import { afterTx, createTransaction, useTransaction } from "../drizzle/transaction";
|
||||
|
||||
export module Team {
|
||||
@@ -19,13 +19,17 @@ export module Team {
|
||||
description: Common.IdDescription,
|
||||
example: Examples.Team.id,
|
||||
}),
|
||||
slug: z.string().openapi({
|
||||
slug: z.string().regex(/^[a-z0-9\-]+$/, "Use a URL friendly name.").openapi({
|
||||
description: "The unique and url-friendly slug of this team",
|
||||
example: Examples.Team.slug
|
||||
}),
|
||||
name: z.string().openapi({
|
||||
description: "The name of this team",
|
||||
example: Examples.Team.name
|
||||
}),
|
||||
planType: z.enum(PlanType).openapi({
|
||||
description: "The type of Plan this team is subscribed to",
|
||||
example: Examples.Team.planType
|
||||
})
|
||||
})
|
||||
.openapi({
|
||||
@@ -45,40 +49,36 @@ export module Team {
|
||||
),
|
||||
};
|
||||
|
||||
export class TeamExistsError extends HTTPException {
|
||||
export class TeamExistsError extends VisibleError {
|
||||
constructor(slug: string) {
|
||||
super(
|
||||
400,
|
||||
{ message: `There is already a team named "${slug}"`, }
|
||||
"already_exists",
|
||||
ErrorCodes.Validation.TEAM_ALREADY_EXISTS,
|
||||
`There is already a team named "${slug}"`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const create = fn(
|
||||
Info.pick({ slug: true, id: true, name: true }).partial({
|
||||
Info.pick({ slug: true, id: true, name: true, planType: true }).partial({
|
||||
id: true,
|
||||
}), (input) => {
|
||||
createTransaction(async (tx) => {
|
||||
const id = input.id ?? createID("team");
|
||||
const result = await tx.insert(teamTable).values({
|
||||
id,
|
||||
slug: input.slug,
|
||||
name: input.name
|
||||
})
|
||||
.onConflictDoNothing({ target: teamTable.slug })
|
||||
|
||||
if (!result.rowCount) throw new TeamExistsError(input.slug);
|
||||
|
||||
await afterTx(() =>
|
||||
withActor({ type: "system", properties: { teamID: id } }, () =>
|
||||
bus.publish(Resource.Bus, Events.Created, {
|
||||
teamID: id,
|
||||
})
|
||||
),
|
||||
);
|
||||
return id;
|
||||
}), (input) =>
|
||||
createTransaction(async (tx) => {
|
||||
const id = input.id ?? createID("team");
|
||||
const result = await tx.insert(teamTable).values({
|
||||
id,
|
||||
//Remove spaces and make sure it is lowercase (this is just to make sure the frontend did this)
|
||||
slug: input.slug, //.toLowerCase().replace(/[\s]/g, ''),
|
||||
planType: input.planType,
|
||||
name: input.name
|
||||
})
|
||||
.onConflictDoNothing({ target: teamTable.slug })
|
||||
|
||||
if (result.count === 0) throw new TeamExistsError(input.slug);
|
||||
|
||||
return id;
|
||||
})
|
||||
)
|
||||
|
||||
export const remove = fn(Info.shape.id, (input) =>
|
||||
useTransaction(async (tx) => {
|
||||
@@ -147,6 +147,7 @@ export module Team {
|
||||
id: input.id,
|
||||
name: input.name,
|
||||
slug: input.slug,
|
||||
planType: input.planType,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,21 +1,27 @@
|
||||
import {} from "drizzle-orm/postgres-js";
|
||||
import { } from "drizzle-orm/postgres-js";
|
||||
import { timestamps, id } from "../drizzle/types";
|
||||
import {
|
||||
varchar,
|
||||
pgTable,
|
||||
primaryKey,
|
||||
uniqueIndex,
|
||||
varchar,
|
||||
text
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
export const PlanType = ["Hosted", "BYOG"] as const;
|
||||
|
||||
export const teamTable = pgTable(
|
||||
"team",
|
||||
{
|
||||
...id,
|
||||
...timestamps,
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
slug: varchar("slug", { length: 255 }).notNull(),
|
||||
planType: text("plan_type", { enum: PlanType }).notNull()
|
||||
},
|
||||
(table) => [uniqueIndex("slug").on(table.slug)],
|
||||
(table) => [
|
||||
uniqueIndex("slug").on(table.slug)
|
||||
],
|
||||
);
|
||||
|
||||
export function teamIndexes(table: any) {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { z } from "zod";
|
||||
import { Polar } from "../polar";
|
||||
import { Team } from "../team";
|
||||
import { bus } from "sst/aws/bus";
|
||||
import { Common } from "../common";
|
||||
import { Polar } from "../polar/index";
|
||||
import { createID, fn } from "../utils";
|
||||
import { userTable } from "./user.sql";
|
||||
import { createEvent } from "../event";
|
||||
@@ -188,7 +188,7 @@ export module User {
|
||||
await tx
|
||||
.update(userTable)
|
||||
.set({
|
||||
timeDeleted: sql`CURRENT_TIMESTAMP()`,
|
||||
timeDeleted: sql`now()`,
|
||||
})
|
||||
.where(and(eq(userTable.id, input)))
|
||||
.execute();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
import { id, timestamps } from "../drizzle/types";
|
||||
import { integer, pgTable, text, uniqueIndex, varchar,json } from "drizzle-orm/pg-core";
|
||||
import { integer, pgTable, text, uniqueIndex, varchar, json } from "drizzle-orm/pg-core";
|
||||
|
||||
// Whether this user is part of the Nestri Team, comes with privileges
|
||||
export const UserFlags = z.object({
|
||||
@@ -15,13 +15,13 @@ export const userTable = pgTable(
|
||||
...id,
|
||||
...timestamps,
|
||||
avatarUrl: text("avatar_url"),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
name: varchar("name", { length: 255 }).notNull(),
|
||||
discriminator: integer("discriminator").notNull(),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
polarCustomerID: varchar("polar_customer_id", { length: 255 }).unique(),
|
||||
flags: json("flags").$type<UserFlags>().default({}),
|
||||
},
|
||||
(user) => [
|
||||
uniqueIndex("user_email").on(user.email),
|
||||
],
|
||||
]
|
||||
);
|
||||
Reference in New Issue
Block a user