mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
feat(api): add updateCount field to user (#55349)
Co-authored-by: Oliver Eyton-Williams <ojeytonwilliams@gmail.com>
This commit is contained in:
@@ -55,11 +55,6 @@ type ProfileUI {
|
||||
showTimeLine Boolean? // Undefined
|
||||
}
|
||||
|
||||
// Currently unused, so commented out.
|
||||
// type ProgressTimestamp {
|
||||
// timestamp Float
|
||||
// }
|
||||
|
||||
type SavedChallengeFile {
|
||||
contents String
|
||||
ext String
|
||||
@@ -74,11 +69,11 @@ type SavedChallenge {
|
||||
lastSavedDate Float
|
||||
}
|
||||
|
||||
/// Corresponds to the `user` collection.
|
||||
model user {
|
||||
id String @id @default(auto()) @map("_id") @db.ObjectId
|
||||
about String
|
||||
acceptedPrivacyTerms Boolean
|
||||
// badges Json? // Undefined | { coreTeam [][] } removed, as new API never uses it.
|
||||
completedChallenges CompletedChallenge[]
|
||||
completedExams CompletedExam[] // Undefined
|
||||
currentChallengeId String?
|
||||
@@ -88,7 +83,6 @@ model user {
|
||||
emailVerified Boolean
|
||||
emailVerifyTTL DateTime? // Null | Undefined
|
||||
externalId String
|
||||
// github String? Removed, because value was only ever found to be Null
|
||||
githubProfile String? // Undefined
|
||||
isApisMicroservicesCert Boolean? // Undefined
|
||||
isBackEndCert Boolean? // Undefined
|
||||
@@ -127,13 +121,20 @@ model user {
|
||||
portfolio Portfolio[]
|
||||
profileUI ProfileUI? // Undefined
|
||||
progressTimestamps Json? // ProgressTimestamp[] | Null[] | Int64[] | Double[] - TODO: NORMALIZE
|
||||
// rand Float? // Undefined removed, as new API never uses it.
|
||||
/// A random number between 0 and 1.
|
||||
///
|
||||
/// Valuable for selectively performing random logic.
|
||||
rand Float?
|
||||
savedChallenges SavedChallenge[] // Undefined | SavedChallenge[]
|
||||
sendQuincyEmail Boolean
|
||||
theme String? // Undefined
|
||||
timezone String? // Undefined
|
||||
twitter String? // Null | Undefined
|
||||
unsubscribeId String
|
||||
/// Used to track the number of times the user's record was written to.
|
||||
///
|
||||
/// This has the main benefit of allowing concurrent ops to check for race conditions.
|
||||
updateCount Int? @default(0)
|
||||
username String // TODO(Post-MVP): make this unique
|
||||
usernameDisplay String? // Undefined
|
||||
verificationToken String? // Undefined
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import { defaultUserEmail, setupServer } from '../../jest.utils';
|
||||
import { createUserInput } from '../utils/create-user';
|
||||
|
||||
describe('prisma client extensions', () => {
|
||||
setupServer();
|
||||
|
||||
beforeEach(async () => {
|
||||
await fastifyTestInstance.prisma.user.deleteMany({
|
||||
where: { email: defaultUserEmail }
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await fastifyTestInstance.prisma.user.deleteMany({
|
||||
where: { email: defaultUserEmail }
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCount', () => {
|
||||
it('should default to 0', async () => {
|
||||
const user = await fastifyTestInstance.prisma.user.create({
|
||||
data: createUserInput(defaultUserEmail)
|
||||
});
|
||||
|
||||
expect(user).toMatchObject({
|
||||
updateCount: 0
|
||||
});
|
||||
});
|
||||
|
||||
it('should increment by one for updates and creates', async () => {
|
||||
const user = await fastifyTestInstance.prisma.user.create({
|
||||
data: createUserInput(defaultUserEmail)
|
||||
});
|
||||
|
||||
const updateUser = await fastifyTestInstance.prisma.user.update({
|
||||
where: { id: user.id },
|
||||
data: { username: 'any-change' }
|
||||
});
|
||||
|
||||
expect(updateUser).toMatchObject({
|
||||
username: 'any-change',
|
||||
updateCount: 1
|
||||
});
|
||||
|
||||
await fastifyTestInstance.prisma.user.updateMany({
|
||||
where: { id: user.id },
|
||||
// Even no change to values updates the updateCount
|
||||
data: { username: 'any-change' }
|
||||
});
|
||||
|
||||
const updateManyUser = await fastifyTestInstance.prisma.user.findUnique({
|
||||
where: { id: user.id }
|
||||
});
|
||||
|
||||
expect(updateManyUser).toMatchObject({
|
||||
username: 'any-change',
|
||||
updateCount: 2
|
||||
});
|
||||
|
||||
const upsertUser = await fastifyTestInstance.prisma.user.upsert({
|
||||
where: { id: user.id },
|
||||
create: createUserInput(defaultUserEmail),
|
||||
update: { username: 'upser-user' }
|
||||
});
|
||||
|
||||
expect(upsertUser).toMatchObject({
|
||||
username: 'upser-user',
|
||||
updateCount: 3
|
||||
});
|
||||
});
|
||||
|
||||
it("should not increment for 'find' queries", async () => {
|
||||
const user = await fastifyTestInstance.prisma.user.create({
|
||||
data: createUserInput(defaultUserEmail)
|
||||
});
|
||||
|
||||
const findUniqueUser = await fastifyTestInstance.prisma.user.findUnique({
|
||||
where: { id: user.id }
|
||||
});
|
||||
|
||||
expect(findUniqueUser).toMatchObject({
|
||||
updateCount: 0
|
||||
});
|
||||
|
||||
const findManyUsers = await fastifyTestInstance.prisma.user.findMany();
|
||||
|
||||
expect(findManyUsers).toHaveLength(1);
|
||||
expect(findManyUsers[0]).toMatchObject({
|
||||
updateCount: 0
|
||||
});
|
||||
|
||||
const findFirstUser = await fastifyTestInstance.prisma.user.findFirst();
|
||||
|
||||
expect(findFirstUser).toMatchObject({
|
||||
updateCount: 0
|
||||
});
|
||||
|
||||
const findRawUser = await fastifyTestInstance.prisma.user.findRaw({
|
||||
filter: { email: defaultUserEmail }
|
||||
});
|
||||
|
||||
expect(findRawUser[0]).toMatchObject({
|
||||
updateCount: 0
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+37
-7
@@ -7,18 +7,20 @@ import { MONGOHQ_URL } from '../utils/env';
|
||||
|
||||
declare module 'fastify' {
|
||||
interface FastifyInstance {
|
||||
prisma: PrismaClient;
|
||||
prisma: ReturnType<typeof extendClient>;
|
||||
}
|
||||
}
|
||||
|
||||
const prismaPlugin: FastifyPluginAsync = fp(async (server, _options) => {
|
||||
const prisma = new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
url: MONGOHQ_URL
|
||||
const prisma = extendClient(
|
||||
new PrismaClient({
|
||||
datasources: {
|
||||
db: {
|
||||
url: MONGOHQ_URL
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
await prisma.$connect();
|
||||
|
||||
@@ -29,4 +31,32 @@ const prismaPlugin: FastifyPluginAsync = fp(async (server, _options) => {
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: It would be nice to split this up into multiple update functions,
|
||||
// but the types are a pain.
|
||||
// TODO: Multiple extended clients can be used for different restrictions (e.g. session vs non-session users)
|
||||
// TODO: Could be used to add other _easily forgotten_ fields like `progressTimestamp`
|
||||
function extendClient(prisma: PrismaClient) {
|
||||
return prisma.$extends({
|
||||
query: {
|
||||
user: {
|
||||
async update({ args, query }) {
|
||||
args.data.updateCount = { increment: 1 };
|
||||
return query(args);
|
||||
},
|
||||
async updateMany({ args, query }) {
|
||||
args.data.updateCount = { increment: 1 };
|
||||
return query(args);
|
||||
},
|
||||
async upsert({ args, query }) {
|
||||
args.update.updateCount = { increment: 1 };
|
||||
return query(args);
|
||||
}
|
||||
// NOTE: raw ops are untouched, as it is meant to be a direct passthrough to mongodb
|
||||
// async findRaw({ model, operation, args, query }) {}
|
||||
// async aggregateRaw({ model, operation, args, query }) {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export default prismaPlugin;
|
||||
|
||||
Reference in New Issue
Block a user