Skip to content

Commit

Permalink
Refactor Prisma client extensions + add abbreviation to tag_translati…
Browse files Browse the repository at this point in the history
…on (#1203)

* add tag abbreviation

* use Prisma client extensions for some simplification

* some more extensions

* fix organization logo file size error; add prototype for custom error

* remove custom error name overrides
  • Loading branch information
mipyykko authored Jun 5, 2023
1 parent 00da6bf commit dc0849d
Show file tree
Hide file tree
Showing 63 changed files with 2,475 additions and 665 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ jobs:
- backend-cache-{{ checksum "package-lock.json" }}
- run:
name: "Install dependencies"
command: npm ci --cache /home/circleci/.npm --prefer-offline --no-audit
command: npm ci --prefer-offline --no-audit
- run:
name: "Set environment"
command: |
Expand Down
12 changes: 11 additions & 1 deletion backend/api/routes/__test__/userCourseSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,16 @@ describe("API", () => {
})
})

it("errors on non-existent course", async () => {
return getSettings("foobar")({
headers: FAKE_ADMIN_USER_AUTHORIZATION_HEADERS,
})
.then(() => fail())
.catch(({ response }) => {
expect(response.status).toBe(404)
})
})

it("returns null with user with no settings", async () => {
return getSettings("course1")({
headers: FAKE_THIRD_USER_AUTHORIZATION_HEADERS,
Expand Down Expand Up @@ -146,7 +156,7 @@ describe("API", () => {
})
.then(() => fail())
.catch(({ response }) => {
expect(response.data.message).toContain("doesn't exist")
expect(response.data.message).toContain("course not found")
expect(response.status).toBe(404)
})
})
Expand Down
4 changes: 2 additions & 2 deletions backend/api/routes/completions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ export class CompletionController extends Controller {
return res.status(404).json({ message: "user not found" })
}

const course = await this.getCourse({
const course = await prisma.course.findUniqueOrAlias({
where: {
slug,
},
Expand Down Expand Up @@ -316,7 +316,7 @@ export class CompletionController extends Controller {
})
}

const course = await this.getCourse({
const course = await prisma.course.findUniqueOrAlias({
where: {
id: course_id ?? undefined,
slug: slug ?? undefined,
Expand Down
6 changes: 3 additions & 3 deletions backend/api/routes/progress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ interface ExerciseCompletionResult {
}

const isId = (idOrSlug: string) =>
Boolean(idOrSlug.match(/^[0-9a-fA-F]{32}$/)) ||
Boolean(idOrSlug.match(/^[0-9a-fA-F-]{36}$/))
Boolean(RegExp(/^[0-9a-fA-F]{32}$/).exec(idOrSlug)) ||
Boolean(RegExp(/^[0-9a-fA-F-]{36}$/).exec(idOrSlug))

export class ProgressController extends Controller {
constructor(override readonly ctx: ApiContext) {
Expand Down Expand Up @@ -255,7 +255,7 @@ export class ProgressController extends Controller {

const { user } = getUserResult.value

const course = await this.getCourse({
const course = await prisma.course.findUniqueOrAlias({
where: { slug },
})

Expand Down
4 changes: 2 additions & 2 deletions backend/api/routes/storedData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export class StoredDataController extends Controller {
if (!data) {
return res.status(400).json({ message: "must provide data" })
}
const course = await this.getCourse({ where: { slug } })
const course = await prisma.course.findUniqueOrAlias({ where: { slug } })

if (!course) {
return res.status(401).json({
Expand Down Expand Up @@ -81,7 +81,7 @@ export class StoredDataController extends Controller {
const { prisma } = this.ctx
const { slug } = req.params

const course = await this.getCourse({ where: { slug } })
const course = await prisma.course.findUniqueOrAlias({ where: { slug } })

if (!course) {
return res
Expand Down
87 changes: 30 additions & 57 deletions backend/api/routes/userCourseSettings.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Request, Response } from "express"
import { intersection, omit } from "lodash"

import { User, UserCourseSettingsVisibility } from "@prisma/client"
import { UserCourseSettingsVisibility } from "@prisma/client"

import { redisify } from "../../services/redis"
import { err, ok } from "../../util/result"
import { ApiContext, Controller } from "../types"

type UserCourseSettingsCountResult =
Expand All @@ -25,7 +24,7 @@ export class UserCourseSettingsController extends Controller {
}

get = async (req: Request<{ slug: string }>, res: Response) => {
const { logger } = this.ctx
const { logger, prisma } = this.ctx
const { slug } = req.params

if (!slug) {
Expand All @@ -40,12 +39,22 @@ export class UserCourseSettingsController extends Controller {

const { user } = getUserResult.value

const courseAndSettingsResult = await this.getCourseAndSettings(user, slug)
const course = await prisma.course.findUniqueOrAlias({
where: {
slug,
},
})

if (courseAndSettingsResult.isErr()) {
return res.status(404).json({ message: courseAndSettingsResult.error })
if (!course) {
return res.status(404).json({ message: "course not found" })
}
const { userCourseSettings } = courseAndSettingsResult.value

const userCourseSettings = await prisma.user.findUserCourseSettings({
where: {
user_id: user.id,
course_slug: slug,
},
})

const overwrittenKeys = intersection(
Object.keys(omit(userCourseSettings, "other") ?? {}),
Expand Down Expand Up @@ -93,12 +102,22 @@ export class UserCourseSettingsController extends Controller {

const { user } = getUserResult.value

const courseAndSettingsResult = await this.getCourseAndSettings(user, slug)
const course = await prisma.course.findUniqueOrAlias({
where: {
slug,
},
})

if (courseAndSettingsResult.isErr()) {
return res.status(404).json({ message: courseAndSettingsResult.error })
if (!course) {
return res.status(404).json({ message: "course not found" })
}
const { course, userCourseSettings } = courseAndSettingsResult.value

const userCourseSettings = await prisma.user.findUserCourseSettings({
where: {
user_id: user.id,
course_slug: slug,
},
})

const permittedFields = [
"country",
Expand Down Expand Up @@ -231,52 +250,6 @@ export class UserCourseSettingsController extends Controller {

return res.status(200).json(resObject)
}

private async getCourseAndSettings(user: User, slug: string) {
const course = await this.getCourse({ where: { slug } })

if (!course) {
return err(
`course with slug or course alias with course code ${slug} doesn't exist`,
)
}

try {
const courseWithSettings = await this.ctx.prisma.course.findUnique({
where: {
id: course.id,
},
include: {
user_course_settings: {
where: {
user_id: user.id,
},
orderBy: { created_at: "asc" },
take: 1,
},
inherit_settings_from: {
include: {
user_course_settings: {
where: {
user_id: user.id,
},
orderBy: { created_at: "asc" },
take: 1,
},
},
},
},
})

const userCourseSettings =
courseWithSettings?.inherit_settings_from?.user_course_settings?.[0] ??
courseWithSettings?.user_course_settings?.[0]

return ok({ course, userCourseSettings })
} catch (e) {
return err(e instanceof Error ? e.message : e)
}
}
}

const isError = (res: any): res is { error: true } =>
Expand Down
4 changes: 1 addition & 3 deletions backend/api/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BaseContext } from "../context"
import { getCourseOrAlias, getCourseOrAliasKnex } from "../util/db-functions"
import { getCourseOrAliasKnex } from "../util/db-functions"
import {
getOrganization,
getUser,
Expand All @@ -10,13 +10,11 @@ export type ApiContext = BaseContext

export abstract class Controller {
protected getCourseKnex
protected getCourse
protected getUser
protected getOrganization
protected requireAdmin

constructor(readonly ctx: ApiContext) {
this.getCourse = getCourseOrAlias(ctx)
this.getCourseKnex = getCourseOrAliasKnex(ctx)
this.getUser = getUser(ctx)
this.getOrganization = getOrganization(ctx)
Expand Down
23 changes: 11 additions & 12 deletions backend/bin/fetchUserAppDatum.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DateTime } from "luxon"

import { Course, Prisma, PrismaClient, UserCourseSetting } from "@prisma/client"
import { Course, Prisma, UserCourseSetting } from "@prisma/client"

import { CONFIG_NAME } from "../config"
import { UserInfo } from "../domain/UserInfo"
Expand Down Expand Up @@ -36,14 +36,13 @@ const fetchUserAppDatum = async () => {
logger.info(`data length ${tmcData.length}`)
logger.info("sorting")

const data = tmcData
.sort(
(a, b) =>
DateTime.fromISO(a.updated_at).toMillis() -
DateTime.fromISO(b.updated_at).toMillis(),
)
.filter(notEmpty)
.filter((e) => e.user_id !== null)
tmcData.sort(
(a, b) =>
DateTime.fromISO(a.updated_at).toMillis() -
DateTime.fromISO(b.updated_at).toMillis(),
)

const data = tmcData.filter(notEmpty).filter((e) => e.user_id !== null)

// logger.info(data)
// logger.info("sorted")
Expand Down Expand Up @@ -146,11 +145,11 @@ const fetchUserAppDatum = async () => {
await saveOther(e)
}
if (index % saveInterval == 0) {
await saveProgress(prisma, new Date(e.updated_at))
await saveProgress(new Date(e.updated_at))
}
}

await saveProgress(prisma, new Date(data[data.length - 1].updated_at))
await saveProgress(new Date(data[data.length - 1].updated_at))

const stopTime = new Date().getTime()
logger.info(`used ${stopTime - startTime} milliseconds`)
Expand Down Expand Up @@ -274,7 +273,7 @@ const getUserFromTmcAndSaveToDB = async (user_id: number, tmc: TmcClient) => {

const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))

async function saveProgress(prisma: PrismaClient, dateToDB: Date) {
async function saveProgress(dateToDB: Date) {
logger.info("saving")
dateToDB.setMinutes(dateToDB.getMinutes() - 10)

Expand Down
19 changes: 9 additions & 10 deletions backend/bin/fetchUserFieldValues.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { DateTime } from "luxon"

import { PrismaClient } from "@prisma/client"

import { CONFIG_NAME } from "../config"
import { UserInfo } from "../domain/UserInfo"
import { DatabaseInputError, TMCError } from "../lib/errors"
Expand All @@ -13,7 +11,7 @@ const FETCH_USER_FIELD_VALUES_CONFIG_NAME = CONFIG_NAME ?? "userFieldValues"

const logger = sentryLogger({ service: "fetch-user-field-values" })

const fetcUserFieldValues = async () => {
const fetchUserFieldValues = async () => {
const startTime = new Date().getTime()
const tmc = new TmcClient()

Expand All @@ -24,13 +22,14 @@ const fetcUserFieldValues = async () => {

logger.info(latestTimeStamp)

const data_from_tmc = await tmc.getUserFieldValues(
const data = await tmc.getUserFieldValues(
latestTimeStamp?.toISOString() ?? null,
)
logger.info("Got data from tmc")
logger.info(`data length ${data_from_tmc.length}`)
logger.info(`data length ${data.length}`)
logger.info("sorting")
const data = data_from_tmc.sort(

data.sort(
(a, b) =>
DateTime.fromISO(a.updated_at).toMillis() -
DateTime.fromISO(b.updated_at).toMillis(),
Expand Down Expand Up @@ -83,11 +82,11 @@ const fetcUserFieldValues = async () => {
}

if (saveCounter % saveInterval == 0) {
await saveProgress(prisma, new Date(p.updated_at))
await saveProgress(new Date(p.updated_at))
}
}

await saveProgress(prisma, new Date(data[data.length - 1].updated_at))
await saveProgress(new Date(data[data.length - 1].updated_at))

const stopTime = new Date().getTime()
logger.info(`used ${stopTime - startTime} milliseconds`)
Expand Down Expand Up @@ -139,7 +138,7 @@ const getUserFromTmcAndSaveToDB = async (user_id: number, tmc: TmcClient) => {

const delay = (ms: number) => new Promise((res) => setTimeout(res, ms))

async function saveProgress(prisma: PrismaClient, dateToDB: Date) {
async function saveProgress(dateToDB: Date) {
logger.info("saving")
dateToDB.setMinutes(dateToDB.getMinutes() - 10)

Expand All @@ -155,7 +154,7 @@ async function saveProgress(prisma: PrismaClient, dateToDB: Date) {
})
}

fetcUserFieldValues().catch((e) => {
fetchUserFieldValues().catch((e) => {
logger.error(e)
process.exit(1)
})
21 changes: 20 additions & 1 deletion backend/bin/importOrganizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ const fetchOrganizations = async () => {
}
}

const parseLogoSize = (organization: OrganizationInfo) => {
const { logo_file_size } = organization
if (!logo_file_size) {
return undefined
}

const parsed = parseInt(logo_file_size)

if (isNaN(parsed)) {
logger.warn(
"Failed to parsed logo_file_size - organization data: " +
JSON.stringify(organization, null, 2),
)
return undefined
}

return parsed
}

const upsertOrganization = async (org: OrganizationInfo) => {
const user =
org.creator_id != null ? await getUserFromTmc(org.creator_id) : null
Expand All @@ -36,7 +55,7 @@ const upsertOrganization = async (org: OrganizationInfo) => {
creator: user !== null ? { connect: { id: user.id } } : undefined,
logo_file_name: org.logo_file_name,
logo_content_type: org.logo_content_type,
logo_file_size: parseInt(org.logo_file_size ?? "") ?? undefined,
logo_file_size: parseLogoSize(org),
logo_updated_at: org.logo_updated_at,
phone: org.phone,
contact_information: org.contact_information,
Expand Down
Loading

0 comments on commit dc0849d

Please sign in to comment.