Browse Source

users: add user_view, make routes modular, add relations

Adds user_* child relations of user
Reorgs the folder structure a bit
Port over user_view with some basic functionality
Make user routes modular
main
eday 2 years ago
parent
commit
bcb0cc17b3
  1. 2
      .gitignore
  2. 1
      package.json
  3. 8
      src/app.ts
  4. 10
      src/enums.ts
  5. 2
      src/middleware/user.middleware.ts
  6. 12
      src/user/models/user-ban.model.ts
  7. 15
      src/user/models/user-mention.model.ts
  8. 15
      src/user/models/user-tag.model.ts
  9. 17
      src/user/models/user-token.model.ts
  10. 124
      src/user/models/user.model.ts
  11. 26
      src/user/user.controller.ts
  12. 40
      src/user/user.model.ts
  13. 21
      src/user/user.service.ts

2
.gitignore

@ -8,3 +8,5 @@ node_modules
*~
package-lock.json
yarn.lock

1
package.json

@ -22,6 +22,7 @@
"typescript": "^4.0.5"
},
"dependencies": {
"dayjs": "^1.9.5",
"express": "^4.17.1",
"jsonwebtoken": "^8.5.1",
"knex": "^0.21.10",

8
src/app.ts

@ -1,5 +1,5 @@
import express, { Request, Response, Application } from 'express';
import { getUsers, login } from './user/user.controller';
import { UserRoutes } from './user/user.controller';
import { getSite } from './site/site.controller';
import { getPosts } from './post/post.controller';
import { getPrivateMessages } from './private-message/private-message.controller';
@ -24,11 +24,7 @@ app.get('/api/v1/communities', getCommunities);
app.get('/api/v1/comments', getComments);
app.get('/api/v1/categories', getCategories);
app.get('/api/v1/users', getUsers);
app.get('/api/v1/users/login', login)
app.get('/api/v1/users/auth-test', requireUserToken, getUsers);
// Sample middleware route - pass 1 to view users, pass 2 to get error
app.get('/api/v1/users/:userId', requireAdminOrMod, getUsers);
app.use('/api/v1/users', UserRoutes);
app.use(errorHandler);

10
src/enums.ts

@ -0,0 +1,10 @@
export enum SortType {
Active,
Hot,
New,
TopDay,
TopWeek,
TopMonth,
TopYear,
TopAll
}

2
src/middleware/user.middleware.ts

@ -1,6 +1,6 @@
// Middleware pertaining to user auth or permissions
import { Request, Response, NextFunction } from 'express';
import { getUserById } from '../user/user.model';
import { getUserById } from '../user/models/user.model';
import { decodeToken } from '../lib/jwt';
import { Unauthorized_Error } from '../lib/errors';

12
src/user/models/user-ban.model.ts

@ -0,0 +1,12 @@
import { knex } from '../../database/knex';
import { User } from './user.model';
export interface UserBan {
id: number;
user_id: User['id'];
published: string;
}
export const getUserBan = async (userId: number): Promise<UserBan|undefined> => {
return await knex<UserBan>('user_ban').where('user_id', userId).first();
}

15
src/user/models/user-mention.model.ts

@ -0,0 +1,15 @@
import { knex } from '../../database/knex';
import { User } from './user.model';
import { Comment } from '../../comment/comment.model';
export interface UserMention {
id: number;
recipient_id: User['id'];
comment_id: Comment['id'];
read: boolean;
published: string;
}
export const getUserMentions = async (userId: number): Promise<UserMention[]> => {
return await knex<UserMention>('user_mention').where('recipient_id', userId);
}

15
src/user/models/user-tag.model.ts

@ -0,0 +1,15 @@
import { knex } from '../../database/knex';
import { User } from './user.model';
export interface UserTags {
pronouns: string[];
}
export interface UserTag {
user_id: User['id'];
tags: UserTags;
}
export const getUserTags = async (userId: number): Promise<UserTag|undefined> => {
return await knex<UserTag>('user_tag').where('user_id', userId).first();
}

17
src/user/models/user-token.model.ts

@ -0,0 +1,17 @@
import { knex } from '../../database/knex';
import { User } from './user.model';
export interface UserToken {
id: string;
user_id: User['id'];
token_hash: string;
created_at: string;
expires_at: string;
renewed_at: string;
is_revoked: boolean;
}
export const getUserTokens = async (userId: number): Promise<UserToken[]> => {
return await knex<UserToken>('hexbear.user_tokens').where('user_id', userId);
}

124
src/user/models/user.model.ts

@ -0,0 +1,124 @@
import { knex } from '../../database/knex';
import { SortType } from '../../enums';
import dayjs from 'dayjs';
import { off } from 'process';
export interface User {
id: number;
name: string;
preferred_username?: string;
password_encrypted: string;
email?: string;
avatar?: string;
admin: boolean;
banned: boolean;
published: string;
updated?: string;
show_nsfw: boolean;
theme: string;
default_sort_type: number;
default_listing_type: number;
lang: string;
show_avatars: boolean;
send_notifications_to_email: boolean;
matrix_user_id?: string;
actor_id: string;
bio?: string;
local: boolean;
private_key?: string;
public_key?: string;
last_refreshed_at: string;
sitemod: boolean;
banner?: string;
has_2fa: boolean;
inbox_disabled: boolean;
}
export const getUserList = (): Promise<User[]> => {
return knex<User>('user_');
}
export const getUserById = (userId: number): Promise<User|undefined> => {
return knex<User>('user_').where('id', userId).first();
}
export interface UserView {
readonly id: User['id'];
readonly actor_id: User['actor_id'];
readonly name: User['name'];
readonly preferred_username?: User['preferred_username'];
readonly avatar?: User['avatar'];
readonly banner?: User['banner'];
readonly email?: User['email']; // TODO this shouldn't be in this view
readonly matrix_user_id?: User['matrix_user_id'];
readonly bio?: User['bio'];
readonly local: User['local'];
readonly admin: User['admin'];
readonly sitemod: User['sitemod'];
readonly banned: User['banner'];
readonly show_avatars: User['show_avatars']; // TODO this is a setting, probably doesn't need to be here
readonly send_notifications_to_email: User['send_notifications_to_email']; // TODO also never used
readonly published: User['published'];
readonly number_of_posts: number,
readonly post_score: number,
readonly number_of_comments: number,
readonly comment_score: number,
readonly has_2fa: User['has_2fa'];
readonly inbox_disabled: User['inbox_disabled'];
}
export const getUserView = async (filter: Partial<User>, omitEmail: boolean = false, sortType?: SortType, limit?: number, offset?: number): Promise<UserView[]> => {
const users = await knex<UserView>('hexbear.user_view').modify(query => {
query.where(filter);
switch (sortType as SortType) {
case SortType.Active:
query.orderBy([{ column: 'comment_score', order: 'desc' }, { column: 'published', order: 'desc' }]);
break;
case SortType.Hot:
query.orderBy([{ column: 'comment_score', order: 'desc' }, { column: 'published', order: 'desc' }]);
break;
case SortType.New:
query.orderBy([ { column: 'published', order: 'desc' } ]);
break;
case SortType.TopAll:
query.orderBy([{ column: 'comment_score', order: 'desc' } ]);
break;
case SortType.TopYear:
query.andWhere('published', '>', dayjs().subtract(1, 'year').toISOString());
query.orderBy([{ column: 'comment_score', order: 'desc' } ]);
break;
case SortType.TopMonth:
query.andWhere('published', '>', dayjs().subtract(1, 'month').toISOString());
query.orderBy([{ column: 'comment_score', order: 'desc' } ]);
break;
case SortType.TopWeek:
query.andWhere('published', '>', dayjs().subtract(1, 'week').toISOString());
query.orderBy([{ column: 'comment_score', order: 'desc' } ]);
break;
case SortType.TopDay:
query.andWhere('published', '>', dayjs().subtract(1, 'day').toISOString());
query.orderBy([{ column: 'comment_score', order: 'desc' } ]);
break;
default:
query.orderBy([ { column: 'published', order: 'desc' } ]);
}
if (limit) {
query.limit(limit);
}
if (offset) {
query.offset(offset);
}
});
if (omitEmail) {
return users.map((u: UserView) => {
return { ...u, email: '' };
});
}
return users;
}

26
src/user/user.controller.ts

@ -1,13 +1,27 @@
import { Request, Response } from 'express';
import { getUserList } from './user.model';
import { Request, Response, Router } from 'express';
import { getUserList } from './models/user.model';
import { createToken } from '../lib/jwt';
import { getUserSecure } from './user.service';
import { requireAdminOrMod, requireUserToken } from '../middleware/user.middleware';
export const getUsers = async (req: Request, res: Response) => {
const router = Router();
router.get('/', async (req: Request, res: Response) => {
return res.json({ users: await getUserList() });
}
});
// This just makes a token for testing for now
export const login = async (req: Request, res: Response) => {
router.get('/login', async (req: Request, res: Response) => {
const jwt = await createToken({ user_id: 1, token_id: 'uuidv4', hostname: 'localhost' });
return res.json({ jwt });
}
});
router.get('/auth-test', requireUserToken, async (req: Request, res: Response) => {
return res.json({ users: await getUserList() });
});
router.get('/:userId', requireAdminOrMod, async (req: Request, res: Response) => {
return res.json({ user: await getUserSecure(parseInt(req.params.userId)) })
});
export const UserRoutes = router;

40
src/user/user.model.ts

@ -1,40 +0,0 @@
import { knex } from '../database/knex';
export interface User {
id: number;
name: string;
preferred_username?: string;
password_encrypted: string;
email?: string;
avatar?: string;
admin: boolean;
banned: boolean;
published: string;
updated?: string;
show_nsfw: boolean;
theme: string;
default_sort_type: number;
default_listing_type: number;
lang: string;
show_avatars: boolean;
send_notifications_to_email: boolean;
matrix_user_id?: string;
actor_id: string;
bio?: string;
local: boolean;
private_key?: string;
public_key?: string;
last_refreshed_at: string;
sitemod: boolean;
banner?: string;
has_2fa: boolean;
inbox_disabled: boolean;
}
export const getUserList = async (): Promise<User[]> => {
return await knex<User>('user_');
}
export const getUserById = async (userId: number): Promise<User|undefined> => {
return await knex<User>('user_').where('id', userId).first();
}

21
src/user/user.service.ts

@ -1,3 +1,18 @@
// This should house biz logic related to the user resource - example - UserDetailResponse from lemmy is composed of several resources
// so this file could have a fn that calls the needed user.model logic and imports logic either from a comment and post service or model
// kinda analogous to lemmy src/api/user.rs but hopefully with more code dedupe and slimmer model/crud code
import { getUserView, UserView } from './models/user.model';
export const getSiteAdmins = (): Promise<UserView[]> => {
return getUserView({ admin: true }, true);
}
export const getSiteMods = (): Promise<UserView[]> => {
return getUserView({ sitemod: true }, true);
}
export const getBanned = (): Promise<UserView[]> => {
return getUserView({ banned: true }, true);
}
export const getUserSecure = async (userId: UserView['id']): Promise<UserView> => {
const userList = await getUserView({ id: userId }, true);
return userList[0];
}
Loading…
Cancel
Save