PRO cutoff: an account that signed up before 2026-12-01 keeps the whole studio with its Activated Pro box ticked by the calendar, signups from the cutoff on start basic
This commit is contained in:
@@ -241,6 +241,7 @@ export const PHOTO_HISTORY_MAX = 3;
|
||||
// `verified` the client reads.
|
||||
// `pro` is the admin's own 0/1 grant — the "Activated Pro" box in the users
|
||||
// table, and the only way an unproven address reaches the PRO tier.
|
||||
// `createdAt` is the signup instant, which the PRO cutoff reads (see server.ts).
|
||||
export type User = {
|
||||
id: number;
|
||||
email: string;
|
||||
@@ -249,6 +250,7 @@ export type User = {
|
||||
deletedAt: string | null;
|
||||
emailVerified: number;
|
||||
pro: number;
|
||||
createdAt: string;
|
||||
};
|
||||
export type Recipe = {
|
||||
id: number;
|
||||
@@ -282,10 +284,11 @@ const now = () => new Date().toISOString();
|
||||
|
||||
export function createUser(email: string, password: string): User | null {
|
||||
try {
|
||||
const createdAt = now();
|
||||
const info = db
|
||||
.prepare('INSERT INTO users (email, password_hash, created_at) VALUES (?, ?, ?)')
|
||||
.run(email, hashPassword(password), now());
|
||||
return { id: Number(info.lastInsertRowid), email, avatar: null, blocked: 0, deletedAt: null, emailVerified: 0, pro: 0 };
|
||||
.run(email, hashPassword(password), createdAt);
|
||||
return { id: Number(info.lastInsertRowid), email, avatar: null, blocked: 0, deletedAt: null, emailVerified: 0, pro: 0, createdAt };
|
||||
} catch (err) {
|
||||
if ((err as { code?: string }).code === 'SQLITE_CONSTRAINT_UNIQUE') return null;
|
||||
throw err;
|
||||
@@ -295,14 +298,14 @@ export function createUser(email: string, password: string): User | null {
|
||||
export function findUserByEmail(email: string): (User & { password_hash: string }) | undefined {
|
||||
return db
|
||||
.prepare(
|
||||
'SELECT id, email, avatar, blocked, deleted_at AS deletedAt, email_verified AS emailVerified, pro, password_hash FROM users WHERE email = ?',
|
||||
'SELECT id, email, avatar, blocked, deleted_at AS deletedAt, email_verified AS emailVerified, pro, created_at AS createdAt, password_hash FROM users WHERE email = ?',
|
||||
)
|
||||
.get(email) as (User & { password_hash: string }) | undefined;
|
||||
}
|
||||
|
||||
export function findUserById(id: number): User | undefined {
|
||||
return db
|
||||
.prepare('SELECT id, email, avatar, blocked, deleted_at AS deletedAt, email_verified AS emailVerified, pro FROM users WHERE id = ?')
|
||||
.prepare('SELECT id, email, avatar, blocked, deleted_at AS deletedAt, email_verified AS emailVerified, pro, created_at AS createdAt FROM users WHERE id = ?')
|
||||
.get(id) as User | undefined;
|
||||
}
|
||||
|
||||
@@ -604,6 +607,7 @@ export type AdminUser = {
|
||||
blocked: number;
|
||||
deletedAt: string | null;
|
||||
pro: number;
|
||||
emailVerified: number;
|
||||
};
|
||||
|
||||
export function listUsersWithCounts(): AdminUser[] {
|
||||
@@ -611,7 +615,8 @@ export function listUsersWithCounts(): AdminUser[] {
|
||||
.prepare(
|
||||
`SELECT users.id AS id, users.email AS email, users.created_at AS createdAt,
|
||||
users.avatar AS avatar, users.blocked AS blocked,
|
||||
users.deleted_at AS deletedAt, users.pro AS pro, COUNT(photos.id) AS photos
|
||||
users.deleted_at AS deletedAt, users.pro AS pro,
|
||||
users.email_verified AS emailVerified, COUNT(photos.id) AS photos
|
||||
FROM users LEFT JOIN photos ON photos.user_id = users.id
|
||||
GROUP BY users.id
|
||||
ORDER BY users.id`,
|
||||
|
||||
Reference in New Issue
Block a user