|
|
@ -1,6 +1,7 @@
|
|
|
|
import crypto from 'crypto'
|
|
|
|
import crypto from 'crypto'
|
|
|
|
|
|
|
|
|
|
|
|
import { readFile, writeFile, access, constants } from 'fs/promises'
|
|
|
|
import { readFile, writeFile, access, constants } from 'fs/promises'
|
|
|
|
|
|
|
|
import { Database, DatabaseConnection } from '../../shared/database'
|
|
|
|
|
|
|
|
|
|
|
|
import { MetadataProps as MetaProps, Problem, ProblemId, Solution, SolutionId, User, UserId, UserRole } from '../../shared/model'
|
|
|
|
import { MetadataProps as MetaProps, Problem, ProblemId, Solution, SolutionId, User, UserId, UserRole } from '../../shared/model'
|
|
|
|
|
|
|
|
|
|
|
@ -53,16 +54,18 @@ function createMutex(): Mutex {
|
|
|
|
return { lock }
|
|
|
|
return { lock }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type DatabaseConnection = {
|
|
|
|
type DatabaseInternal = {
|
|
|
|
path: string
|
|
|
|
path: string
|
|
|
|
initialValue: Database
|
|
|
|
initialValue: Database
|
|
|
|
mu: Mutex
|
|
|
|
mu: Mutex
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type Database = {
|
|
|
|
export function createDatabaseWrapper(db: DatabaseInternal): DatabaseConnection {
|
|
|
|
users: Record<string, User>
|
|
|
|
return {
|
|
|
|
problems: Record<string, Problem>
|
|
|
|
getProblem(id: string): Promise<Problem | null> {
|
|
|
|
solutions: Record<string, Solution>
|
|
|
|
return getProblem(db, id)
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function createDatabase(path: string, initialValue: Database) {
|
|
|
|
export function createDatabase(path: string, initialValue: Database) {
|
|
|
@ -73,7 +76,7 @@ export function createDatabase(path: string, initialValue: Database) {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
async function withDatabase<R>({ path, initialValue, mu }: DatabaseConnection, fn: (db: Database) => R | Promise<R>): Promise<R> {
|
|
|
|
async function withDatabase<R>({ path, initialValue, mu }: DatabaseInternal, fn: (db: Database) => R | Promise<R>): Promise<R> {
|
|
|
|
const unlock: Lock = await mu.lock()
|
|
|
|
const unlock: Lock = await mu.lock()
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
try {
|
|
|
@ -100,17 +103,17 @@ async function withDatabase<R>({ path, initialValue, mu }: DatabaseConnection, f
|
|
|
|
// Users
|
|
|
|
// Users
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
export const getUsers = (db: DatabaseConnection) =>
|
|
|
|
export const getUsers = (db: DatabaseInternal) =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
return Object.values(state.users)
|
|
|
|
return Object.values(state.users)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getUser: (db: DatabaseConnection, id: string) => Promise<User | null> = (db, id) =>
|
|
|
|
export const getUser: (db: DatabaseInternal, id: string) => Promise<User | null> = (db, id) =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
return state.users[id] ?? null
|
|
|
|
return state.users[id] ?? null
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const createUser: (db: DatabaseConnection, user: User) => Promise<void> = (db, user) =>
|
|
|
|
export const createUser: (db: DatabaseInternal, user: User) => Promise<void> = (db, user) =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
state.users[user.id] = user
|
|
|
|
state.users[user.id] = user
|
|
|
|
})
|
|
|
|
})
|
|
|
@ -119,7 +122,7 @@ export const createUser: (db: DatabaseConnection, user: User) => Promise<void> =
|
|
|
|
// Problems
|
|
|
|
// Problems
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
export const createProblem = (db: DatabaseConnection, { title, content, createdBy }: Omit<Problem, MetaProps>): Promise<ProblemId> =>
|
|
|
|
export const createProblem = (db: DatabaseInternal, { title, content, createdBy }: Omit<Problem, MetaProps>): Promise<ProblemId> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
const id = crypto.randomBytes(4).toString('hex') as ProblemId
|
|
|
|
const id = crypto.randomBytes(4).toString('hex') as ProblemId
|
|
|
|
|
|
|
|
|
|
|
@ -136,18 +139,18 @@ export const createProblem = (db: DatabaseConnection, { title, content, createdB
|
|
|
|
return id
|
|
|
|
return id
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getProblem = (db: DatabaseConnection, id: string): Promise<Problem | null> =>
|
|
|
|
export const getProblem = (db: DatabaseInternal, id: string): Promise<Problem | null> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
const problem = (state.problems[id] ?? null) as Problem | null
|
|
|
|
const problem = (state.problems[id] ?? null) as Problem | null
|
|
|
|
return problem && !problem.deleted ? problem : null
|
|
|
|
return problem && !problem.deleted ? problem : null
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getProblems = (db: DatabaseConnection): Promise<Problem[]> =>
|
|
|
|
export const getProblems = (db: DatabaseInternal): Promise<Problem[]> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
return Object.values(state.problems).filter(p => !p.deleted)
|
|
|
|
return Object.values(state.problems).filter(p => !p.deleted)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const updateProblem = (db: DatabaseConnection, id: ProblemId, problem: Partial<Omit<Problem, MetaProps>>): Promise<Problem> =>
|
|
|
|
export const updateProblem = (db: DatabaseInternal, id: ProblemId, problem: Partial<Omit<Problem, MetaProps>>): Promise<Problem> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
state.problems[id] = {
|
|
|
|
state.problems[id] = {
|
|
|
|
...state.problems[id],
|
|
|
|
...state.problems[id],
|
|
|
@ -157,7 +160,7 @@ export const updateProblem = (db: DatabaseConnection, id: ProblemId, problem: Pa
|
|
|
|
return state.problems[id]
|
|
|
|
return state.problems[id]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteProblem = (db: DatabaseConnection, id: ProblemId): Promise<void> =>
|
|
|
|
export const deleteProblem = (db: DatabaseInternal, id: ProblemId): Promise<void> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
state.problems[id].deleted = true
|
|
|
|
state.problems[id].deleted = true
|
|
|
|
})
|
|
|
|
})
|
|
|
@ -167,7 +170,7 @@ export const deleteProblem = (db: DatabaseConnection, id: ProblemId): Promise<vo
|
|
|
|
//
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
|
|
export const createSolution = (
|
|
|
|
export const createSolution = (
|
|
|
|
db: DatabaseConnection,
|
|
|
|
db: DatabaseInternal,
|
|
|
|
{ sentBy, forProblem, content }: Omit<Solution, MetaProps | 'status' | 'visible'>
|
|
|
|
{ sentBy, forProblem, content }: Omit<Solution, MetaProps | 'status' | 'visible'>
|
|
|
|
): Promise<SolutionId> =>
|
|
|
|
): Promise<SolutionId> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
@ -188,25 +191,25 @@ export const createSolution = (
|
|
|
|
return id
|
|
|
|
return id
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getSolution = (db: DatabaseConnection, id: SolutionId): Promise<Solution | null> =>
|
|
|
|
export const getSolution = (db: DatabaseInternal, id: SolutionId): Promise<Solution | null> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
const solution = (state.solutions[id] ?? null) as Solution | null
|
|
|
|
const solution = (state.solutions[id] ?? null) as Solution | null
|
|
|
|
return solution && !solution.deleted ? solution : null
|
|
|
|
return solution && !solution.deleted ? solution : null
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getSolutions = (db: DatabaseConnection) =>
|
|
|
|
export const getSolutions = (db: DatabaseInternal) =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
return Object.values(state.solutions).filter(s => !s.deleted)
|
|
|
|
return Object.values(state.solutions).filter(s => !s.deleted)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const getVisibleSolutions = (db: DatabaseConnection): Promise<Solution[]> =>
|
|
|
|
export const getVisibleSolutions = (db: DatabaseInternal): Promise<Solution[]> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
return Object.values(state.solutions)
|
|
|
|
return Object.values(state.solutions)
|
|
|
|
.filter(s => !s.deleted)
|
|
|
|
.filter(s => !s.deleted)
|
|
|
|
.filter(s => s.visible)
|
|
|
|
.filter(s => s.visible)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const updateSolution = (db: DatabaseConnection, id: SolutionId, solution: Partial<Omit<Solution, MetaProps>>): Promise<Solution> =>
|
|
|
|
export const updateSolution = (db: DatabaseInternal, id: SolutionId, solution: Partial<Omit<Solution, MetaProps>>): Promise<Solution> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
state.solutions[id] = {
|
|
|
|
state.solutions[id] = {
|
|
|
|
...state.solutions[id],
|
|
|
|
...state.solutions[id],
|
|
|
@ -216,7 +219,7 @@ export const updateSolution = (db: DatabaseConnection, id: SolutionId, solution:
|
|
|
|
return state.solutions[id]
|
|
|
|
return state.solutions[id]
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
export const deleteSolution = (db: DatabaseConnection, id: SolutionId): Promise<void> =>
|
|
|
|
export const deleteSolution = (db: DatabaseInternal, id: SolutionId): Promise<void> =>
|
|
|
|
withDatabase(db, state => {
|
|
|
|
withDatabase(db, state => {
|
|
|
|
state.solutions[id].deleted = true
|
|
|
|
state.solutions[id].deleted = true
|
|
|
|
})
|
|
|
|
})
|
|
|
|