Skip to content

Commit

Permalink
Merge branch 'main' into tony/userreview
Browse files Browse the repository at this point in the history
  • Loading branch information
akmatchev authored Sep 11, 2023
2 parents dc8ffc1 + 6c845ee commit 1ad53a8
Show file tree
Hide file tree
Showing 19 changed files with 214 additions and 52 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ dist/
node_modules/
package-lock.json
yarn.lock
*.DS_Store
11 changes: 6 additions & 5 deletions src/api/controllers/NotifController.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { JsonController, Post } from 'routing-controllers';
import { ExpoPushMessage, PushTicket } from 'src/types';
import { Body, CurrentUser, Delete, Get, JsonController, Params, Post } from 'routing-controllers';
import { ExpoPushMessage, PushTicket, FindTokensRequest } from 'src/types';
import { NotifService } from '../../services/NotifService';

@JsonController('notif/')
Expand All @@ -11,7 +11,8 @@ export class NotifController {
}

@Post()
async sendPost( notifRequest : ExpoPushMessage ) {
return this.notifService.sendNotifs(notifRequest, {});
async sendNotif(@Body() findTokensRequest: FindTokensRequest) {
return this.notifService.sendNotifs(findTokensRequest);
}
}
}

7 changes: 6 additions & 1 deletion src/api/controllers/PostController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,13 @@ export class PostController {
return { isSaved: await this.postService.isSavedPost(user, params) };
}

@Post('edit/postID/:id/')
@Post('edit/postId/:id/')
async editPrice(@Body() editPriceRequest: EditPostPriceRequest, @CurrentUser() user: UserModel, @Params() params: UuidParam): Promise<EditPriceResponse> {
return { new_price: await (await this.postService.editPostPrice(user, params, editPriceRequest)).altered_price };
}

@Get('similar/postId/:id/')
async similarPosts(@Params() params: UuidParam): Promise<GetPostsResponse> {
return { posts: await this.postService.similarPosts(params) };
}
}
2 changes: 1 addition & 1 deletion src/api/controllers/UserController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Body, CurrentUser, Get, JsonController, Param, Params, Post } from 'rou

import { UserModel } from '../../models/UserModel';
import { UserService } from '../../services/UserService';
import { EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, SetAdminByEmailRequest } from '../../types';
import { EditProfileRequest, GetUserByEmailRequest, GetUserResponse, GetUsersResponse, SaveTokenRequest, SetAdminByEmailRequest } from '../../types';
import { UuidParam } from '../validators/GenericRequests';

@JsonController('user/')
Expand Down
2 changes: 2 additions & 0 deletions src/api/controllers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { PostController } from './PostController';
import { RequestController } from './RequestController';
import { UserController } from './UserController';
import { UserReviewController } from './UserReviewController';
import { NotifController } from './NotifController'

export const controllers = [
AuthController,
FeedbackController,
ImageController,
NotifController,
PostController,
RequestController,
UserController,
Expand Down
3 changes: 3 additions & 0 deletions src/api/validators/AuthControllerRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ export class LoginRequest implements AuthRequest {

@IsDefined()
user: GoogleLoginUser;

@IsDefined()
deviceToken: string;
}
21 changes: 21 additions & 0 deletions src/migrations/0001_AddDeviceTokenForUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner, TableColumn } from "typeorm";

const TABLE_NAME = "User";

export class AddDeviceTokenForUser1682226973547 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
// add device token for user table
await queryRunner.addColumn(
TABLE_NAME,
new TableColumn({
name: "deviceTokens",
type: "text[]",
default: 'array[]::text[]',
})
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn(TABLE_NAME, "deviceTokens");
}
}
22 changes: 22 additions & 0 deletions src/migrations/0003_AddDeviceTokenToSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {MigrationInterface, QueryRunner, TableColumn} from "typeorm";

const TABLE_NAME = "UserSession"

export class AddDeviceTokenToSession1682461409919 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.addColumn(
TABLE_NAME,
new TableColumn({
name: "deviceToken",
type: "text",
default: "''"
})
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn(TABLE_NAME, "deviceToken");
}

}
22 changes: 22 additions & 0 deletions src/migrations/0004_RemoveDeviceTokenForUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm';

const TABLE_NAME = "User";

export class RemoveDeviceTokenForUser1683322987029 implements MigrationInterface {

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropColumn(TABLE_NAME, "deviceTokens");
}

public async down(queryRunner: QueryRunner): Promise<void> {
// add device token for user table
await queryRunner.addColumn(
TABLE_NAME,
new TableColumn({
name: "deviceTokens",
type: "text[]",
default: 'array[]::text[]',
})
);
}
}
4 changes: 4 additions & 0 deletions src/models/UserSessionModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class UserSessionModel {
@Column()
refreshToken: string;

@Column("text", { default: "", nullable: false, })
deviceToken: string;

@ManyToOne(() => UserModel, user => user.sessions, { onDelete: "CASCADE" })
@JoinColumn({ name: 'user' })
user: UserModel;
Expand All @@ -43,6 +46,7 @@ export class UserSessionModel {
active: this.expiresAt.getTime() > Date.now(),
expiresAt: this.expiresAt.getTime(),
refreshToken: this.refreshToken,
deviceToken: this.deviceToken
};
}

Expand Down
2 changes: 1 addition & 1 deletion src/repositories/UserRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class UserRepository extends AbstractRepository<UserModel> {
username: string | undefined,
photoUrl: string | undefined,
venmoHandle: string | undefined,
bio: string | undefined
bio: string | undefined,
): Promise<UserModel> {
const existingUser = this.repository
.createQueryBuilder("user")
Expand Down
5 changes: 5 additions & 0 deletions src/repositories/UserSessionRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ export class UserSessionRepository extends AbstractRepository<UserSessionModel>
return session;
}

public async updateSessionDeviceToken(session: UserSessionModel, deviceToken: string): Promise<UserSessionModel> {
session.deviceToken = deviceToken;
return await this.repository.save(session);
}

public async verifySession(accessToken: string): Promise<boolean> {
const session = await this.repository
.createQueryBuilder("UserSessionModel")
Expand Down
5 changes: 3 additions & 2 deletions src/services/AuthService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ export class AuthService {
user = await userRepository.createUser(netid, netid, newUser.givenName, newUser.familyName,
newUser.photoUrl, newUser.email, userId);
}
// since they're logging in, create a new session for them
const session = sessionsRepository.createSession(user);
//add device token
const session = await sessionsRepository.createSession(user);
sessionsRepository.updateSessionDeviceToken(session, authRequest.deviceToken)
return session;
});
}
Expand Down
98 changes: 66 additions & 32 deletions src/services/NotifService.ts
Original file line number Diff line number Diff line change
@@ -1,50 +1,84 @@
import { NotFoundError} from 'routing-controllers';
import { Service } from 'typedi';

import { ExpoPushMessage, PushTicket } from '../types';
import { ExpoPushMessage, PushTicket, FindTokensRequest, NotifSent } from '../types';
import { Expo } from 'expo-server-sdk';

import { UserRepository } from 'src/repositories/UserRepository';
import Repositories, { TransactionsManager } from '../repositories';
import { EntityManager } from 'typeorm';
import { InjectManager } from 'typeorm-typedi-extensions';
var accessToken = process.env['EXPO_ACCESS_TOKEN']
const expoServer = new Expo({ accessToken: accessToken });


@Service()
export class NotifService {
// /**
private transactions: TransactionsManager;

constructor(@InjectManager() entityManager: EntityManager) {
this.transactions = new TransactionsManager(entityManager);
}

// /**
// * Takes an array of notifications and sends them to users in batches (called chunks)
// * @param {NotifObject[]} notifs an array of notification objects
// * @param {Object} expoServer the server object to connect with
// */
public sendNotifChunks = async (notifs : ExpoPushMessage[], expoServer : Expo) => {
let chunks = expoServer.chunkPushNotifications(notifs);
let tickets = [];

for (let chunk of chunks) {
try {
let ticketChunk = await expoServer.sendPushNotificationsAsync(chunk);
// store tickets to check for notif status later
tickets.push(...ticketChunk);
} catch (err) {
console.log("Error while sending notif chunk");
public sendNotifChunks = async (notifs : ExpoPushMessage[], expoServer : Expo) => {
let chunks = expoServer.chunkPushNotifications(notifs);
let tickets = [];

for (let chunk of chunks) {
try {
let ticketChunk = await expoServer.sendPushNotificationsAsync(chunk);
// store tickets to check for notif status later
tickets.push(...ticketChunk);
} catch (err) {
console.log("Error while sending notif chunk");
}
console.log(tickets);
}
console.log(tickets);
}
}

public sendNotifs = (notif : ExpoPushMessage, json = {}) => {
try {
let notifs : ExpoPushMessage[] = [];
notif.to.forEach(token => {
notifs.push({
to: notif.to,
sound: notif.sound,
title: notif.title,
body: notif.body,
data: notif.data
})
public async sendNotifs(request: FindTokensRequest) {
return this.transactions.readWrite(async (transactionalEntityManager) => {
const userRepository = Repositories.user(transactionalEntityManager);
const userSessionRepository = Repositories.session(transactionalEntityManager);
let user = await userRepository.getUserByEmail(request.email);
if (!user) {
throw new NotFoundError("User not found!");
}
const allDeviceTokens = [];
const allsessions = await userSessionRepository.getSessionsByUserId(user.id);
for (var sess of allsessions) {
if (sess.deviceToken) {
allDeviceTokens.push(sess.deviceToken); }
}
let notif: ExpoPushMessage=
{
to: allDeviceTokens,
sound: 'default',
title: request.title,
body: request.body,
data: request.data
}
try {
let notifs : ExpoPushMessage[] = [];
notif.to.forEach(token => {
notifs.push({
to: [token],
sound: notif.sound,
title: notif.title,
body: notif.body,
data: notif.data
})
})
this.sendNotifChunks(notifs, expoServer)
}

// Simply do nothing if the user has no tokens
catch (err) {
console.log(err) }
})

this.sendNotifChunks(notifs, expoServer)
}
// Simply do nothing if the user has no tokens
catch (err) { console.log(err) }
}
}
28 changes: 28 additions & 0 deletions src/services/PostService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,5 +223,33 @@ export class PostService {
}, 0);
return result;
}

public async similarPosts(params: UuidParam): Promise<PostModel[]> {
return this.transactions.readOnly(async (transactionalEntityManager) => {
const postRepository = Repositories.post(transactionalEntityManager);
const post = await postRepository.getPostById(params.id);
if (!post) throw new NotFoundError('Post not found!');
const allPosts = await postRepository.getAllPosts();
let posts: PostModel[] = []
const model = await getLoadedModel();
for (const p of allPosts) {
if (post.id != p.id) {
const sentences = [
post.title,
p.title
];
await model.embed(sentences).then(async (embeddings: any) => {
embeddings = embeddings.arraySync()
const a = embeddings[0];
const b = embeddings[1];
if (this.similarity(a, b) >= 0.5) {
posts.push(p)
}
});
}
}
return posts
});
}
}

2 changes: 1 addition & 1 deletion src/services/UserService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { InjectManager } from 'typeorm-typedi-extensions';
import { UuidParam } from '../api/validators/GenericRequests';
import { UserModel } from '../models/UserModel';
import Repositories, { TransactionsManager } from '../repositories';
import { EditProfileRequest, SetAdminByEmailRequest } from '../types';
import { EditProfileRequest, SaveTokenRequest, SetAdminByEmailRequest } from '../types';
import { uploadImage } from '../utils/Requests';

@Service()
Expand Down
15 changes: 14 additions & 1 deletion src/types/ApiRequests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface GoogleLoginUser {
export interface AuthRequest {
idToken: string;
user: GoogleLoginUser;
deviceToken: string;
}

export interface EditProfileRequest {
Expand Down Expand Up @@ -114,9 +115,21 @@ export interface CreateUserReviewRequest {
// NOTIFICATION
export interface ExpoPushMessage {
to: string[];
sound: 'default' | null;
//special type for ExpoPushMessage
sound: 'default';
title: string;
body: string;
data: JSON;
}

export interface SaveTokenRequest {
token: string;
userId: Uuid;
}

export interface FindTokensRequest {
email: string;
title: string;
body: string;
data: JSON;
}
Loading

0 comments on commit 1ad53a8

Please sign in to comment.