Skip to content

Commit 2d37b08

Browse files
committed
server/models: refactor apiKey to its own file & add test
1 parent f56509f commit 2d37b08

File tree

3 files changed

+89
-36
lines changed

3 files changed

+89
-36
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import mongoose, { model } from 'mongoose';
2+
import { apiKeySchema } from '../apiKey';
3+
4+
const ApiKey = model('ApiKey', apiKeySchema);
5+
6+
describe('ApiKey schema', () => {
7+
it('should set default label and generate an id virtual', () => {
8+
const doc = new ApiKey({ hashedKey: 'supersecret' });
9+
10+
expect(doc.label).toBe('API Key');
11+
12+
// _id is always generated
13+
expect(doc._id).toBeInstanceOf(mongoose.Types.ObjectId);
14+
15+
// id virtual is stringified _id
16+
expect(typeof doc.id).toBe('string');
17+
expect(doc.id).toEqual(doc._id.toHexString());
18+
});
19+
20+
it('should exclude hashedKey from toObject and toJSON', () => {
21+
const doc = new ApiKey({ hashedKey: 'supersecret', label: 'Test Key' });
22+
23+
const obj = doc.toObject();
24+
const json = doc.toJSON();
25+
26+
expect(obj).not.toHaveProperty('hashedKey');
27+
expect(json).not.toHaveProperty('hashedKey');
28+
});
29+
30+
it('should include id, label, lastUsedAt, createdAt and updatedAt in output', () => {
31+
const now = new Date();
32+
const doc = new ApiKey({
33+
hashedKey: 'supersecret',
34+
label: 'My Key',
35+
lastUsedAt: now
36+
});
37+
38+
// mock timestamps (normally set on save)
39+
doc.createdAt = new Date('2025-01-01T00:00:00Z');
40+
doc.updatedAt = new Date('2025-01-02T00:00:00Z');
41+
42+
const obj = doc.toObject();
43+
44+
expect(obj).toMatchObject({
45+
id: expect.any(String),
46+
label: 'My Key',
47+
lastUsedAt: now,
48+
createdAt: new Date('2025-01-01T00:00:00Z')
49+
});
50+
});
51+
});

server/models/apiKey.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import mongoose, { Schema } from 'mongoose';
2+
3+
export const apiKeySchema = new Schema(
4+
{
5+
label: { type: String, default: 'API Key' },
6+
lastUsedAt: { type: Date },
7+
hashedKey: { type: String, required: true }
8+
},
9+
{ timestamps: true, _id: true }
10+
);
11+
12+
apiKeySchema.virtual('id').get(function getApiKeyId() {
13+
return this._id.toHexString();
14+
});
15+
16+
/**
17+
* When serialising an APIKey instance, the `hashedKey` field
18+
* should never be exposed to the client. So we only return
19+
* a safe list of fields when toObject and toJSON are called.
20+
*/
21+
function apiKeyMetadata(doc, ret, options) {
22+
return {
23+
id: doc.id,
24+
label: doc.label,
25+
lastUsedAt: doc.lastUsedAt,
26+
createdAt: doc.createdAt
27+
};
28+
}
29+
30+
apiKeySchema.set('toObject', {
31+
transform: apiKeyMetadata
32+
});
33+
34+
apiKeySchema.set('toJSON', {
35+
virtuals: true,
36+
transform: apiKeyMetadata
37+
});

server/models/user.js

Lines changed: 1 addition & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import mongoose from 'mongoose';
22
import bcrypt from 'bcryptjs';
3+
import { apiKeySchema } from './apiKey';
34

45
const EmailConfirmationStates = {
56
Verified: 'verified',
@@ -9,42 +10,6 @@ const EmailConfirmationStates = {
910

1011
const { Schema } = mongoose;
1112

12-
const apiKeySchema = new Schema(
13-
{
14-
label: { type: String, default: 'API Key' },
15-
lastUsedAt: { type: Date },
16-
hashedKey: { type: String, required: true }
17-
},
18-
{ timestamps: true, _id: true }
19-
);
20-
21-
apiKeySchema.virtual('id').get(function getApiKeyId() {
22-
return this._id.toHexString();
23-
});
24-
25-
/**
26-
* When serialising an APIKey instance, the `hashedKey` field
27-
* should never be exposed to the client. So we only return
28-
* a safe list of fields when toObject and toJSON are called.
29-
*/
30-
function apiKeyMetadata(doc, ret, options) {
31-
return {
32-
id: doc.id,
33-
label: doc.label,
34-
lastUsedAt: doc.lastUsedAt,
35-
createdAt: doc.createdAt
36-
};
37-
}
38-
39-
apiKeySchema.set('toObject', {
40-
transform: apiKeyMetadata
41-
});
42-
43-
apiKeySchema.set('toJSON', {
44-
virtuals: true,
45-
transform: apiKeyMetadata
46-
});
47-
4813
const userSchema = new Schema(
4914
{
5015
name: { type: String, default: '' },

0 commit comments

Comments
 (0)