Skip to content

Commit

Permalink
Server: Added API end points to manage users
Browse files Browse the repository at this point in the history
  • Loading branch information
laurent22 committed May 18, 2021
1 parent daaaa13 commit 77b284f
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 3 deletions.
Binary file modified packages/server/schema.sqlite
Binary file not shown.
2 changes: 0 additions & 2 deletions packages/server/src/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ export interface User extends WithDates, WithUuid {
is_admin?: number;
max_item_size?: number;
can_share?: number;
max_share_recipients?: number;
}

export interface Session extends WithDates, WithUuid {
Expand Down Expand Up @@ -382,7 +381,6 @@ export const databaseSchema: DatabaseTables = {
created_time: { type: 'string' },
max_item_size: { type: 'number' },
can_share: { type: 'number' },
max_share_recipients: { type: 'number' },
},
sessions: {
id: { type: 'string' },
Expand Down
80 changes: 80 additions & 0 deletions packages/server/src/routes/api/users.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { User } from '../../db';
import { deleteApi, getApi, patchApi, postApi } from '../../utils/testing/apiUtils';
import { beforeAllDb, afterAllTests, beforeEachDb, createUserAndSession, models } from '../../utils/testing/testUtils';

describe('api_users', function() {

beforeAll(async () => {
await beforeAllDb('api_users');
});

afterAll(async () => {
await afterAllTests();
});

beforeEach(async () => {
await beforeEachDb();
});

test('should create a user', async function() {
const { session: adminSession } = await createUserAndSession(1, true);

const userToSave: User = {
full_name: 'Toto',
email: '[email protected]',
max_item_size: 1000,
can_share: 0,
};

await postApi(adminSession.id, 'users', userToSave);

const savedUser = await models().user().loadByEmail('[email protected]');
expect(savedUser.full_name).toBe('Toto');
expect(savedUser.email).toBe('[email protected]');
expect(savedUser.can_share).toBe(0);
expect(savedUser.max_item_size).toBe(1000);
});

test('should patch a user', async function() {
const { session: adminSession } = await createUserAndSession(1, true);
const { user } = await createUserAndSession(2);

await patchApi(adminSession.id, `users/${user.id}`, {
max_item_size: 1000,
});

const savedUser = await models().user().load(user.id);
expect(savedUser.max_item_size).toBe(1000);
});

test('should get a user', async function() {
const { session: adminSession } = await createUserAndSession(1, true);
const { user } = await createUserAndSession(2);

const fetchedUser: User = await getApi(adminSession.id, `users/${user.id}`);

expect(fetchedUser.id).toBe(user.id);
expect(fetchedUser.email).toBe(user.email);
});

test('should delete a user', async function() {
const { session: adminSession } = await createUserAndSession(1, true);
const { user } = await createUserAndSession(2);

await deleteApi(adminSession.id, `users/${user.id}`);

const loadedUser = await models().user().load(user.id);
expect(loadedUser).toBeFalsy();
});

test('should list users', async function() {
const { session: adminSession } = await createUserAndSession(1, true);
await createUserAndSession(2);
await createUserAndSession(3);

const results: any = await getApi(adminSession.id, 'users');
console.info(results);
expect(results.items.length).toBe(3);
});

});
61 changes: 61 additions & 0 deletions packages/server/src/routes/api/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { User } from '../../db';
import { bodyFields } from '../../utils/requestUtils';
import { SubPath } from '../../utils/routeUtils';
import Router from '../../utils/Router';
import { AppContext } from '../../utils/types';
import { ErrorNotFound } from '../../utils/errors';
import { AclAction } from '../../models/BaseModel';
import uuidgen from '../../utils/uuidgen';

const router = new Router();

async function fetchUser(path: SubPath, ctx: AppContext): Promise<User> {
const user = await ctx.models.user().load(path.id);
if (!user) throw new ErrorNotFound(`No user with ID ${path.id}`);
return user;
}

async function postedUserFromContext(ctx: AppContext): Promise<User> {
return ctx.models.user().fromApiInput(await bodyFields<any>(ctx.req));
}

router.get('api/users/:id', async (path: SubPath, ctx: AppContext) => {
const user = await fetchUser(path, ctx);
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Read, user);
return user;
});

router.post('api/users', async (_path: SubPath, ctx: AppContext) => {
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Create);
const user = await postedUserFromContext(ctx);

// We set a random password because it's required, but user will have to
// set it by clicking on the confirmation link.
user.password = uuidgen();
const output = await ctx.models.user().save(user);
return ctx.models.user().toApiOutput(output);
});

router.get('api/users', async (_path: SubPath, ctx: AppContext) => {
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.List);

return {
items: await ctx.models.user().all(),
has_more: false,
};
});

router.del('api/users/:id', async (path: SubPath, ctx: AppContext) => {
const user = await fetchUser(path, ctx);
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Delete, user);
await ctx.models.user().delete(user.id);
});

router.patch('api/users/:id', async (path: SubPath, ctx: AppContext) => {
const user = await fetchUser(path, ctx);
await ctx.models.user().checkIfAllowed(ctx.owner, AclAction.Update, user);
const postedUser = await postedUserFromContext(ctx);
await ctx.models.user().save({ id: user.id, ...postedUser });
});

export default router;
2 changes: 2 additions & 0 deletions packages/server/src/routes/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import apiEvents from './api/events';
import apiItems from './api/items';
import apiPing from './api/ping';
import apiSessions from './api/sessions';
import apiUsers from './api/users';
import apiShares from './api/shares';
import apiShareUsers from './api/share_users';

Expand All @@ -27,6 +28,7 @@ const routes: Routers = {
'api/sessions': apiSessions,
'api/share_users': apiShareUsers,
'api/shares': apiShares,
'api/users': apiUsers,

'changes': indexChanges,
'home': indexHome,
Expand Down
2 changes: 1 addition & 1 deletion packages/server/src/utils/testing/testUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export const createUserAndSession = async function(index: number = 1, isAdmin: b
const session = await models().session().authenticate(options.email, options.password);

return {
user: user,
user: await models().user().load(user.id),
session: session,
};
};
Expand Down

0 comments on commit 77b284f

Please sign in to comment.