dev
This commit is contained in:
+6
-5
@@ -1,9 +1,10 @@
|
||||
export const PERMISSIONS = {
|
||||
VIEW_POSTINGS: 0b00000001,
|
||||
VIEW_ACCOUNT: 0b00000010,
|
||||
APPLY_FOR_JOBS: 0b00000100,
|
||||
SUBMIT_POSTINGS: 0b00001000,
|
||||
VIEW: 0b00000001,
|
||||
APPLY_FOR_JOBS: 0b0000010,
|
||||
SUBMIT_POSTINGS: 0b0000100,
|
||||
MANAGE_EMPLOYERS: 0b00001000,
|
||||
MANAGE_TAGS: 0b00010000,
|
||||
MANAGE_POSTINGS: 0b00100000,
|
||||
MANAGE_USERS: 0b01000000
|
||||
MANAGE_USERS: 0b01000000,
|
||||
MANAGE_COMPANIES: 0b10000000
|
||||
};
|
||||
|
||||
+166
-74
@@ -1,100 +1,192 @@
|
||||
import bcrypt from 'bcrypt';
|
||||
import sql from '$lib/db/db.server';
|
||||
import type { Cookies } from '@sveltejs/kit';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { saveAvatar } from '$lib/index.server';
|
||||
|
||||
export async function createUser(username: string, password: string): Promise<void> {
|
||||
const password_hash: string = await bcrypt.hash(password, 12);
|
||||
const timestamp = new Date(Date.now()).toISOString();
|
||||
|
||||
console.log(timestamp);
|
||||
export async function createUser(user: User): Promise<number> {
|
||||
const password_hash: string = await bcrypt.hash(user.password!, 12);
|
||||
|
||||
const response = await sql`
|
||||
INSERT INTO users (username, password_hash, perms, created_at, last_signin, active)
|
||||
VALUES (${username}, ${password_hash}, 3, ${timestamp}, ${timestamp}, ${true});
|
||||
INSERT INTO users (username, password_hash, perms, created_at, last_signin, active, email, phone, full_name, company_code)
|
||||
VALUES (${user.username}, ${password_hash}, ${user.perms}, NOW(), NOW(), ${user.active}, ${user.email}, ${user.phone}, ${user.fullName}, ${user.companyCode})
|
||||
RETURNING id;
|
||||
`;
|
||||
|
||||
// TODO: handle custom image uploads
|
||||
user.id = response[0].id;
|
||||
await saveAvatar(user);
|
||||
|
||||
return response[0].id;
|
||||
}
|
||||
|
||||
export async function checkUserCreds(username: string, password: string): Promise<number> {
|
||||
export async function updateUser(user: User): Promise<number> {
|
||||
let password_hash: string | null = null;
|
||||
|
||||
// Hash the password if provided
|
||||
if (user.password) {
|
||||
password_hash = await bcrypt.hash(user.password, 12);
|
||||
}
|
||||
|
||||
// Construct the SQL query
|
||||
const response = await sql`UPDATE users
|
||||
SET
|
||||
username = ${user.username},
|
||||
perms = ${user.perms},
|
||||
active = ${user.active},
|
||||
${password_hash !== null ? sql`password_hash = ${password_hash},` : sql``}
|
||||
email = ${user.email},
|
||||
phone = ${user.phone},
|
||||
full_name = ${user.fullName},
|
||||
company_code = ${user.companyCode}
|
||||
WHERE id = ${user.id}
|
||||
RETURNING id;`;
|
||||
|
||||
await saveAvatar(user);
|
||||
|
||||
return response[0].id;
|
||||
}
|
||||
|
||||
export async function checkUserCreds(username: string, password: string): Promise<User | null> {
|
||||
const [user] = await sql`
|
||||
SELECT password_hash, perms
|
||||
SELECT id, password_hash, perms, active
|
||||
FROM users
|
||||
WHERE username = ${username}
|
||||
`;
|
||||
|
||||
if (!user) {
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
if (await bcrypt.compare(password, user.password_hash)) {
|
||||
return user['perms'];
|
||||
return <User>{ id: user.id, perms: user.perms, active: user.active };
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function getUserPerms(cookies: Cookies): number {
|
||||
if (process.env.JWT_SECRET === undefined) {
|
||||
throw new Error('JWT_SECRET not defined');
|
||||
}
|
||||
|
||||
const JWT = cookies.get('jwt');
|
||||
if (JWT) {
|
||||
try {
|
||||
const decoded = jwt.verify(JWT, process.env.JWT_SECRET);
|
||||
if (typeof decoded === 'object' && 'perms' in decoded) {
|
||||
return decoded['perms'];
|
||||
}
|
||||
} catch (err) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
return null;
|
||||
}
|
||||
|
||||
// should require MANAGE_USERS permission
|
||||
export async function getUsers(): Promise<User[]> {
|
||||
const users = await sql<
|
||||
{
|
||||
id: number;
|
||||
username: string;
|
||||
perms: number;
|
||||
created_at: Date;
|
||||
last_signin: Date;
|
||||
active: boolean;
|
||||
}[]
|
||||
>`
|
||||
SELECT id, username, perms,
|
||||
created_at AT TIME ZONE 'UTC' AS created_at,
|
||||
last_signin AT TIME ZONE 'UTC' AS last_signin,
|
||||
active
|
||||
FROM users;
|
||||
export async function getUsers(searchQuery: string | null = null): Promise<User[]> {
|
||||
const users = await sql`
|
||||
SELECT id,
|
||||
username,
|
||||
perms,
|
||||
created_at AT TIME ZONE 'UTC' AS "createdAt",
|
||||
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
|
||||
active,
|
||||
email,
|
||||
phone,
|
||||
full_name AS "fullName",
|
||||
company_id
|
||||
FROM users
|
||||
WHERE username ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
|
||||
`;
|
||||
users.forEach((user) => {
|
||||
user.company = {
|
||||
id: user.company_id
|
||||
};
|
||||
delete user.company_id;
|
||||
});
|
||||
return <User[]>(<unknown>users);
|
||||
}
|
||||
|
||||
// should require MANAGE_USERS permission
|
||||
export async function getUser(id: number): Promise<User> {
|
||||
const [user] = await sql`
|
||||
SELECT id, username, perms,
|
||||
created_at AT TIME ZONE 'UTC' AS "createdAt",
|
||||
last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
|
||||
active, email, phone, full_name AS "fullName", company_id, company_code
|
||||
FROM users
|
||||
WHERE id = ${id};
|
||||
`;
|
||||
if (!user) {
|
||||
error(404, 'User not found');
|
||||
}
|
||||
user.company = {
|
||||
id: user.company_id
|
||||
};
|
||||
delete user.company_id;
|
||||
return <User>user;
|
||||
}
|
||||
|
||||
export async function getUserWithCompany(id: number): Promise<User> {
|
||||
const [user] = await sql`
|
||||
SELECT
|
||||
u.id,
|
||||
u.username,
|
||||
u.perms,
|
||||
u.email,
|
||||
u.phone,
|
||||
u.full_name AS "fullName",
|
||||
u.created_at AT TIME ZONE 'UTC' AS "createdAt",
|
||||
u.last_signin AT TIME ZONE 'UTC' AS "lastSignIn",
|
||||
u.active,
|
||||
c.id AS company_id,
|
||||
c.name AS company_name,
|
||||
c.description AS company_description,
|
||||
c.website AS company_website,
|
||||
c.created_at AS company_created_at
|
||||
FROM
|
||||
users u
|
||||
LEFT JOIN
|
||||
companies c ON u.company_id = c.id
|
||||
WHERE
|
||||
u.id = ${id};
|
||||
`;
|
||||
if (!user) {
|
||||
error(404, 'User not found');
|
||||
}
|
||||
user.company = {
|
||||
id: user.company_id,
|
||||
name: user.company_name,
|
||||
description: user.company_description,
|
||||
website: user.company_website,
|
||||
createdAt: user.company_created_at
|
||||
};
|
||||
|
||||
// Remove the company-specific columns from the user object
|
||||
delete user.company_id;
|
||||
delete user.company_name;
|
||||
delete user.company_description;
|
||||
delete user.company_website;
|
||||
delete user.company_created_at;
|
||||
|
||||
return <User>user;
|
||||
}
|
||||
|
||||
// should require MANAGE_USERS permission
|
||||
export async function deleteUser(id: number): Promise<void> {
|
||||
const response = await sql`
|
||||
DELETE FROM users
|
||||
WHERE id = ${id};
|
||||
`;
|
||||
return users.map(
|
||||
(user): User => ({
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
perms: user.perms,
|
||||
created_at: user.created_at,
|
||||
last_signin: user.last_signin,
|
||||
active: user.active
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// should require MANAGE_TAGS permission
|
||||
export async function getTags(): Promise<Tag[]> {
|
||||
const tags = await sql<
|
||||
{
|
||||
id: number;
|
||||
display_name: string;
|
||||
}[]
|
||||
>`
|
||||
SELECT id, display_name
|
||||
FROM tags;
|
||||
export async function getTags(searchQuery: string | null): Promise<Tag[]> {
|
||||
return sql<Tag[]>`
|
||||
SELECT id, display_name as "displayName", created_at AT TIME ZONE 'UTC' AS "createdAt"
|
||||
FROM tags
|
||||
WHERE display_name ILIKE ${searchQuery ? `%${searchQuery}%` : '%'};
|
||||
`;
|
||||
return tags.map(
|
||||
(tag): Tag => ({
|
||||
id: tag.id,
|
||||
display_name: tag.display_name
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
export async function updateLastSignin(username: string): Promise<void> {
|
||||
await sql`
|
||||
UPDATE users
|
||||
SET last_signin = NOW()
|
||||
WHERE username = ${username};
|
||||
`;
|
||||
}
|
||||
|
||||
export async function createCompany(
|
||||
name: string,
|
||||
description: string,
|
||||
website: string
|
||||
): Promise<number> {
|
||||
const response = await sql`
|
||||
INSERT INTO companies (name, description, website, created_at, company_code)
|
||||
VALUES (${name}, ${description}, ${website}, NOW(), generate_company_code(CAST(CURRVAL('companies_id_seq') AS INT)))
|
||||
RETURNING id;
|
||||
`;
|
||||
|
||||
return response[0].id;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import fetch from 'node-fetch';
|
||||
import { type Cookies, error } from '@sveltejs/kit';
|
||||
import jwt from 'jsonwebtoken';
|
||||
import { getUserWithCompany } from '$lib/db/index.server';
|
||||
|
||||
// TODO: Handle saving custom avatar uploads
|
||||
export async function saveAvatar(user: User): Promise<void> {
|
||||
const url = `https://ui-avatars.com/api/?background=random&format=svg&name=${user.fullName ? encodeURIComponent(user.fullName) : encodeURIComponent(user.username)}`;
|
||||
const response = await fetch(url, { headers: { accept: 'image/svg+xml' } });
|
||||
const avatar = await response.text();
|
||||
const filePath = path.join('static', 'uploads', 'avatars', `${user.id}.svg`);
|
||||
fs.writeFileSync(filePath, avatar);
|
||||
}
|
||||
|
||||
export function getUserPerms(cookies: Cookies): number {
|
||||
if (process.env.JWT_SECRET === undefined) {
|
||||
throw new Error('JWT_SECRET not defined');
|
||||
}
|
||||
|
||||
const JWT = cookies.get('jwt');
|
||||
if (JWT) {
|
||||
try {
|
||||
const decoded = jwt.verify(JWT, process.env.JWT_SECRET);
|
||||
if (typeof decoded === 'object' && 'perms' in decoded) {
|
||||
return decoded['perms'];
|
||||
}
|
||||
} catch (err) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
export function getUserId(cookies: Cookies): number {
|
||||
if (process.env.JWT_SECRET === undefined) {
|
||||
throw new Error('JWT_SECRET not defined');
|
||||
}
|
||||
|
||||
const JWT = cookies.get('jwt');
|
||||
if (JWT) {
|
||||
try {
|
||||
const decoded = jwt.verify(JWT, process.env.JWT_SECRET);
|
||||
if (typeof decoded === 'object' && 'id' in decoded) {
|
||||
return decoded['id'];
|
||||
}
|
||||
} catch (err) {
|
||||
error(403, 'Unauthorized');
|
||||
}
|
||||
}
|
||||
error(403, 'Unauthorized');
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
@@ -1 +1,21 @@
|
||||
export let userState = $state({ perms: 0b00000001 });
|
||||
import { PERMISSIONS } from '$lib/consts';
|
||||
|
||||
export let userState = $state({ perms: PERMISSIONS.VIEW, username: null, id: null });
|
||||
|
||||
export const userPerms = PERMISSIONS.VIEW | PERMISSIONS.APPLY_FOR_JOBS;
|
||||
export const employerPerms = PERMISSIONS.SUBMIT_POSTINGS | PERMISSIONS.MANAGE_EMPLOYERS;
|
||||
export const adminPerms =
|
||||
PERMISSIONS.MANAGE_TAGS |
|
||||
PERMISSIONS.MANAGE_POSTINGS |
|
||||
PERMISSIONS.MANAGE_USERS |
|
||||
PERMISSIONS.MANAGE_COMPANIES;
|
||||
|
||||
export function telFormatter(initial: string) {
|
||||
const num = initial.replace(/\D/g, '');
|
||||
initial =
|
||||
(num.length > 0 ? '(' : '') +
|
||||
num.substring(0, 3) +
|
||||
(num.length > 3 ? ') ' + num.substring(3, 6) : '') +
|
||||
(num.length > 6 ? '-' + num.substring(6, 10) : '');
|
||||
return initial;
|
||||
}
|
||||
|
||||
+18
-3
@@ -1,13 +1,28 @@
|
||||
interface User {
|
||||
id: number | null;
|
||||
username: string;
|
||||
password: string | null;
|
||||
perms: number;
|
||||
created_at: Date | null;
|
||||
last_signin: Date | null;
|
||||
createdAt: Date | null;
|
||||
lastSignIn: Date | null;
|
||||
active: boolean | null;
|
||||
email: string | null;
|
||||
phone: string | null;
|
||||
fullName: string | null;
|
||||
company: Company | null;
|
||||
companyCode: string | null;
|
||||
}
|
||||
|
||||
interface Tag {
|
||||
id: number;
|
||||
display_name: string;
|
||||
displayName: string;
|
||||
createdAt: Date | null;
|
||||
}
|
||||
|
||||
interface Company {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
website: string;
|
||||
createdAt: Date | null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user