Skip to content

Commit

Permalink
feat: add lastLoginAt to user (#1185)
Browse files Browse the repository at this point in the history
* add lastLoginAt to user

* fix lint and fixture issues

* Add test for user not found

* Update test to evaluate the correct user

---------

Co-authored-by: Jacob Capps <[email protected]>
Co-authored-by: Jacob Capps <[email protected]>
  • Loading branch information
3 people authored Jan 3, 2024
1 parent 265731b commit c54ad4a
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 6 deletions.
9 changes: 9 additions & 0 deletions src/__fixtures__/authUsers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ObjectId } from 'bson'
import { DateTime } from 'luxon'
import type { SessionUser, PortalUser, Collection, Widget } from 'types'

const mockNews: Widget = {
Expand Down Expand Up @@ -111,6 +112,7 @@ export const testPortalUser1: PortalUser = {
],
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

export const portalUserMaxedOutCollection: PortalUser = {
Expand Down Expand Up @@ -156,6 +158,7 @@ export const portalUserMaxedOutCollection: PortalUser = {
],
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

export const portalUserWithExampleCollection: PortalUser = {
Expand Down Expand Up @@ -194,6 +197,7 @@ export const portalUserWithExampleCollection: PortalUser = {
],
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

const mockCollectionWithGuardianIdeal: Widget = {
Expand All @@ -207,6 +211,7 @@ export const portalUserGuardianIdeal: PortalUser = {
mySpace: [mockCollectionWithGuardianIdeal],
displayName: 'BERNADETTE CAMPBELL',
theme: 'dark',
lastLoginAt: DateTime.now().toISO()!,
}

const mockCollectionWithFeaturedShortcuts: Widget = {
Expand All @@ -220,6 +225,7 @@ export const portalUserFeaturedShortcuts: PortalUser = {
mySpace: [mockCollectionWithFeaturedShortcuts],
displayName: 'BERNADETTE CAMPBELL',
theme: 'dark',
lastLoginAt: DateTime.now().toISO()!,
}

const mockCollection: Collection = {
Expand Down Expand Up @@ -260,13 +266,15 @@ export const portalUserCollectionLimit: PortalUser = {
mySpace: maxCollections,
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

export const portalUserAlmostAtCollectionLimit: PortalUser = {
userId: '[email protected]',
mySpace: almostMaxCollections,
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

export const portalUserCollectionLimitWithAllAdditionalWidgets: PortalUser = {
Expand All @@ -279,6 +287,7 @@ export const portalUserCollectionLimitWithAllAdditionalWidgets: PortalUser = {
],
displayName: 'BERNADETTE CAMPBELL',
theme: 'light',
lastLoginAt: DateTime.now().toISO()!,
}

export const portalUserNoCollections: PortalUser = {
Expand Down
107 changes: 106 additions & 1 deletion src/models/User.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Db, MongoClient } from 'mongodb'
import { DateTime, Settings } from 'luxon'

import User from './User'
import {
Expand All @@ -10,6 +11,9 @@ import { WIDGETS } from 'constants/index'
let connection: typeof MongoClient
let db: typeof Db

// Set test time to specific time
Settings.now = () => new Date('2024-01-01T10:11:03.000Z').valueOf()

describe('User model', () => {
beforeAll(async () => {
connection = await MongoClient.connect(process.env.MONGO_URL, {
Expand Down Expand Up @@ -37,6 +41,7 @@ describe('User model', () => {
],
displayName: 'Floyd King',
theme: 'light',
lastLoginAt: DateTime.now().toISO(),
}

const displayName = 'Floyd King'
Expand All @@ -45,7 +50,8 @@ describe('User model', () => {
[exampleCollection],
displayName,
'light',
{ db }
{ db },
DateTime.now()
)

const insertedUser = await User.findOne('testUserId', { db })
Expand All @@ -56,6 +62,41 @@ describe('User model', () => {
expect(insertedUser.mySpace[0].title).toContain(
expectedUser.mySpace[0].title
)
expect(insertedUser.lastLoginAt).toBe(expectedUser.lastLoginAt)
})

test('can create and find a new user without lastLoginAt', async () => {
const expectedUser = {
_id: expect.anything(),
userId: 'testUserId2',
mySpace: [
WIDGETS.FEATUREDSHORTCUTS,
WIDGETS.GUARDIANIDEAL,
exampleCollection1,
],
displayName: 'Floyd King',
theme: 'light',
lastLoginAt: DateTime.now().toISO(),
}

const displayName = 'Floyd King'
await User.createOne(
'testUserId2',
[exampleCollection],
displayName,
'light',
{ db }
)

const insertedUser = await User.findOne('testUserId2', { db })

expect(insertedUser.userId).toBe(expectedUser.userId)
expect(insertedUser.displayName).toBe(expectedUser.displayName)
expect(insertedUser.theme).toBe(expectedUser.theme)
expect(insertedUser.mySpace[0].title).toContain(
expectedUser.mySpace[0].title
)
expect(insertedUser.lastLoginAt).toBe(expectedUser.lastLoginAt)
})

test('returns null if finding a user that doesn’t exist', async () => {
Expand Down Expand Up @@ -205,4 +246,68 @@ describe('User model', () => {
).rejects.toThrow('UserModel Error: error in setMySpace no user found')
})
})

describe('lastLoginAt', () => {
test('can update the lastLoginAt of a user if no time passed in', async () => {
const expectedUser = {
_id: expect.anything(),
userId: 'testUserId',
mySpace: [exampleCollection1],
theme: 'dark',
lastLoginAt: DateTime.now().toISO()!,
}

const { userId } = expectedUser
await User.setLastLoginAt(
{
userId,
},
{
db,
}
)

const updatedUser = await User.findOne('testUserId', { db })
expect(updatedUser.lastLoginAt).toEqual(expectedUser.lastLoginAt)
})

test('can update the lastLoginAt of a user', async () => {
const expectedUser = {
_id: expect.anything(),
userId: 'testUserId',
mySpace: [exampleCollection1],
theme: 'dark',
lastLoginAt: DateTime.now().toISO()!,
}

const { userId } = expectedUser
await User.setLastLoginAt(
{
userId,
lastLoginAt: DateTime.now(),
},
{
db,
}
)

const updatedUser = await User.findOne('testUserId', { db })
expect(updatedUser.lastLoginAt).toEqual(expectedUser.lastLoginAt)
})

test('throws an error if user not found', async () => {
await expect(
User.setLastLoginAt(
{
userId: 'thisuserdoesnotexist',
},
{
db,
}
)
).rejects.toThrow(
'UserModel Error: error in setLastLoginAt no user found'
)
})
})
})
36 changes: 35 additions & 1 deletion src/models/User.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Context } from '@apollo/client'
import { DateTime } from 'luxon'

import { CollectionModel } from './Collection'
import { MySpaceModel } from './MySpace'
Expand All @@ -24,6 +25,11 @@ type EditTheme = {
theme: string
}

type EditLastLoginAt = {
userId: string
lastLoginAt?: DateTime
}

const UserModel = {
async findOne(userId: string, { db }: Context) {
const foundUser = await db.collection('users').findOne({ userId })
Expand All @@ -34,13 +40,15 @@ const UserModel = {
initCollections: CollectionRecords,
displayName: string,
theme: 'light' | 'dark',
{ db }: Context
{ db }: Context,
lastLoginAt: DateTime = DateTime.now()
) {
const newUser: PortalUser = {
userId,
mySpace: [],
displayName,
theme,
lastLoginAt: lastLoginAt.toISO()!,
}
// Default widgets when creating a new user
const widgets: WidgetInputType[] = [
Expand Down Expand Up @@ -70,6 +78,32 @@ const UserModel = {

return true
},
async setLastLoginAt(
{ userId, lastLoginAt = DateTime.now() }: EditLastLoginAt,
{ db }: Context
) {
const user = await UserModel.findOne(userId, { db })
if (!user) {
throw new Error('UserModel Error: error in setLastLoginAt no user found')
}

const query = {
userId: userId,
}

const updateDocument = {
$set: {
...user,
lastLoginAt: lastLoginAt.toISO()!,
},
}

const result = await db
.collection('users')
.findOneAndUpdate(query, updateDocument, { returnDocument: 'after' })

return result.value
},
async setDisplayName(
{ userId, displayName }: EditDisplayName,
{ db }: Context
Expand Down
19 changes: 15 additions & 4 deletions src/pages/api/graphql.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ApolloServer } from '@apollo/server'
import { startServerAndCreateNextHandler } from '@as-integrations/next'
import { gql } from 'graphql-tag'
import { GraphQLError } from 'graphql'
import { DateTime } from 'luxon'
import { typeDefs } from '../../schema'
import WeatherAPI from './dataSources/weather'
import KeystoneAPI from './dataSources/keystone'
Expand Down Expand Up @@ -88,12 +89,22 @@ export default startServerAndCreateNextHandler(apolloServer, {
// Check if user exists. If not, create new user
const foundUser = await User.findOne(userId, { db })

if (!foundUser) {
const lastLoginAt = DateTime.now()
if (foundUser) {
await User.setLastLoginAt({ userId, lastLoginAt }, { db })
} else {
try {
const initCollection = await getExampleCollection()
await User.createOne(userId, [initCollection], displayName, 'light', {
db,
})
await User.createOne(
userId,
[initCollection],
displayName,
'light',
{
db,
},
lastLoginAt
)
} catch (e) {
// TODO log error
// console.error('error in creating new user', e)
Expand Down
1 change: 1 addition & 0 deletions src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ export type PortalUser = {
mySpace: (Widget | Collection)[]
displayName: string
theme: string
lastLoginAt: string
}

export type SessionUser = SAMLUser & {
Expand Down

0 comments on commit c54ad4a

Please sign in to comment.