Skip to content

Commit e1d5e64

Browse files
committed
Merge branch 'tony/userreview' of https://github.com/cuappdev/resell-backend into tony/userreview
2 parents ab9db56 + 1ad53a8 commit e1d5e64

19 files changed

+214
-52
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ dist/
44
node_modules/
55
package-lock.json
66
yarn.lock
7+
*.DS_Store
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import { JsonController, Post } from 'routing-controllers';
2-
import { ExpoPushMessage, PushTicket } from 'src/types';
1+
import { Body, CurrentUser, Delete, Get, JsonController, Params, Post } from 'routing-controllers';
2+
import { ExpoPushMessage, PushTicket, FindTokensRequest } from 'src/types';
33
import { NotifService } from '../../services/NotifService';
44

55
@JsonController('notif/')
@@ -11,7 +11,8 @@ export class NotifController {
1111
}
1212

1313
@Post()
14-
async sendPost( notifRequest : ExpoPushMessage ) {
15-
return this.notifService.sendNotifs(notifRequest, {});
14+
async sendNotif(@Body() findTokensRequest: FindTokensRequest) {
15+
return this.notifService.sendNotifs(findTokensRequest);
1616
}
17-
}
17+
}
18+

src/api/controllers/PostController.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,13 @@ export class PostController {
9898
return { isSaved: await this.postService.isSavedPost(user, params) };
9999
}
100100

101-
@Post('edit/postID/:id/')
101+
@Post('edit/postId/:id/')
102102
async editPrice(@Body() editPriceRequest: EditPostPriceRequest, @CurrentUser() user: UserModel, @Params() params: UuidParam): Promise<EditPriceResponse> {
103103
return { new_price: await (await this.postService.editPostPrice(user, params, editPriceRequest)).altered_price };
104104
}
105+
106+
@Get('similar/postId/:id/')
107+
async similarPosts(@Params() params: UuidParam): Promise<GetPostsResponse> {
108+
return { posts: await this.postService.similarPosts(params) };
109+
}
105110
}

src/api/controllers/UserController.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Body, CurrentUser, Get, JsonController, Param, Params, Post } from 'rou
22

33
import { UserModel } from '../../models/UserModel';
44
import { UserService } from '../../services/UserService';
5-
import { EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, SetAdminByEmailRequest } from '../../types';
5+
import { EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, SaveTokenRequest, SetAdminByEmailRequest } from '../../types';
66
import { UuidParam } from '../validators/GenericRequests';
77

88
@JsonController('user/')

src/api/controllers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import { PostController } from './PostController';
55
import { RequestController } from './RequestController';
66
import { UserController } from './UserController';
77
import { UserReviewController } from './UserReviewController';
8+
import { NotifController } from './NotifController'
89

910
export const controllers = [
1011
AuthController,
1112
FeedbackController,
1213
ImageController,
14+
NotifController,
1315
PostController,
1416
RequestController,
1517
UserController,

src/api/validators/AuthControllerRequests.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,7 @@ export class LoginRequest implements AuthRequest {
88

99
@IsDefined()
1010
user: GoogleLoginUser;
11+
12+
@IsDefined()
13+
deviceToken: string;
1114
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";
2+
3+
const TABLE_NAME = "User";
4+
5+
export class AddDeviceTokenForUser1682226973547 implements MigrationInterface {
6+
public async up(queryRunner: QueryRunner): Promise<void> {
7+
// add device token for user table
8+
await queryRunner.addColumn(
9+
TABLE_NAME,
10+
new TableColumn({
11+
name: "deviceTokens",
12+
type: "text[]",
13+
default: 'array[]::text[]',
14+
})
15+
);
16+
}
17+
18+
public async down(queryRunner: QueryRunner): Promise<void> {
19+
await queryRunner.dropColumn(TABLE_NAME, "deviceTokens");
20+
}
21+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import {MigrationInterface, QueryRunner, TableColumn} from "typeorm";
2+
3+
const TABLE_NAME = "UserSession"
4+
5+
export class AddDeviceTokenToSession1682461409919 implements MigrationInterface {
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
await queryRunner.addColumn(
9+
TABLE_NAME,
10+
new TableColumn({
11+
name: "deviceToken",
12+
type: "text",
13+
default: "''"
14+
})
15+
);
16+
}
17+
18+
public async down(queryRunner: QueryRunner): Promise<void> {
19+
await queryRunner.dropColumn(TABLE_NAME, "deviceToken");
20+
}
21+
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';
2+
3+
const TABLE_NAME = "User";
4+
5+
export class RemoveDeviceTokenForUser1683322987029 implements MigrationInterface {
6+
7+
public async up(queryRunner: QueryRunner): Promise<void> {
8+
await queryRunner.dropColumn(TABLE_NAME, "deviceTokens");
9+
}
10+
11+
public async down(queryRunner: QueryRunner): Promise<void> {
12+
// add device token for user table
13+
await queryRunner.addColumn(
14+
TABLE_NAME,
15+
new TableColumn({
16+
name: "deviceTokens",
17+
type: "text[]",
18+
default: 'array[]::text[]',
19+
})
20+
);
21+
}
22+
}

src/models/UserSessionModel.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ export class UserSessionModel {
1919
@Column()
2020
refreshToken: string;
2121

22+
@Column("text", { default: "", nullable: false, })
23+
deviceToken: string;
24+
2225
@ManyToOne(() => UserModel, user => user.sessions, { onDelete: "CASCADE" })
2326
@JoinColumn({ name: 'user' })
2427
user: UserModel;
@@ -43,6 +46,7 @@ export class UserSessionModel {
4346
active: this.expiresAt.getTime() > Date.now(),
4447
expiresAt: this.expiresAt.getTime(),
4548
refreshToken: this.refreshToken,
49+
deviceToken: this.deviceToken
4650
};
4751
}
4852

src/repositories/UserRepository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export class UserRepository extends AbstractRepository<UserModel> {
102102
username: string | undefined,
103103
photoUrl: string | undefined,
104104
venmoHandle: string | undefined,
105-
bio: string | undefined
105+
bio: string | undefined,
106106
): Promise<UserModel> {
107107
const existingUser = this.repository
108108
.createQueryBuilder("user")

src/repositories/UserSessionRepository.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ export class UserSessionRepository extends AbstractRepository<UserSessionModel>
100100
return session;
101101
}
102102

103+
public async updateSessionDeviceToken(session: UserSessionModel, deviceToken: string): Promise<UserSessionModel> {
104+
session.deviceToken = deviceToken;
105+
return await this.repository.save(session);
106+
}
107+
103108
public async verifySession(accessToken: string): Promise<boolean> {
104109
const session = await this.repository
105110
.createQueryBuilder("UserSessionModel")

src/services/AuthService.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,9 @@ export class AuthService {
6868
user = await userRepository.createUser(netid, netid, newUser.givenName, newUser.familyName,
6969
newUser.photoUrl, newUser.email, userId);
7070
}
71-
// since they're logging in, create a new session for them
72-
const session = sessionsRepository.createSession(user);
71+
//add device token
72+
const session = await sessionsRepository.createSession(user);
73+
sessionsRepository.updateSessionDeviceToken(session, authRequest.deviceToken)
7374
return session;
7475
});
7576
}

src/services/NotifService.ts

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,84 @@
1+
import { NotFoundError} from 'routing-controllers';
12
import { Service } from 'typedi';
2-
3-
import { ExpoPushMessage, PushTicket } from '../types';
3+
import { ExpoPushMessage, PushTicket, FindTokensRequest, NotifSent } from '../types';
44
import { Expo } from 'expo-server-sdk';
5-
5+
import { UserRepository } from 'src/repositories/UserRepository';
6+
import Repositories, { TransactionsManager } from '../repositories';
7+
import { EntityManager } from 'typeorm';
8+
import { InjectManager } from 'typeorm-typedi-extensions';
69
var accessToken = process.env['EXPO_ACCESS_TOKEN']
710
const expoServer = new Expo({ accessToken: accessToken });
811

12+
913
@Service()
1014
export class NotifService {
11-
// /**
15+
private transactions: TransactionsManager;
16+
17+
constructor(@InjectManager() entityManager: EntityManager) {
18+
this.transactions = new TransactionsManager(entityManager);
19+
}
20+
21+
// /**
1222
// * Takes an array of notifications and sends them to users in batches (called chunks)
1323
// * @param {NotifObject[]} notifs an array of notification objects
1424
// * @param {Object} expoServer the server object to connect with
1525
// */
16-
public sendNotifChunks = async (notifs : ExpoPushMessage[], expoServer : Expo) => {
17-
let chunks = expoServer.chunkPushNotifications(notifs);
18-
let tickets = [];
1926

20-
for (let chunk of chunks) {
21-
try {
22-
let ticketChunk = await expoServer.sendPushNotificationsAsync(chunk);
23-
// store tickets to check for notif status later
24-
tickets.push(...ticketChunk);
25-
} catch (err) {
26-
console.log("Error while sending notif chunk");
27+
public sendNotifChunks = async (notifs : ExpoPushMessage[], expoServer : Expo) => {
28+
let chunks = expoServer.chunkPushNotifications(notifs);
29+
let tickets = [];
30+
31+
for (let chunk of chunks) {
32+
try {
33+
let ticketChunk = await expoServer.sendPushNotificationsAsync(chunk);
34+
// store tickets to check for notif status later
35+
tickets.push(...ticketChunk);
36+
} catch (err) {
37+
console.log("Error while sending notif chunk");
38+
}
39+
console.log(tickets);
2740
}
28-
console.log(tickets);
2941
}
30-
}
3142

32-
public sendNotifs = (notif : ExpoPushMessage, json = {}) => {
33-
try {
34-
let notifs : ExpoPushMessage[] = [];
35-
notif.to.forEach(token => {
36-
notifs.push({
37-
to: notif.to,
38-
sound: notif.sound,
39-
title: notif.title,
40-
body: notif.body,
41-
data: notif.data
42-
})
43+
public async sendNotifs(request: FindTokensRequest) {
44+
return this.transactions.readWrite(async (transactionalEntityManager) => {
45+
const userRepository = Repositories.user(transactionalEntityManager);
46+
const userSessionRepository = Repositories.session(transactionalEntityManager);
47+
let user = await userRepository.getUserByEmail(request.email);
48+
if (!user) {
49+
throw new NotFoundError("User not found!");
50+
}
51+
const allDeviceTokens = [];
52+
const allsessions = await userSessionRepository.getSessionsByUserId(user.id);
53+
for (var sess of allsessions) {
54+
if (sess.deviceToken) {
55+
allDeviceTokens.push(sess.deviceToken); }
56+
}
57+
let notif: ExpoPushMessage=
58+
{
59+
to: allDeviceTokens,
60+
sound: 'default',
61+
title: request.title,
62+
body: request.body,
63+
data: request.data
64+
}
65+
try {
66+
let notifs : ExpoPushMessage[] = [];
67+
notif.to.forEach(token => {
68+
notifs.push({
69+
to: [token],
70+
sound: notif.sound,
71+
title: notif.title,
72+
body: notif.body,
73+
data: notif.data
74+
})
75+
})
76+
this.sendNotifChunks(notifs, expoServer)
77+
}
78+
79+
// Simply do nothing if the user has no tokens
80+
catch (err) {
81+
console.log(err) }
4382
})
44-
45-
this.sendNotifChunks(notifs, expoServer)
4683
}
47-
// Simply do nothing if the user has no tokens
48-
catch (err) { console.log(err) }
49-
}
5084
}

src/services/PostService.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,5 +223,33 @@ export class PostService {
223223
}, 0);
224224
return result;
225225
}
226+
227+
public async similarPosts(params: UuidParam): Promise<PostModel[]> {
228+
return this.transactions.readOnly(async (transactionalEntityManager) => {
229+
const postRepository = Repositories.post(transactionalEntityManager);
230+
const post = await postRepository.getPostById(params.id);
231+
if (!post) throw new NotFoundError('Post not found!');
232+
const allPosts = await postRepository.getAllPosts();
233+
let posts: PostModel[] = []
234+
const model = await getLoadedModel();
235+
for (const p of allPosts) {
236+
if (post.id != p.id) {
237+
const sentences = [
238+
post.title,
239+
p.title
240+
];
241+
await model.embed(sentences).then(async (embeddings: any) => {
242+
embeddings = embeddings.arraySync()
243+
const a = embeddings[0];
244+
const b = embeddings[1];
245+
if (this.similarity(a, b) >= 0.5) {
246+
posts.push(p)
247+
}
248+
});
249+
}
250+
}
251+
return posts
252+
});
253+
}
226254
}
227255

src/services/UserService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { InjectManager } from 'typeorm-typedi-extensions';
66
import { UuidParam } from '../api/validators/GenericRequests';
77
import { UserModel } from '../models/UserModel';
88
import Repositories, { TransactionsManager } from '../repositories';
9-
import { EditProfileRequest, SetAdminByEmailRequest } from '../types';
9+
import { EditProfileRequest, SaveTokenRequest, SetAdminByEmailRequest } from '../types';
1010
import { uploadImage } from '../utils/Requests';
1111

1212
@Service()

src/types/ApiRequests.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export interface GoogleLoginUser {
1616
export interface AuthRequest {
1717
idToken: string;
1818
user: GoogleLoginUser;
19+
deviceToken: string;
1920
}
2021

2122
export interface EditProfileRequest {
@@ -114,9 +115,21 @@ export interface CreateUserReviewRequest {
114115
// NOTIFICATION
115116
export interface ExpoPushMessage {
116117
to: string[];
117-
sound: 'default' | null;
118+
//special type for ExpoPushMessage
119+
sound: 'default';
118120
title: string;
119121
body: string;
120122
data: JSON;
121123
}
122124

125+
export interface SaveTokenRequest {
126+
token: string;
127+
userId: Uuid;
128+
}
129+
130+
export interface FindTokensRequest {
131+
email: string;
132+
title: string;
133+
body: string;
134+
data: JSON;
135+
}

0 commit comments

Comments
 (0)