Merge pull request #365 from wasp-lang/update-updateUser-info

remove user lastActiveTimestamp property
This commit is contained in:
Filip Sodić 2025-02-20 15:42:24 +01:00 committed by GitHub
commit 40f6b72290
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 31 additions and 66 deletions

View File

@ -104,7 +104,7 @@
},
},
}
@@ -212,9 +208,9 @@
@@ -207,9 +203,9 @@
}
api paymentsWebhook {

View File

@ -0,0 +1,11 @@
--- template/app/migrations/20250220095333_remove_last_active_timestamp/migration.sql
+++ opensaas-sh/app/migrations/20250220095333_remove_last_active_timestamp/migration.sql
@@ -0,0 +1,8 @@
+/*
+ Warnings:
+
+ - You are about to drop the column `lastActiveTimestamp` on the `User` table. All the data in the column will be lost.
+
+*/
+-- AlterTable
+ALTER TABLE "User" DROP COLUMN "lastActiveTimestamp";

View File

@ -1,9 +1,9 @@
--- template/app/schema.prisma
+++ opensaas-sh/app/schema.prisma
@@ -14,10 +14,12 @@
@@ -13,10 +13,12 @@
email String? @unique
username String? @unique
lastActiveTimestamp DateTime @default(now())
- isAdmin Boolean @default(false)
+ isAdmin Boolean @default(true)
+ // isMockUser is an extra property for the demo app ensuring that all users can access

View File

@ -8,7 +8,7 @@
const [isAdminFilter, setIsAdminFilter] = useState<boolean | undefined>(undefined);
const [statusOptions, setStatusOptions] = useState<SubscriptionStatus[]>([]);
const { data, isLoading, error } = useQuery(getPaginatedUsers, {
@@ -222,7 +223,7 @@
@@ -211,7 +212,7 @@
<p className='text-sm text-black dark:text-white'>{user.subscriptionStatus}</p>
</div>
<div className='col-span-2 flex items-center'>

View File

@ -1,13 +1,13 @@
--- template/app/src/server/scripts/dbSeeds.ts
+++ opensaas-sh/app/src/server/scripts/dbSeeds.ts
@@ -38,9 +38,11 @@
@@ -37,9 +37,11 @@
sendNewsletter: false,
credits,
subscriptionStatus,
- lemonSqueezyCustomerPortalUrl: null,
- paymentProcessorUserId: hasUserPaidOnStripe ? `cus_test_${faker.string.uuid()}` : null,
+ stripeId: hasUserPaidOnStripe ? `cus_test_${faker.string.uuid()}` : null,
datePaid: hasUserPaidOnStripe ? faker.date.between({ from: createdAt, to: lastActiveTimestamp }) : null,
datePaid: hasUserPaidOnStripe ? faker.date.between({ from: createdAt, to: timePaid }) : null,
subscriptionPlan: subscriptionStatus ? faker.helpers.arrayElement(getSubscriptionPaymentPlanIds()) : null,
+ // For the demo app, we want to default isMockUser to true so that our admin dash only shows mock users
+ // and not real users signing up to test the app

View File

@ -1,18 +1,18 @@
--- template/app/src/user/operations.ts
+++ opensaas-sh/app/src/user/operations.ts
@@ -52,7 +52,10 @@
@@ -38,7 +38,10 @@
subscriptionStatus?: SubscriptionStatus[];
};
type GetPaginatedUsersOutput = {
- users: Pick<User, 'id' | 'email' | 'username' | 'lastActiveTimestamp' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
- users: Pick<User, 'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
+ users: Pick<
+ User,
+ 'id' | 'email' | 'username' | 'lastActiveTimestamp' | 'subscriptionStatus' | 'stripeId'
+ 'id' | 'email' | 'username' | 'subscriptionStatus' | 'stripeId'
+ >[];
totalPages: number;
};
@@ -65,8 +68,10 @@
@@ -51,8 +54,10 @@
}
const allSubscriptionStatusOptions = args.subscriptionStatus as Array<string | null> | undefined;
@ -25,7 +25,7 @@
const queryResults = await context.entities.User.findMany({
skip: args.skip,
@@ -79,6 +84,7 @@
@@ -65,6 +70,7 @@
mode: 'insensitive',
},
isAdmin: args.isAdmin,
@ -33,16 +33,16 @@
},
{
OR: [
@@ -103,7 +109,7 @@
@@ -88,7 +94,7 @@
username: true,
isAdmin: true,
lastActiveTimestamp: true,
subscriptionStatus: true,
- paymentProcessorUserId: true,
+ stripeId: true,
},
orderBy: {
id: 'desc',
@@ -119,6 +125,7 @@
@@ -104,6 +110,7 @@
mode: 'insensitive',
},
isAdmin: args.isAdmin,

View File

@ -18,7 +18,6 @@ entity User {=psl
email String? @unique
username String?
createdAt DateTime @default(now())
lastActiveTimestamp DateTime @default(now())
isAdmin Boolean @default(false)
paymentProcessorUserId String? @unique
lemonSqueezyCustomerPortalUrl String? // You can delete this if you're not using Lemon Squeezy as your payments processor.
@ -116,7 +115,6 @@ entity User {=psl
email String? @unique
username String?
createdAt DateTime @default(now())
lastActiveTimestamp DateTime @default(now())
isAdmin Boolean @default(false)
//...
psl=}

View File

@ -79,7 +79,7 @@ Authorization on the server-side is the core of your access control logic, and d
You can authorize access to server-side operations by adding a check for a logged-in user on the `context.user` object which is passed to all operations in Wasp:
```tsx title="src/server/actions.ts"
export const updateCurrentUser: UpdateCurrentUser<...> = async (args, context) => {
export const someServerAction: SomeServerAction<...> = async (args, context) => {
if (!context.user) {
throw new HttpError(401); // throw an error if user is not logged in
}

View File

@ -140,11 +140,6 @@ query getPaginatedUsers {
entities: [User]
}
action updateCurrentUserLastActiveTimestamp {
fn: import { updateCurrentUserLastActiveTimestamp } from "@src/user/operations",
entities: [User]
}
action updateIsUserAdminById {
fn: import { updateIsUserAdminById } from "@src/user/operations",
entities: [User]

View File

@ -13,7 +13,6 @@ model User {
email String? @unique
username String? @unique
lastActiveTimestamp DateTime @default(now())
isAdmin Boolean @default(false)
paymentProcessorUserId String? @unique

View File

@ -172,13 +172,10 @@ const UsersTable = () => {
</div>
</div>
<div className='grid grid-cols-12 border-t-4 border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '>
<div className='grid grid-cols-9 border-t-4 border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '>
<div className='col-span-3 flex items-center'>
<p className='font-medium'>Email / Username</p>
</div>
<div className='col-span-3 hidden items-center sm:flex'>
<p className='font-medium'>Last Active</p>
</div>
<div className='col-span-2 flex items-center'>
<p className='font-medium'>Subscription Status</p>
</div>
@ -202,7 +199,7 @@ const UsersTable = () => {
data.users.map((user) => (
<div
key={user.id}
className='grid grid-cols-12 gap-4 border-t border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '
className='grid grid-cols-9 gap-4 border-t border-stroke py-4.5 px-4 dark:border-strokedark md:px-6 '
>
<div className='col-span-3 flex items-center'>
<div className='flex flex-col gap-1 '>
@ -210,14 +207,6 @@ const UsersTable = () => {
<p className='text-sm text-black dark:text-white'>{user.username}</p>
</div>
</div>
<div className='col-span-3 hidden items-center sm:flex'>
<p className='text-sm text-black dark:text-white'>
{user.lastActiveTimestamp.toLocaleDateString() +
' ' +
user.lastActiveTimestamp.toLocaleTimeString()}
</p>
</div>
<div className='col-span-2 flex items-center'>
<p className='text-sm text-black dark:text-white'>{user.subscriptionStatus}</p>
</div>

View File

@ -8,7 +8,6 @@ import { routes } from 'wasp/client/router';
import { Outlet, useLocation } from 'react-router-dom';
import { useAuth } from 'wasp/client/auth';
import { useIsLandingPage } from './hooks/useIsLandingPage';
import { updateCurrentUserLastActiveTimestamp } from 'wasp/client/operations';
/**
* use this component to wrap all child components
@ -28,16 +27,6 @@ export default function App() {
return location.pathname.startsWith('/admin');
}, [location]);
useEffect(() => {
if (user) {
const lastSeenAt = new Date(user.lastActiveTimestamp);
const today = new Date();
if (today.getTime() - lastSeenAt.getTime() > 5 * 60 * 1000) {
updateCurrentUserLastActiveTimestamp({ lastActiveTimestamp: today });
}
}
}, [user]);
useEffect(() => {
if (location.hash) {
const id = location.hash.replace('#', '');

View File

@ -26,21 +26,20 @@ function generateMockUserData(): MockUserData {
const subscriptionStatus = faker.helpers.arrayElement<SubscriptionStatus | null>(['active', 'cancel_at_period_end', 'past_due', 'deleted', null]);
const now = new Date();
const createdAt = faker.date.past({ refDate: now });
const lastActiveTimestamp = faker.date.between({ from: createdAt, to: now });
const timePaid = faker.date.between({ from: createdAt, to: now });
const credits = subscriptionStatus ? 0 : faker.number.int({ min: 0, max: 10 });
const hasUserPaidOnStripe = !!subscriptionStatus || credits > 3
return {
email: faker.internet.email({ firstName, lastName }),
username: faker.internet.userName({ firstName, lastName }),
createdAt,
lastActiveTimestamp,
isAdmin: false,
sendNewsletter: false,
credits,
subscriptionStatus,
lemonSqueezyCustomerPortalUrl: null,
paymentProcessorUserId: hasUserPaidOnStripe ? `cus_test_${faker.string.uuid()}` : null,
datePaid: hasUserPaidOnStripe ? faker.date.between({ from: createdAt, to: lastActiveTimestamp }) : null,
datePaid: hasUserPaidOnStripe ? faker.date.between({ from: createdAt, to: timePaid }) : null,
subscriptionPlan: subscriptionStatus ? faker.helpers.arrayElement(getSubscriptionPaymentPlanIds()) : null,
};
}

View File

@ -1,5 +1,4 @@
import {
type UpdateCurrentUserLastActiveTimestamp,
type UpdateIsUserAdminById,
type GetPaginatedUsers,
} from 'wasp/server/operations';
@ -31,19 +30,6 @@ export const updateIsUserAdminById: UpdateIsUserAdminById<{ id: string; data: Pi
return updatedUser;
};
export const updateCurrentUserLastActiveTimestamp: UpdateCurrentUserLastActiveTimestamp<Pick<User, 'lastActiveTimestamp'>, User> = async ({ lastActiveTimestamp }, context) => {
if (!context.user) {
throw new HttpError(401);
}
return context.entities.User.update({
where: {
id: context.user.id,
},
data: {lastActiveTimestamp},
});
};
type GetPaginatedUsersInput = {
skip: number;
cursor?: number | undefined;
@ -52,7 +38,7 @@ type GetPaginatedUsersInput = {
subscriptionStatus?: SubscriptionStatus[];
};
type GetPaginatedUsersOutput = {
users: Pick<User, 'id' | 'email' | 'username' | 'lastActiveTimestamp' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
users: Pick<User, 'id' | 'email' | 'username' | 'subscriptionStatus' | 'paymentProcessorUserId'>[];
totalPages: number;
};
@ -101,7 +87,6 @@ export const getPaginatedUsers: GetPaginatedUsers<GetPaginatedUsersInput, GetPag
email: true,
username: true,
isAdmin: true,
lastActiveTimestamp: true,
subscriptionStatus: true,
paymentProcessorUserId: true,
},