refactor(server): move auth and system services to class
This commit is contained in:
parent
c42b96ae53
commit
5ff7451267
8 changed files with 242 additions and 208 deletions
|
@ -1,12 +1,15 @@
|
|||
import { z } from 'zod';
|
||||
import AuthService from '../../services/auth/auth.service';
|
||||
import { AuthServiceClass } from '../../services/auth/auth.service';
|
||||
import { router, publicProcedure, protectedProcedure } from '../../trpc';
|
||||
import { prisma } from '../../db/client';
|
||||
|
||||
const AuthService = new AuthServiceClass(prisma);
|
||||
|
||||
export const authRouter = router({
|
||||
login: publicProcedure.input(z.object({ username: z.string(), password: z.string() })).mutation(async ({ input }) => AuthService.login({ ...input })),
|
||||
logout: protectedProcedure.mutation(async ({ ctx }) => AuthService.logout(ctx.session.id)),
|
||||
logout: protectedProcedure.mutation(async ({ ctx }) => AuthServiceClass.logout(ctx.session.id)),
|
||||
register: publicProcedure.input(z.object({ username: z.string(), password: z.string() })).mutation(async ({ input }) => AuthService.register({ ...input })),
|
||||
refreshToken: protectedProcedure.mutation(async ({ ctx }) => AuthService.refreshToken(ctx.session.id)),
|
||||
refreshToken: protectedProcedure.mutation(async ({ ctx }) => AuthServiceClass.refreshToken(ctx.session.id)),
|
||||
me: publicProcedure.query(async ({ ctx }) => AuthService.me(ctx.session?.userId)),
|
||||
isConfigured: publicProcedure.query(async () => AuthService.isConfigured()),
|
||||
});
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import { inferRouterOutputs } from '@trpc/server';
|
||||
import { router, protectedProcedure, publicProcedure } from '../../trpc';
|
||||
import { SystemService } from '../../services/system/system.service';
|
||||
import { SystemServiceClass } from '../../services/system';
|
||||
|
||||
export type SystemRouterOutput = inferRouterOutputs<typeof systemRouter>;
|
||||
const SystemService = new SystemServiceClass();
|
||||
|
||||
export const systemRouter = router({
|
||||
status: publicProcedure.query(SystemService.status),
|
||||
systemInfo: protectedProcedure.query(SystemService.systemInfo),
|
||||
status: publicProcedure.query(SystemServiceClass.status),
|
||||
systemInfo: protectedProcedure.query(SystemServiceClass.systemInfo),
|
||||
getVersion: publicProcedure.query(SystemService.getVersion),
|
||||
restart: protectedProcedure.mutation(SystemService.restart),
|
||||
update: protectedProcedure.mutation(SystemService.update),
|
||||
|
|
|
@ -1,32 +1,38 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import * as argon2 from 'argon2';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { setConfig } from '../../core/TipiConfig';
|
||||
import { createUser } from '../../tests/user.factory';
|
||||
import AuthService from './auth.service';
|
||||
import { prisma } from '../../db/client';
|
||||
import { AuthServiceClass } from './auth.service';
|
||||
import TipiCache from '../../core/TipiCache';
|
||||
import { getTestDbClient } from '../../../../tests/server/db-connection';
|
||||
|
||||
jest.mock('redis');
|
||||
let db: PrismaClient;
|
||||
let AuthService: AuthServiceClass;
|
||||
const TEST_SUITE = 'authservice';
|
||||
|
||||
beforeAll(async () => {
|
||||
setConfig('jwtSecret', 'test');
|
||||
db = await getTestDbClient(TEST_SUITE);
|
||||
AuthService = new AuthServiceClass(db);
|
||||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
await prisma.user.deleteMany();
|
||||
jest.mock('redis');
|
||||
await db.user.deleteMany();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await prisma.user.deleteMany();
|
||||
await prisma.$disconnect();
|
||||
await db.user.deleteMany();
|
||||
await db.$disconnect();
|
||||
});
|
||||
|
||||
describe('Login', () => {
|
||||
it('Should return a valid jsonwebtoken containing a user id', async () => {
|
||||
// Arrange
|
||||
const email = faker.internet.email();
|
||||
const user = await createUser(email);
|
||||
const user = await createUser(email, db);
|
||||
|
||||
// Act
|
||||
const { token } = await AuthService.login({ username: email, password: 'password' });
|
||||
|
@ -49,7 +55,7 @@ describe('Login', () => {
|
|||
|
||||
it('Should throw if password is incorrect', async () => {
|
||||
const email = faker.internet.email();
|
||||
await createUser(email);
|
||||
await createUser(email, db);
|
||||
await expect(AuthService.login({ username: email, password: 'wrong' })).rejects.toThrowError('Wrong password');
|
||||
});
|
||||
});
|
||||
|
@ -78,7 +84,7 @@ describe('Register', () => {
|
|||
|
||||
// Act
|
||||
await AuthService.register({ username: email, password: 'test' });
|
||||
const user = await prisma.user.findFirst({ where: { username: email.toLowerCase().trim() } });
|
||||
const user = await db.user.findFirst({ where: { username: email.toLowerCase().trim() } });
|
||||
|
||||
// Assert
|
||||
expect(user).toBeDefined();
|
||||
|
@ -90,7 +96,7 @@ describe('Register', () => {
|
|||
const email = faker.internet.email();
|
||||
|
||||
// Act & Assert
|
||||
await createUser(email);
|
||||
await createUser(email, db);
|
||||
await expect(AuthService.register({ username: email, password: 'test' })).rejects.toThrowError('User already exists');
|
||||
});
|
||||
|
||||
|
@ -108,7 +114,7 @@ describe('Register', () => {
|
|||
|
||||
// Act
|
||||
await AuthService.register({ username: email, password: 'test' });
|
||||
const user = await prisma.user.findUnique({ where: { username: email } });
|
||||
const user = await db.user.findUnique({ where: { username: email } });
|
||||
const isPasswordValid = await argon2.verify(user?.password || '', 'test');
|
||||
|
||||
// Assert
|
||||
|
@ -123,7 +129,7 @@ describe('Register', () => {
|
|||
describe('Test: logout', () => {
|
||||
it('Should return true if there is no session to delete', async () => {
|
||||
// Act
|
||||
const result = await AuthService.logout();
|
||||
const result = await AuthServiceClass.logout();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
|
@ -136,7 +142,7 @@ describe('Test: logout', () => {
|
|||
expect(await TipiCache.get(session)).toBe('test');
|
||||
|
||||
// Act
|
||||
const result = await AuthService.logout(session);
|
||||
const result = await AuthServiceClass.logout(session);
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
|
@ -147,7 +153,7 @@ describe('Test: logout', () => {
|
|||
describe('Test: refreshToken', () => {
|
||||
it('Should return null if session is not provided', async () => {
|
||||
// Act
|
||||
const result = await AuthService.refreshToken();
|
||||
const result = await AuthServiceClass.refreshToken();
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
|
@ -155,7 +161,7 @@ describe('Test: refreshToken', () => {
|
|||
|
||||
it('Should return null if session is not found in cache', async () => {
|
||||
// Act
|
||||
const result = await AuthService.refreshToken('test');
|
||||
const result = await AuthServiceClass.refreshToken('test');
|
||||
|
||||
// Assert
|
||||
expect(result).toBeNull();
|
||||
|
@ -167,7 +173,7 @@ describe('Test: refreshToken', () => {
|
|||
await TipiCache.set(session, 'test');
|
||||
|
||||
// Act
|
||||
const result = await AuthService.refreshToken(session);
|
||||
const result = await AuthServiceClass.refreshToken(session);
|
||||
|
||||
// Assert
|
||||
expect(result).not.toBeNull();
|
||||
|
@ -181,7 +187,7 @@ describe('Test: refreshToken', () => {
|
|||
await TipiCache.set(session, '1');
|
||||
|
||||
// Act
|
||||
const result = await AuthService.refreshToken(session);
|
||||
const result = await AuthServiceClass.refreshToken(session);
|
||||
const expiration = await TipiCache.ttl(session);
|
||||
|
||||
// Assert
|
||||
|
@ -213,7 +219,7 @@ describe('Test: me', () => {
|
|||
it('Should return user if user exists', async () => {
|
||||
// Arrange
|
||||
const email = faker.internet.email();
|
||||
const user = await createUser(email);
|
||||
const user = await createUser(email, db);
|
||||
|
||||
// Act
|
||||
const result = await AuthService.me(user.id);
|
||||
|
@ -224,3 +230,25 @@ describe('Test: me', () => {
|
|||
expect(result).toHaveProperty('username');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Test: isConfigured', () => {
|
||||
it('Should return false if no user exists', async () => {
|
||||
// Act
|
||||
const result = await AuthService.isConfigured();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('Should return true if user exists', async () => {
|
||||
// Arrange
|
||||
const email = faker.internet.email();
|
||||
await createUser(email, db);
|
||||
|
||||
// Act
|
||||
const result = await AuthService.isConfigured();
|
||||
|
||||
// Assert
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { PrismaClient } from '@prisma/client';
|
||||
import * as argon2 from 'argon2';
|
||||
import { v4 } from 'uuid';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import validator from 'validator';
|
||||
import { getConfig } from '../../core/TipiConfig';
|
||||
import TipiCache from '../../core/TipiCache';
|
||||
import { prisma } from '../../db/client';
|
||||
|
||||
type UsernamePasswordInput = {
|
||||
username: string;
|
||||
|
@ -15,141 +15,138 @@ type TokenResponse = {
|
|||
token: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Authenticate user with given username and password
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the user's username and password
|
||||
* @return {Promise<{token:string}>} - A promise that resolves to an object containing the JWT token
|
||||
*/
|
||||
const login = async (input: UsernamePasswordInput) => {
|
||||
const { password, username } = input;
|
||||
export class AuthServiceClass {
|
||||
private prisma;
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { username: username.trim().toLowerCase() } });
|
||||
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
constructor(p: PrismaClient) {
|
||||
this.prisma = p;
|
||||
}
|
||||
|
||||
const isPasswordValid = await argon2.verify(user.password, password);
|
||||
/**
|
||||
* Authenticate user with given username and password
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the user's username and password
|
||||
* @returns {Promise<{token:string}>} - A promise that resolves to an object containing the JWT token
|
||||
*/
|
||||
public login = async (input: UsernamePasswordInput) => {
|
||||
const { password, username } = input;
|
||||
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Wrong password');
|
||||
}
|
||||
const user = await this.prisma.user.findUnique({ where: { username: username.trim().toLowerCase() } });
|
||||
|
||||
const session = v4();
|
||||
const token = jwt.sign({ id: user.id, session }, getConfig().jwtSecret, { expiresIn: '7d' });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
|
||||
await TipiCache.set(session, user.id.toString());
|
||||
const isPasswordValid = await argon2.verify(user.password, password);
|
||||
|
||||
return { token };
|
||||
};
|
||||
if (!isPasswordValid) {
|
||||
throw new Error('Wrong password');
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new user with the provided email and password and returns a session token
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the email and password fields
|
||||
* @returns {Promise<{token: string}>} - An object containing the session token
|
||||
* @throws {Error} - If the email or password is missing, the email is invalid or the user already exists
|
||||
*/
|
||||
const register = async (input: UsernamePasswordInput) => {
|
||||
const { password, username } = input;
|
||||
const email = username.trim().toLowerCase();
|
||||
const session = v4();
|
||||
const token = jwt.sign({ id: user.id, session }, getConfig().jwtSecret, { expiresIn: '7d' });
|
||||
|
||||
if (!username || !password) {
|
||||
throw new Error('Missing email or password');
|
||||
}
|
||||
await TipiCache.set(session, user.id.toString());
|
||||
|
||||
if (username.length < 3 || !validator.isEmail(email)) {
|
||||
throw new Error('Invalid username');
|
||||
}
|
||||
return { token };
|
||||
};
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { username: email } });
|
||||
/**
|
||||
* Creates a new user with the provided email and password and returns a session token
|
||||
*
|
||||
* @param {UsernamePasswordInput} input - An object containing the email and password fields
|
||||
* @returns {Promise<{token: string}>} - An object containing the session token
|
||||
* @throws {Error} - If the email or password is missing, the email is invalid or the user already exists
|
||||
*/
|
||||
public register = async (input: UsernamePasswordInput) => {
|
||||
const { password, username } = input;
|
||||
const email = username.trim().toLowerCase();
|
||||
|
||||
if (user) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
if (!username || !password) {
|
||||
throw new Error('Missing email or password');
|
||||
}
|
||||
|
||||
const hash = await argon2.hash(password);
|
||||
const newUser = await prisma.user.create({ data: { username: email, password: hash } });
|
||||
if (username.length < 3 || !validator.isEmail(email)) {
|
||||
throw new Error('Invalid username');
|
||||
}
|
||||
|
||||
const session = v4();
|
||||
const token = jwt.sign({ id: newUser.id, session }, getConfig().jwtSecret, { expiresIn: '1d' });
|
||||
const user = await this.prisma.user.findUnique({ where: { username: email } });
|
||||
|
||||
await TipiCache.set(session, newUser.id.toString());
|
||||
if (user) {
|
||||
throw new Error('User already exists');
|
||||
}
|
||||
|
||||
return { token };
|
||||
};
|
||||
const hash = await argon2.hash(password);
|
||||
const newUser = await this.prisma.user.create({ data: { username: email, password: hash } });
|
||||
|
||||
/**
|
||||
* Retrieves the user with the provided ID
|
||||
*
|
||||
* @param {number|undefined} userId - The user ID to retrieve
|
||||
* @returns {Promise<{id: number, username: string} | null>} - An object containing the user's id and email, or null if the user is not found
|
||||
*/
|
||||
const me = async (userId: number | undefined) => {
|
||||
if (!userId) return null;
|
||||
const session = v4();
|
||||
const token = jwt.sign({ id: newUser.id, session }, getConfig().jwtSecret, { expiresIn: '1d' });
|
||||
|
||||
const user = await prisma.user.findUnique({ where: { id: Number(userId) }, select: { id: true, username: true } });
|
||||
await TipiCache.set(session, newUser.id.toString());
|
||||
|
||||
if (!user) return null;
|
||||
return { token };
|
||||
};
|
||||
|
||||
return user;
|
||||
};
|
||||
/**
|
||||
* Retrieves the user with the provided ID
|
||||
*
|
||||
* @param {number|undefined} userId - The user ID to retrieve
|
||||
* @returns {Promise<{id: number, username: string} | null>} - An object containing the user's id and email, or null if the user is not found
|
||||
*/
|
||||
public me = async (userId: number | undefined) => {
|
||||
if (!userId) return null;
|
||||
|
||||
/**
|
||||
* Logs out the current user by removing the session token
|
||||
*
|
||||
* @param {string} [session] - The session token to log out
|
||||
* @returns {Promise<boolean>} - Returns true if the session token is removed successfully
|
||||
*/
|
||||
const logout = async (session?: string): Promise<boolean> => {
|
||||
if (session) {
|
||||
await TipiCache.del(session);
|
||||
}
|
||||
const user = await this.prisma.user.findUnique({ where: { id: Number(userId) }, select: { id: true, username: true } });
|
||||
|
||||
return true;
|
||||
};
|
||||
if (!user) return null;
|
||||
|
||||
/**
|
||||
* Refreshes a user's session token
|
||||
*
|
||||
* @param {string} [session] - The current session token
|
||||
* @returns {Promise<{token: string} | null>} - An object containing the new session token, or null if the session is invalid
|
||||
*/
|
||||
const refreshToken = async (session?: string): Promise<TokenResponse | null> => {
|
||||
if (!session) return null;
|
||||
return user;
|
||||
};
|
||||
|
||||
const userId = await TipiCache.get(session);
|
||||
if (!userId) return null;
|
||||
/**
|
||||
* Logs out the current user by removing the session token
|
||||
*
|
||||
* @param {string} [session] - The session token to log out
|
||||
* @returns {Promise<boolean>} - Returns true if the session token is removed successfully
|
||||
*/
|
||||
public static logout = async (session?: string): Promise<boolean> => {
|
||||
if (session) {
|
||||
await TipiCache.del(session);
|
||||
}
|
||||
|
||||
// Expire token in 6 seconds
|
||||
await TipiCache.set(session, userId, 6);
|
||||
return true;
|
||||
};
|
||||
|
||||
const newSession = v4();
|
||||
const token = jwt.sign({ id: userId, session: newSession }, getConfig().jwtSecret, { expiresIn: '1d' });
|
||||
await TipiCache.set(newSession, userId);
|
||||
/**
|
||||
* Refreshes a user's session token
|
||||
*
|
||||
* @param {string} [session] - The current session token
|
||||
* @returns {Promise<{token: string} | null>} - An object containing the new session token, or null if the session is invalid
|
||||
*/
|
||||
public static refreshToken = async (session?: string): Promise<TokenResponse | null> => {
|
||||
if (!session) return null;
|
||||
|
||||
return { token };
|
||||
};
|
||||
const userId = await TipiCache.get(session);
|
||||
if (!userId) return null;
|
||||
|
||||
/**
|
||||
* Check if the system is configured and has at least one user
|
||||
*
|
||||
* @returns {Promise<boolean>} - A boolean indicating if the system is configured or not
|
||||
*/
|
||||
const isConfigured = async (): Promise<boolean> => {
|
||||
const count = await prisma.user.count();
|
||||
// Expire token in 6 seconds
|
||||
await TipiCache.set(session, userId, 6);
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
const newSession = v4();
|
||||
const token = jwt.sign({ id: userId, session: newSession }, getConfig().jwtSecret, { expiresIn: '1d' });
|
||||
await TipiCache.set(newSession, userId);
|
||||
|
||||
const AuthService = {
|
||||
login,
|
||||
register,
|
||||
me,
|
||||
logout,
|
||||
refreshToken,
|
||||
isConfigured,
|
||||
};
|
||||
return { token };
|
||||
};
|
||||
|
||||
export default AuthService;
|
||||
/**
|
||||
* Check if the system is configured and has at least one user
|
||||
*
|
||||
* @returns {Promise<boolean>} - A boolean indicating if the system is configured or not
|
||||
*/
|
||||
public isConfigured = async (): Promise<boolean> => {
|
||||
const count = await this.prisma.user.count();
|
||||
|
||||
return count > 0;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
export { SystemService } from './system.service';
|
||||
export { SystemServiceClass } from './system.service';
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import fs from 'fs-extra';
|
||||
import semver from 'semver';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { SystemService } from '.';
|
||||
import { EventDispatcher } from '../../core/EventDispatcher';
|
||||
import { setConfig } from '../../core/TipiConfig';
|
||||
import TipiCache from '../../core/TipiCache';
|
||||
import { SystemServiceClass } from '.';
|
||||
|
||||
jest.mock('axios');
|
||||
jest.mock('redis');
|
||||
|
||||
const SystemService = new SystemServiceClass();
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.mock('fs-extra');
|
||||
jest.resetModules();
|
||||
|
@ -18,7 +20,7 @@ beforeEach(async () => {
|
|||
describe('Test: systemInfo', () => {
|
||||
it('Should throw if system-info.json does not exist', () => {
|
||||
try {
|
||||
SystemService.systemInfo();
|
||||
SystemServiceClass.systemInfo();
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof Error) {
|
||||
expect(e).toBeDefined();
|
||||
|
@ -45,7 +47,7 @@ describe('Test: systemInfo', () => {
|
|||
fs.__createMockFiles(MockFiles);
|
||||
|
||||
// Act
|
||||
const systemInfo = SystemService.systemInfo();
|
||||
const systemInfo = SystemServiceClass.systemInfo();
|
||||
|
||||
// Assert
|
||||
expect(systemInfo).toBeDefined();
|
||||
|
|
|
@ -23,88 +23,91 @@ const systemInfoSchema = z.object({
|
|||
}),
|
||||
});
|
||||
|
||||
const status = async (): Promise<{ status: SystemStatus }> => ({
|
||||
status: getConfig().status as SystemStatus,
|
||||
});
|
||||
export class SystemServiceClass {
|
||||
private cache;
|
||||
|
||||
/**
|
||||
* Get the current and latest version of Tipi
|
||||
* @returns {Promise<{ current: string; latest: string }>}
|
||||
*/
|
||||
const getVersion = async (): Promise<{ current: string; latest?: string }> => {
|
||||
try {
|
||||
let version = await TipiCache.get('latestVersion');
|
||||
private dispatcher;
|
||||
|
||||
if (!version) {
|
||||
const data = await fetch('https://api.github.com/repos/meienberger/runtipi/releases/latest');
|
||||
const release = await data.json();
|
||||
constructor() {
|
||||
this.cache = TipiCache;
|
||||
this.dispatcher = EventDispatcher;
|
||||
}
|
||||
|
||||
version = release.name.replace('v', '');
|
||||
await TipiCache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
||||
/**
|
||||
* Get the current and latest version of Tipi
|
||||
* @returns {Promise<{ current: string; latest: string }>}
|
||||
*/
|
||||
public getVersion = async (): Promise<{ current: string; latest?: string }> => {
|
||||
try {
|
||||
let version = await this.cache.get('latestVersion');
|
||||
|
||||
if (!version) {
|
||||
const data = await fetch('https://api.github.com/repos/meienberger/runtipi/releases/latest');
|
||||
const release = await data.json();
|
||||
|
||||
version = release.name.replace('v', '');
|
||||
await this.cache.set('latestVersion', version?.replace('v', '') || '', 60 * 60);
|
||||
}
|
||||
|
||||
return { current: getConfig().version, latest: version?.replace('v', '') };
|
||||
} catch (e) {
|
||||
Logger.error(e);
|
||||
return { current: getConfig().version, latest: undefined };
|
||||
}
|
||||
};
|
||||
|
||||
public static systemInfo = (): z.infer<typeof systemInfoSchema> => {
|
||||
const info = systemInfoSchema.safeParse(readJsonFile('/runtipi/state/system-info.json'));
|
||||
|
||||
if (!info.success) {
|
||||
throw new Error('Error parsing system info');
|
||||
} else {
|
||||
return info.data;
|
||||
}
|
||||
};
|
||||
|
||||
public update = async (): Promise<boolean> => {
|
||||
const { current, latest } = await this.getVersion();
|
||||
|
||||
if (getConfig().NODE_ENV === 'development') {
|
||||
throw new Error('Cannot update in development mode');
|
||||
}
|
||||
|
||||
return { current: getConfig().version, latest: version?.replace('v', '') };
|
||||
} catch (e) {
|
||||
Logger.error(e);
|
||||
return { current: getConfig().version, latest: undefined };
|
||||
}
|
||||
};
|
||||
if (!latest) {
|
||||
throw new Error('Could not get latest version');
|
||||
}
|
||||
|
||||
const systemInfo = (): z.infer<typeof systemInfoSchema> => {
|
||||
const info = systemInfoSchema.safeParse(readJsonFile('/runtipi/state/system-info.json'));
|
||||
if (semver.gt(current, latest)) {
|
||||
throw new Error('Current version is newer than latest version');
|
||||
}
|
||||
|
||||
if (!info.success) {
|
||||
throw new Error('Error parsing system info');
|
||||
} else {
|
||||
return info.data;
|
||||
}
|
||||
};
|
||||
if (semver.eq(current, latest)) {
|
||||
throw new Error('Current version is already up to date');
|
||||
}
|
||||
|
||||
const restart = async (): Promise<boolean> => {
|
||||
if (getConfig().NODE_ENV === 'development') {
|
||||
throw new Error('Cannot restart in development mode');
|
||||
}
|
||||
if (semver.major(current) !== semver.major(latest)) {
|
||||
throw new Error('The major version has changed. Please update manually (instructions on GitHub)');
|
||||
}
|
||||
|
||||
setConfig('status', 'RESTARTING');
|
||||
EventDispatcher.dispatchEventAsync('restart');
|
||||
setConfig('status', 'UPDATING');
|
||||
|
||||
return true;
|
||||
};
|
||||
this.dispatcher.dispatchEventAsync('update');
|
||||
|
||||
const update = async (): Promise<boolean> => {
|
||||
const { current, latest } = await getVersion();
|
||||
return true;
|
||||
};
|
||||
|
||||
if (getConfig().NODE_ENV === 'development') {
|
||||
throw new Error('Cannot update in development mode');
|
||||
}
|
||||
public restart = async (): Promise<boolean> => {
|
||||
if (getConfig().NODE_ENV === 'development') {
|
||||
throw new Error('Cannot restart in development mode');
|
||||
}
|
||||
|
||||
if (!latest) {
|
||||
throw new Error('Could not get latest version');
|
||||
}
|
||||
setConfig('status', 'RESTARTING');
|
||||
this.dispatcher.dispatchEventAsync('restart');
|
||||
|
||||
if (semver.gt(current, latest)) {
|
||||
throw new Error('Current version is newer than latest version');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
if (semver.eq(current, latest)) {
|
||||
throw new Error('Current version is already up to date');
|
||||
}
|
||||
|
||||
if (semver.major(current) !== semver.major(latest)) {
|
||||
throw new Error('The major version has changed. Please update manually (instructions on GitHub)');
|
||||
}
|
||||
|
||||
setConfig('status', 'UPDATING');
|
||||
|
||||
EventDispatcher.dispatchEventAsync('update');
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
export const SystemService = {
|
||||
getVersion,
|
||||
systemInfo,
|
||||
restart,
|
||||
update,
|
||||
status,
|
||||
};
|
||||
public static status = async (): Promise<{ status: SystemStatus }> => ({
|
||||
status: getConfig().status as SystemStatus,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3,11 +3,11 @@ import { faker } from '@faker-js/faker';
|
|||
import * as argon2 from 'argon2';
|
||||
import { prisma } from '../db/client';
|
||||
|
||||
const createUser = async (email?: string) => {
|
||||
const createUser = async (email?: string, db = prisma) => {
|
||||
const hash = await argon2.hash('password');
|
||||
|
||||
const username = email?.toLowerCase().trim() || faker.internet.email().toLowerCase().trim();
|
||||
const user = await prisma.user.create({ data: { username, password: hash } });
|
||||
const user = await db.user.create({ data: { username, password: hash } });
|
||||
|
||||
return user;
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue