Skip to content

Commit

Permalink
Release dev to master: recnet v1.14.2 / recnet-api v1.7.0 (#323)
Browse files Browse the repository at this point in the history
## RecNet auto-release action
This is an auto-generated PR by recnet-release-action 🤖
Please make sure to test your changes in staging before merging. 
## Related Issues
- #60
- #324
- #317
- #253
- #254
## Related PRs
- #322
- #325
- #320
- #311
## Staging links
recnet-web:
[https://vercel.live/link/recnet-git-dev-recnet-542617e7.vercel.app](https://vercel.live/link/recnet-git-dev-recnet-542617e7.vercel.app)
recnet-api:
[https://dev-api.recnet.io/api](https://dev-api.recnet.io/api)
  • Loading branch information
swh00tw authored Oct 9, 2024
2 parents c638a33 + 2e8d5f5 commit 63cb68e
Show file tree
Hide file tree
Showing 39 changed files with 1,003 additions and 356 deletions.
1 change: 1 addition & 0 deletions apps/recnet-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ docker rm recnet-postgres
```bash
# if you don't have Postgres CLI, install libpq first
brew install libpq # macOS
brew install postgresql@16 # macOS

# copy from sample file and modify the .env.dbdump.local
cp apps/recnet-api/scripts/.env.dbdump.sample apps/recnet-api/scripts/.env.dbdump.local
Expand Down
2 changes: 1 addition & 1 deletion apps/recnet-api/package.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"name": "recnet-api",
"version": "1.6.1"
"version": "1.7.0"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
-- DropForeignKey
ALTER TABLE "RecReaction" DROP CONSTRAINT "RecReaction_userId_fkey";

-- DropForeignKey
ALTER TABLE "RecReaction" DROP CONSTRAINT "RecReaction_recId_fkey";

-- DropTable
DROP TABLE "RecReaction";

-- DropEnum
DROP TYPE "ReactionType";

Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
-- CreateEnum
CREATE TYPE "ReactionType" AS ENUM ('THUMBS_UP', 'THINKING', 'SURPRISED', 'CRYING', 'STARRY_EYES', 'MINDBLOWN', 'EYES', 'ROCKET', 'HEART', 'PRAY', 'PARTY');

-- CreateTable
CREATE TABLE "RecReaction" (
"id" SERIAL NOT NULL,
"userId" VARCHAR(64) NOT NULL,
"recId" VARCHAR(64) NOT NULL,
"reaction" "ReactionType" NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "RecReaction_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE UNIQUE INDEX "RecReaction_userId_recId_reaction_key" ON "RecReaction"("userId", "recId", "reaction");

-- AddForeignKey
ALTER TABLE "RecReaction" ADD CONSTRAINT "RecReaction_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "RecReaction" ADD CONSTRAINT "RecReaction_recId_fkey" FOREIGN KEY ("recId") REFERENCES "Recommendation"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
34 changes: 32 additions & 2 deletions apps/recnet-api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,21 @@ enum CronStatus {
IN_PROGRESS
}

enum ReactionType {
THUMBS_UP
THINKING
SURPRISED
CRYING
STARRY_EYES
MINDBLOWN
EYES
ROCKET
HEART
PRAY
PARTY
// Add other reactions here as needed
}

model User {
id String @id @default(uuid()) @db.VarChar(64) // Primary key, UUID type
provider Provider // Enum type
Expand Down Expand Up @@ -59,6 +74,7 @@ model User {
inviteCodeOwner InviteCode[] @relation("InviteCodeOwner")
inviteCodeUsed InviteCode? @relation("InviteCodeUsedBy")
announcements Announcement[]
recReactions RecReaction[]
@@unique([provider, providerId])
}
Expand All @@ -83,8 +99,9 @@ model Recommendation {
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
user User @relation(fields: [userId], references: [id]) // User who made the recommendation
article Article @relation(fields: [articleId], references: [id]) // Article being recommended
user User @relation(fields: [userId], references: [id]) // User who made the recommendation
article Article @relation(fields: [articleId], references: [id]) // Article being recommended
reactions RecReaction[]
}

model Article {
Expand Down Expand Up @@ -139,6 +156,19 @@ model Announcement {
createdBy User @relation(fields: [createdById], references: [id])
}

model RecReaction {
id Int @id @default(autoincrement())
userId String @db.VarChar(64)
recId String @db.VarChar(64)
reaction ReactionType
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id])
recommendation Recommendation @relation(fields: [recId], references: [id])
@@unique([userId, recId, reaction])
}

model WeeklyDigestCronLog {
id Int @id @default(autoincrement())
cutoff DateTime
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,89 @@
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";

import PrismaConnectionProvider from "@recnet-api/database/prisma/prisma.connection.provider";

import { digitalLibrary } from "./digital-library.repository.type";
import {
CreateDigitalLibraryInput,
DigitalLibrary,
digitalLibrary,
DigitalLibraryFilterBy,
UpdateDigitalLibrariesRankInput,
UpdateDigitalLibraryInput,
} from "./digital-library.repository.type";
@Injectable()
export default class DigitalLibraryRepository {
constructor(private readonly prisma: PrismaConnectionProvider) {}

public async findAll() {
public async findAll(): Promise<Array<DigitalLibrary>> {
return this.prisma.digitalLibrary.findMany({
orderBy: {
rank: "asc",
rank: Prisma.SortOrder.asc,
},
select: digitalLibrary.select,
});
}

public async findById(id: number) {
public async findById(id: number): Promise<DigitalLibrary> {
return this.prisma.digitalLibrary.findUniqueOrThrow({
where: { id },
select: digitalLibrary.select,
});
}

public async findMany(
filter: DigitalLibraryFilterBy
): Promise<Array<DigitalLibrary>> {
return this.prisma.digitalLibrary.findMany({
where: filter,
select: digitalLibrary.select,
});
}

public async create(
data: CreateDigitalLibraryInput
): Promise<DigitalLibrary> {
return this.prisma.digitalLibrary.create({
data,
select: digitalLibrary.select,
});
}

public async update(
id: number,
data: UpdateDigitalLibraryInput
): Promise<DigitalLibrary> {
return this.prisma.digitalLibrary.update({
where: { id },
data,
select: digitalLibrary.select,
});
}

public async delete(id: number): Promise<DigitalLibrary> {
return this.prisma.digitalLibrary.delete({
where: { id },
});
}

public async updateRanks(
data: UpdateDigitalLibrariesRankInput
): Promise<void> {
// Update temp ranks to avoid rank conflicts
const tempUpdates = data.map(({ id }, idx) =>
this.prisma.digitalLibrary.update({
where: { id },
data: { rank: -1 * (idx + 1) },
})
);

const updates = data.map(({ id, rank }) =>
this.prisma.digitalLibrary.update({
where: { id },
data: { rank },
})
);

await this.prisma.$transaction([...tempUpdates, ...updates]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,29 @@ export const digitalLibrary =
rank: true,
},
});
export type DigitalLibrary = Prisma.ArticleGetPayload<typeof digitalLibrary>;
export type DigitalLibrary = Prisma.DigitalLibraryGetPayload<
typeof digitalLibrary
>;

export type DigitalLibraryFilterBy = {
id?: number;
name?: string;
};

export type CreateDigitalLibraryInput = {
name: string;
regex: Array<string>;
isVerified: boolean;
rank: number;
};

export type UpdateDigitalLibraryInput = {
name?: string;
regex?: Array<string>;
isVerified?: boolean;
};

export type UpdateDigitalLibrariesRankInput = Array<{
id: number;
rank: number;
}>;
32 changes: 31 additions & 1 deletion apps/recnet-api/src/database/repository/rec.repository.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from "@nestjs/common";
import { Prisma } from "@prisma/client";
import { Prisma, ReactionType } from "@prisma/client";

import PrismaConnectionProvider from "@recnet-api/database/prisma/prisma.connection.provider";
import { rec, Rec } from "@recnet-api/database/repository/rec.repository.type";
Expand Down Expand Up @@ -128,6 +128,36 @@ export default class RecRepository {
}));
}

public async createRecReaction(
userId: string,
recId: string,
reaction: ReactionType
) {
return this.prisma.recReaction.create({
data: {
userId: userId,
recId: recId,
reaction: reaction,
},
});
}

public async deleteRecReaction(
userId: string,
recId: string,
reaction: ReactionType
) {
return this.prisma.recReaction.delete({
where: {
userId_recId_reaction: {
userId: userId,
recId: recId,
reaction: reaction,
},
},
});
}

private transformRecFilterByToPrismaWhere(
filter: RecFilterBy
): Prisma.RecommendationWhereInput {
Expand Down
10 changes: 10 additions & 0 deletions apps/recnet-api/src/database/repository/rec.repository.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ export const rec = Prisma.validator<Prisma.RecommendationDefaultArgs>()({
});
export type Rec = Prisma.RecommendationGetPayload<typeof rec>;

export const recReaction = Prisma.validator<Prisma.RecReactionDefaultArgs>()({
select: {
id: true,
userId: true,
recId: true,
reaction: true,
},
});
export type RecReaction = Prisma.RecReactionGetPayload<typeof recReaction>;

export type DateRange = {
from?: Date;
to?: Date;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { HttpStatus, Inject, Injectable } from "@nestjs/common";

import DigitalLibraryRepository from "@recnet-api/database/repository/digital-library.repository";
import {
DigitalLibrary as DbDigitalLibrary,
DigitalLibraryFilterBy,
} from "@recnet-api/database/repository/digital-library.repository.type";
import { RecnetError } from "@recnet-api/utils/error/recnet.error";
import { ErrorCode } from "@recnet-api/utils/error/recnet.error.const";

import { GetDigitalLibrariesResponse } from "./digital-library.response";
import { CreateDigitalLibraryDto } from "./dto/create.digital-library.dto";
import {
UpdateDigitalLibrariesRankDto,
UpdateDigitalLibraryDto,
} from "./dto/update.digital-library.dto";
import { DigitalLibrary } from "./entities/digital-library.entity";

@Injectable()
export class DigitalLibraryAdminService {
constructor(
@Inject(DigitalLibraryRepository)
private readonly digitalLibraryRepository: DigitalLibraryRepository
) {}

public async getDigitalLibraries(
filter: DigitalLibraryFilterBy
): Promise<GetDigitalLibrariesResponse> {
const digitalLibraries =
await this.digitalLibraryRepository.findMany(filter);

return {
digitalLibraries: digitalLibraries.map(this.transformDigitalLibrary),
};
}

public async createDigitalLibrary(
dto: CreateDigitalLibraryDto
): Promise<DigitalLibrary> {
const dbDigitalLibrary = await this.digitalLibraryRepository.create(dto);
return this.transformDigitalLibrary(dbDigitalLibrary);
}

public async updateDigitalLibrary(
id: number,
dto: UpdateDigitalLibraryDto
): Promise<DigitalLibrary> {
const updatedDbDigitalLibrary = await this.digitalLibraryRepository.update(
id,
dto
);
return this.transformDigitalLibrary(updatedDbDigitalLibrary);
}

public async deleteDigitalLibrary(id: number): Promise<void> {
await this.digitalLibraryRepository.delete(id);
}

public async updateDigitalLibrariesRank(
dto: UpdateDigitalLibrariesRankDto
): Promise<GetDigitalLibrariesResponse> {
await this.validateRankUnique(dto);
await this.digitalLibraryRepository.updateRanks(dto);
const digitalLibraries = await this.digitalLibraryRepository.findAll();
return {
digitalLibraries: digitalLibraries.map(this.transformDigitalLibrary),
};
}

private transformDigitalLibrary(
digitalLibrary: DbDigitalLibrary
): DigitalLibrary {
return {
id: digitalLibrary.id,
name: digitalLibrary.name,
regex: digitalLibrary.regex,
rank: digitalLibrary.rank,
isVerified: digitalLibrary.isVerified,
};
}

private async validateRankUnique(
dto: UpdateDigitalLibrariesRankDto
): Promise<void> {
const ranks = dto.map((rank) => rank.rank);
if (new Set(ranks).size !== dto.length) {
throw new RecnetError(
ErrorCode.DIGITAL_LIBRARY_RANK_CONFLICT,
HttpStatus.CONFLICT
);
}
}
}
Loading

0 comments on commit 63cb68e

Please sign in to comment.